Create multiple threads from within your application


When I posted this Sample program to create multiple threads, I knew the inevitable follow-up question was “can I run my VFP code in separate threads?”. Sure enough, several people asked, citing various valid usage scenarios.


 


Below is a class that you can use to run your VFP code in multiple separate threads. It can create as many threads as you like, each of which is running VFP code. Because it is a multithreaded sample, it requires the multithreaded runtime, which is just a few megabytes.


 


The sample code uses the class by creating a routine called MyThreadFunc, which is a CPU intensive task that sums the integers from 1 to some large fixed number and then inserts the result into a table. This task is repeated a few times. The time is measured to call MyThreadFunc from N+1 different threads (including the main thread). That time is compared with calling the same code the same number of times, but only from the main thread.


 


On my dual processor machine (with hyperthreading on), with 10 threads, the performance was almost double the performance of a single thread, as expected. On my single processor laptop, the performance is roughly the same, with the single threaded slightly faster than the multithreaded, due to thread overhead.


 


If I change the task to be less CPU intensive and more shared resource intensive by making it update a single shared table multiple times, the performance gain decreases, due to contention for a single shared resource as expected.


 


The code requires that you have a multithreaded COM DLL built from this code: Blogs get 300 hits per hour: Visual FoxPro can count. The Ic1 interface and the MyDoCmd method are defined in that DLL.


 


This code really maxes out your CPU when running with many threads. In fact, while I had task man open, I saw the CPU usage at very low numbers for an instant when I expected it to be very high because Taskman didn’t get enough CPU to update its display!


 


Observe from the results table that various threads complete at various times, interweaving their results, meaning that in the middle of computation, the threads are swapped out.


 


What kind of performance numbers do you see?


 


Here’s how ThreeadClass works: it allocates memory for various items, such as GUIDs, strings, and the generated code. It generates machine code into a string, and calls CreateThread, pointing to that string as the Thread procedure to execute. The threads are stored in an array. CoCreateInstance  is called to create an instance of the VFP COM object. That object’s MyDoCmd method is called via early binding through it’s vTable.


The vTable of the Ic1 interface  (which inherits from IDispatch, which inherits from IUnknown) is expected to be mapped out like this:


0          QueryInterface               IUnknown


1          AddRef                          IUnknown


2          Release                        IUnknown


3          GetTypeInfoCount          IDispatch


4          GetTypeInfo                   IDispatch


5          GetIDsOfNames IDispatch


6          Invoke                           IDispatch


7          MyDoCmd                     Ic1


8          MyEval                          Ic1


 


For more on vTable layout see my Paper on Visual Foxpro and Advanced COM


 


MyDoCmd is entry #7. 7 * 4 bytes per pointer = 28, which is 1c in hex.


            sCode = sCode + CHR(0xff) + CHR(0x50) + CHR(0x1c)     && call  [eax+1ch] && call indirect the function at 1ch in the vTable


Likewise, the Release is entry #2, so the offset is 8.


 


The preamble does a PUSH EBP, MOV EBP, ESP to set up a stack frame. At the end, there is a MOV ESP, EBP, POP EBP and RET 4 to release the frame. The stack frame means local variables can be accessed via a negative offset from EBP and any parameters with a positive offset. The RET 4 pops off the single 4 byte argument (bstrArg) that was passed in and returns, terminating the thread.


 


If your t1.dll is in a COM + application (as mine is on some machines), then you will need to specify your particular Guid for Ic1 (found in your t1.vbr file) instead of IDispatch. Then COM will know to marshall the rest of the vTable.  In a COM+ application the ProcessIds will be different:


 


x=CREATEOBJECT(“t1.c1”)


?_vfp.ProcessId, x.MyEval(“_vfp.ProcessId”)


 


A COM+ app also might not have rights to write to the disk, and may not shut down the server when you expect: it might keep an instance around for quick activation. It also will probably have a different Current Directory, so may not find MyThreadProc.prg


 


All threads in the sample run the same code, but you can certainly make them run different code various ways. Also, the threads can be kept alive in a thread pool, perhaps waiting for more tasks to execute.


 


As an interesting exercise, try making the thread procedure just Sleep 10 seconds


            IF .t. OR p2=”Thread”


                  DECLARE integer Sleep IN WIN32API integer


                  Sleep(10000)


            ENDIF


 


With 10 threads, the 11 calls to ThreadProc (including the one in the main thread), the Sleeps occur in parallel and it finishes in 10 seconds. In a single threaded app, it takes 110 seconds to execute them in series!


 


The machine code generated is similar to this pseudo C++ code which has no error checking


 


DWORD WINAPI ThreadProc(LPVOID dwParam)


{


      IDispatch *ptrCOMObj;


      VARIANT vtResult;


      CoInitialize(0);  // initialize COM


 


      CoCreateInstance(cClsId,0, CLSCTX_INPROC_SERVER+CLSCTX_LOCAL_SERVER, &cIid, &ptrCOMObj);


      VariantInit(&vtResult);


      ptrComObj->MyDoCmd(bstrCmd(“do d:\fox90\test\MyThreadFunc WITH p2”), dwParam as Variant,vtEmpty,vtEmpty,vtEmpty,&vtResult);


      VariantClear(vtResult); //unused, but needs to be freed


      ptrCOMObj->Release();   // release the COM server


      CoUninitialize();


 


}


 


 


 


I used a C++ project in Visual Studio with inline ASM code and Show Disassembly to get the machine language bytes. My Intel Programmers Reference manual was also useful. The thread proc isn’t optimized, but it doesn’t have to be. Almost no time is spent there, compared with calling the COM server.


 


See also: Windows Security and how it affects running generated code


 


The VFP code (with minimal error checking):


 


CLEAR ALL


CLEAR


 


SET EXCLUSIVE OFF


SET SAFETY OFF


SET ASSERTS ON


CREATE TABLE ThreadLog (threadid i, timestamp t,misc c(50)) && A table into which each thread will insert results


USE ThreadLog && open shared


TEXT TO cstrVFPCode TEXTMERGE NOSHOW && generate the task to run: MyThreadFunc


      PROCEDURE MyThreadFunc(p2)    && p2 is the 2nd param to MyDoCmd


            TRY   && use exception handling


                  DECLARE integer GetCurrentThreadId in WIN32API


                  LOCAL i,j,k,nSum


                  FOR k = 1 TO 3    && calculate result 3 times per thread


                        nSum=0


                        FOR i = 1 TO 500000


                              nSum=nSum+i


                        ENDFOR


                        INSERT INTO <<DBF()>> VALUES (GetCurrentThreadId(), DATETIME(),TRANSFORM(p2)+”:”+TRANSFORM(nSum))


                  ENDFOR


            CATCH TO oex


                  INSERT INTO <<DBF()>> VALUES (GetCurrentThreadId(), DATETIME(),p2+” Error: “+oex.message)


            ENDTRY


            RETURN


ENDTEXT


STRTOFILE(cstrVFPCode,”MyThreadFunc.prg”)


COMPILE MyThreadFunc.prg


 


 


nThreads=10             && WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS = 64


 


nStart=SECONDS()


      ox=CREATEOBJECT(“ThreadClass”)


      cStrIid=”{00020400-0000-0000-C000-000000000046}”      && IID_IDispatch


*     cStrIid=”{3608114E-633A-44FF-8E51-1BBCF7225018}”      && IID_Ic1 from your t1.vbr file.


      ox.StartThreads(nThreads,”do “+SYS(5)+CURDIR()+”MyThreadFunc WITH p2″,”Thread: %threadnum”,cStrIid)


      ?TRANSFORM(nThreads)+” threads created. Main thread calculating…”


      MyThreadFunc(“Main“)    && main thread will do calculation too, rather than just being idle


      ?”Main thread done: waiting for other threads”


      ox.WaitForThreads(“?’main thread waiting'”)     && wait til threads finish


?”Using “+TRANSFORM(nThreads)+” threads takes “+TRANSFORM(SECONDS()-nStart)+” seconds”


 


?”Now try single threaded:”


      nStart=SECONDS()


      FOR i = 1 TO nThreads+1 && add one for the main thread


            MyThreadFunc(“SingleThread”)


      ENDFOR


?”Single thread takes “+TRANSFORM(SECONDS()-nStart)+” seconds”


 


LOCATE && Go to the first record


BROWSE LAST NOWAIT      && show the results


 


 


#define CREATE_SUSPENDED                  0x00000004


#define INFINITE            0xFFFFFFFF 


#define WAIT_TIMEOUT                     258


#define CLSCTX_INPROC_SERVER 1


#define CLSCTX_LOCAL_SERVER 4


#define     VT_BSTR  8


 


DEFINE CLASS ThreadClass as session


      hProcHeap =0


      nThreads=0


      DIMENSION hThreads[1]   && Handle to each thread


      cThreadHandles=”” && Handle to each thread as a string rep of an int array


      PROCEDURE Init


            DECLARE integer LoadLibrary IN WIN32API string


            DECLARE integer FreeLibrary IN WIN32API integer


            DECLARE integer GetProcAddress IN WIN32API integer hModule, string procname


            DECLARE integer CreateThread IN WIN32API integer lpThreadAttributes, ;


                  integer dwStackSize, integer lpStartAddress, integer lpParameter, integer dwCreationFlags, integer @ lpThreadId


            DECLARE integer ResumeThread IN WIN32API integer thrdHandle


            DECLARE integer CloseHandle IN WIN32API integer Handle


            DECLARE integer GetProcessHeap IN WIN32API


            DECLARE integer HeapAlloc IN WIN32API integer hHeap, integer dwFlags, integer dwBytes


            DECLARE integer HeapFree IN WIN32API integer hHeap, integer dwFlags, integer lpMem


            DECLARE integer WaitForSingleObject IN WIN32API integer hHandle, integer dwMilliseconds


            DECLARE integer WaitForMultipleObjects IN WIN32API integer nCount, string pHandles, integer bWaitAll, integer dwMsecs


            DECLARE integer CLSIDFromProgID IN ole32 string lpszProgID, string @ strClSID


            DECLARE integer CLSIDFromString IN ole32 string lpszProgID, string @ strClSID


            DECLARE integer SysAllocString IN oleaut32 string wstr


            DECLARE integer SysFreeString IN oleaut32 integer bstr


            CREATE CURSOR memAllocs (memPtr i, AllocType c(1))    && track mem allocs that need to be freed: H=Heap,B=BSTR,L=Library


            this.hProcHeap = GetProcessHeap()


      PROCEDURE StartThreads(nThreads as Integer, ThreadCmd as String, ThreadProcParam as String,cStrIid as String )


            this.nThreads = nThreads


            cClsId=SPACE(16)


            IF CLSIDFromProgID(STRCONV(“t1.c1″+CHR(0),5),@cClsId)!= 0   && dual interface


                  ?”Error: class not found”


                  RETURN


            ENDIF


            cIid=SPACE(16)


            CLSIDFromString(STRCONV(cStrIid+CHR(0),5),@cIid)


            nLocals = 30      && sufficiently large for local vars


            sCode=””          && generate machine code for thread procedure into a string


*           sCode = sCode + CHR(0xcc)     && int 3    DebugBreak() to attach a debugger


            sCode = sCode + CHR(0x55)                                                                 && push ebp


            sCode = sCode + CHR(0x8b) + CHR(0xec)                                               && mov ebp, esp


            sCode = sCode + CHR(0x81) + CHR(0xec)+BINTOC(nLocals * 4, “4rs”) && sub esp, nLocals


 


            sCode = sCode + CHR(0x6a) + CHR(0x00)     && push 0


            sCode = sCode + this.CallDllFunction(“CoInitialize”, “ole32”)


     


            sCode = sCode + this.GenCodeAtPoint(“BeforeStart”)


            sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xf0) && lea eax, [ebp-10h]   && addr to put COM ptr


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0xb8) + BINTOC(this.MakeStr(cIid),”4rs”)      && mov eax, str


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0xb8) + BINTOC(CLSCTX_INPROC_SERVER+CLSCTX_LOCAL_SERVER,”4rs”)      && mov eax, val


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0x6a) + CHR(0x00)     && push 0


            sCode = sCode + CHR(0xb8) + BINTOC(this.MakeStr(cClsId),”4rs”)    && mov eax, str


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + this.CallDllFunction(“CoCreateInstance”, “ole32”)


            sCode = sCode + this.GenCodeAtPoint(“Creating”)


 


            sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xe0) && lea eax, [ebp-20h]   && local var to get the vtResult of the COM call


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + this.CallDllFunction(“VariantInit”, “oleaut32”)   && Initialize the vtResult


 


            *call MyDoCmd via early binding. First push the parms


            sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xe0) && lea eax, [ebp-20h]   && pass the address of vtResult for return value


            sCode = sCode + CHR(0x50)     && push eax


            *Now we need to push 3 empty variants, each of which is 4 DWORDS


            sCode = sCode + CHR(0x33) + CHR(0xc0)     && xor eax,eax


            sCode = sCode + REPLICATE(CHR(0x50),12)   && push eax 12 times


           


            *2nd param is P2:


            sCode = sCode + CHR(0x33) + CHR(0xc0)     && xor eax,eax


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0x8b) + CHR(0x45) + CHR(0x8)      && mov eax,[ebp+8]      && Form the P2 param as a Variant from the BSTR arg from the parent thread


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0x33) + CHR(0xc0)     && xor eax,eax


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0xb8) + BINTOC(VT_BSTR,”4rs”)     && mov eax, VT_BSTR


            sCode = sCode + CHR(0x50)     && push eax


           


            *1st param is the expr for VFP to Exec.


            sCode = sCode + CHR(0xb8)+BINTOC(this.MakeStr(ThreadCmd,.t.,.t.),”4rs”) && mov eax, cExpr (p2 is 2nd param to MyDoCmd)


            sCode = sCode + CHR(0x50)     && push eax


 


            *Now make the call


            sCode = sCode + CHR(0x8b) + CHR(0x45) + CHR(0xf0)     && mov eax, [ebp-10h]   && the COM ptr


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + CHR(0x8b) + CHR(0)  && mov eax, [eax] && et the vTable


            sCode = sCode + CHR(0xff) + CHR(0x50) + CHR(0x1c)     && call  [eax+1ch] && call indirect the function at 1ch in the vTable


            sCode = sCode + this.GenCodeAtPoint(“Calling”)


 


            *Free the return value with VariantClear because it’s ignored


            sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xe0) && lea eax, [ebp-20h]


            sCode = sCode + CHR(0x50)     && push eax


            sCode = sCode + this.CallDllFunction(“VariantClear”, “oleaut32”)


 


            sCode = sCode + this.GenEndCode(.t.)


 


            AdrCode=this.memAlloc(LEN(sCode),sCode)   && allocate memory for the code


            DIMENSION this.hThreads[nThreads]


            this.cThreadHandles=””


            FOR i = 1 TO nThreads


                  bstrArg=this.MakeStr(STRTRAN(ThreadProcParam,”%threadnum”,TRANSFORM(i)),.t.,.t.)


                  this.hThreads[i] = CreateThread(0,8192, AdrCode, bstrArg, 0, 0)


                  this.cThreadHandles = this.cThreadHandles+BINTOC(this.hThreads[i],”4rs”)      && put the handles into a string rep of an int array


            ENDFOR


      PROCEDURE GenCodeAtPoint(nPoint as String) as String  && derived classes can override to gen code to exec at various points


            RETURN “”


      PROCEDURE GenEndCode(fRelease as Boolean) as String   && generate code to end thread


            LOCAL sCode


            sCode=””


            IF fRelease && do we also release COM obj?


                  *ptr->Release()


                  sCode = sCode + CHR(0x8b) + CHR(0x45) + CHR(0xf0)     && mov eax, [ebp-10h]


                  sCode = sCode + CHR(0x50)     && push eax             && push the THIS ptr


                  sCode = sCode + CHR(0x8b) + CHR(0)  && mov eax, [eax] && get the vTable


                  sCode = sCode + CHR(0xff) + CHR(0x50) + CHR(0x8)      && call  [eax+8h]


            ENDIF


           


            sCode = sCode + this.GenCodeAtPoint(“BeforeEnd”)


            sCode = sCode + this.CallDllFunction(“CoUninitialize”, “ole32”)


 


            sCode = sCode + CHR(0x33) + CHR(0xc0)     && xor eax,eax    && make ExitCodeThread= 0


            sCode = sCode + CHR(0x8b) + CHR(0xe5)     && mov esp, ebp


            sCode = sCode + CHR(0x5d)                       && pop ebp       


            sCode = sCode + CHR(0xc2)+CHR(0x04)+CHR(0x00)   && ret 4


            RETURN sCode


 


      PROCEDURE WaitForThreads(cExpr as String)


            DO WHILE WaitForMultipleObjects(this.nThreads, this.cThreadHandles, 1, 500) = WAIT_TIMEOUT      && wait msecs for the threads to finish


                  &cExpr      && execute any passed in param while waiting


            ENDDO


            FOR i = 1 TO this.nThreads


                  CloseHandle(this.hThreads[i])


            ENDFOR


      PROCEDURE MemAlloc(nSize as Integer, cStr as String) as Integer


            LOCAL nAddr


            nAddr = HeapAlloc(this.hProcHeap, 0, nSize)     && allocate memory


            ASSERT nAddr != 0 MESSAGE “Out of memory”


            INSERT INTO memAllocs VALUES (nAddr,”H”) && track them for freeing later


            SYS(2600,nAddr, LEN(cStr),cStr)           && copy the string into the mem


            RETURN nAddr


      PROCEDURE CallDllFunction(strExport as String, strDllName as String) as String


            *Create a string of machine code that calls a function in a DLL. Parms should already be pushed


            LOCAL nAddr as Integer, hModule as Integer


            hModule = LoadLibrary(strDllName)


            INSERT INTO memAllocs VALUES (hModule,”L”)      && track loads for freeing later


            nAddr=GetProcAddress(hModule,strExport)


            ASSERT nAddr != 0 MESSAGE “Error: Export not found “+ strExport+” “+ strDllName


            RETURN CHR(0xb8)+BINTOC(nAddr,”4rs”) + CHR(0xff) + CHR(0xd0)      && mov eax, addr; call eax


      PROCEDURE MakeStr(str as String, fConvertToUnicode as Logical, fMakeBstr as Logical) as Integer


            * converts a string into a memory allocation and returns a pointer


            LOCAL nRetval as Integer


            IF fConvertToUnicode


                  str=STRCONV(str+CHR(0),5)


            ELSE


                  str = str + CHR(0)      && null terminate


            ENDIF


            IF fMakeBstr


                  nRetval= SysAllocString(str)


                  ASSERT nRetval != 0 MESSAGE “Out of memory”


                  INSERT INTO memAllocs VALUES (nRetval,”B”)      && track them for freeing later


            ELSE


                  nRetval= this.MemAlloc(LEN(str),str)


            ENDIF


            RETURN nRetval


      PROCEDURE Destroy


            SELECT memAllocs


            SCAN


                  DO CASE


                  CASE AllocType=”B”      && BSTR


                        SysFreeString(memPtr)


                  CASE AllocType=”H”      && Heap


                        HeapFree(this.hProcHeap,0,memPtr)


                  CASE AllocType=”L”      && LoadLibrary


                        FreeLibrary(memPtr)


                  ENDCASE


            ENDSCAN


ENDDEFINE


 

Comments (25)

  1. Claude Fox says:

    So a regular VFP exe could call the methods of this class from the mtdll? Or am I missing something?  Can you give an operational example of this in a VFP GUI type app that needs to do something like print in the background?

  2. Rick Strahl says:

    Calvin,

    Is all that really necessary? It seems to me an easier way to do this is to create a couple of generic methods in a DLL or FLL and have it act as a wrapper around the various COM invokation APIs. Sure you can do this in Fox code with code injection, but man is that messy <s>…

    I’m thinking you’d want to have two ways to fire this off – one as you’re doing using the VFP runtime objects to execute a PRG file via DoCmd/Evaluate – so you can have have something approaching true dynamic execution – and another to fire off a generic COM Server method from the code so you can call a separately compiled VFP (or other) COM Server.

    Sort of like I am doing in the .NET sample I posted.

    You’ve given me a couple of good ideas of things to add to my DLL library here <s>…

  3. Franklin Garzón says:

    I believe that the implementation of multiple threads must be by means of functions added like for example: do form <my form> object type 1 multihead =.t. Or begin multimultithread …. instructions … endmultithread or select …(select * from tabla) as calculo from othertable) with multitahread=.t..

    Also I believe that in VFP it should add the funsión sleep (200) since at present many programmers use the old trick of for i=1 to 2000 endfor.

  4. Sanjay says:

    Calvin,

      I am using DLL’s as MTS components to process jobs in seperate threads. This is easy for me as i dont know much about 8086/8088 assembly lanuage.

      I found your example quite good and has made my understanding a lot clearer on this subject.

  5. Gargon says:

    Is this better than using a timer/callback in vfp to multitask??

    && server objects (compile into COM-Exe)

    DEFINE CLASS oTestServer AS Session OLEPUBLIC

     loCallback = .NULL. && saves reference to callback object

     lAdvised = .F. && is callbackobject set?

     HIDDEN loTimer

     FUNCTION Init

    DECLARE INTEGER Sleep IN WIN32API INTEGER

    THIS.loTimer = CREATEOBJECT(‘dirTimer’,THIS)

     ENDFUNC

     FUNCTION Destroy

    THIS.loTimer = NULL

    CLEAR DLLS

     ENDFUNC

     FUNCTION FireEvent AS VOID

      LPARAMETERS lcSomeValue

      IF THIS.lAdvised

        THIS.loCallBack.EventMethod(lcSomeValue)

      ENDIF

     ENDFUNC

     FUNCTION Advise AS VOID

      LPARAMETERS loObj

      THIS.loCallback = loObj

      THIS.lAdvised = .T.

     ENDFUNC

     FUNCTION UnAdvise AS VOID

      THIS.loCallback = .NULL.

      THIS.lAdvised = .F.

     ENDFUNC

     FUNCTION SomeMethodThatFiresEvents AS Boolean

       && prepare something

       THIS.FireEvent(‘Task startet .. ‘)

       Sleep(5000)

       THIS.FireEvent(‘Task finished X percent ..’)

       Sleep(5000)

       && do more

       THIS.FireEvent(‘Task fineshied XX percent ..’)

       && ..

     ENDFUNC

     FUNCTION DoSomeAsynWork AS Boolean

      THIS.loTimer.Interval = 1

    RETURN .T.

     ENDFUNC

    ENDDEFINE

    DEFINE CLASS dirTimer AS Timer

    Interval = 0

    loParent = NULL

    FUNCTION Init

    LPARAMETERS loObj

    THIS.loParent = loObj

    ENDFUNC

    FUNCTION Destroy

    THIS.loParent = NULL

    ENDFUNC

    FUNCTION Timer

    THIS.Interval = 0

           && do some work

           Sleep(1000) && sleep to simulate some work ..

    THIS.loParent.FireEvent(‘Timer Fired’)

    ENDFUNC

    ENDDEFINE

    && client testprogram

    startprocess.prg:

    PUBLIC loFoxServer, loEventhandler

    loFoxServer = CREATEOBJECT(‘testcom1.oTestServer’)

    loEventHandler = CREATEOBJECT(‘oTestCallback’)

    loFoxServer.Advise(loEventHandler)

    DEFINE CLASS oTestCallback AS Session OLEPUBLIC

     FUNCTION EventMethod

       LPARAMETERS lcSomeParam

       ? lcSomeParam

     ENDFUNC

    ENDDEFINE

    startasyn.prg:

    ?loFoxServer.DoSomeAsynWork() && returns immediately ..

    && simulate block situation

    DECLARE INTEGER Sleep IN WIN32API INTEGER

    Sleep(15000) && the event of ther server is fired when this runs

    endprocess.prg:

    loFoxServer.Unadvise()

    loFoxServer = NULL

    loEventhandler = NULL

  6. claudefox says:

    Ok, now I think I see.  Once the class is set up, there’s not really a lot of set up – just feed it the code.  Nice…

  7. Leandro says:

    Dim t as New Thread(addressof Method)

    t.Start()

    Easy and efficient… It is .Net !

  8. claudefox says:

    Well the equivalent here in vfp is:

    ox=CREATEOBJECT("ThreadClass")

    cStrIid="{00020400-0000-0000-C000-000000000046}"      && IID_IDispatch

    ox.StartThreads(nThreads,"do "+SYS(5)+CURDIR()+"MyThreadFunc WITH p2","Thread: %threadnum",cStrIid)

    Not a bad way to give VFP some MT capability!!

  9. Hello Calvin !

    Threading is a cool thing in VFP :-)

    Is it possible to include this native in Sedna?

    This can help me at a project for motorsports time measurement and reporting with the following options:

    – One thread to catch the time from a COM-Port, sended by a chronometer with a light barrier

    – One thread to send the actual time via COM-Port to a big LED display.

    – The main thread is used to append/edit the competitors dates, printing a report and so on.



    With friendly regards

    Hans-Peter Grözinger

  10. SednaY says:

    The main thread is actually being blocked in this example by WaitForMultipleObjects.  I changed this and also changed some commands to be backwards compatible with earlier VFP versions.  This new version can be downloaded at SednaY: http://codegallery.gotdotnet.com/SednaY (project is called MTmyVFP)…

  11. SednaY says:

    Although using MsgWaitForMultipleObjects is an improvement, 2 problems so far:

    1.) Although the main thread is pretty responsive, the command window will not execute a command while the other threads are running even though you can type it in.  Any ideas??  This probably has something to do with how the vfp command window works.  My thought was that maybe Command Windows events are being blocked..

    2.)  On Win2K server, it doesn’t appear to be starting the threads for some reason!?  My guess is that possibly the machine code is picky depending on OS??

  12. In a prior post: Create multiple threads from within your application, there is a sample Thread Class…

  13. Boris says:

    Hi Calvin,

    In your post you mentioned a multithreaded runtime. Where can I get it?

    Thanks,

    Boris

  14. SednaY says:

    Boris, VFP6SP5 and above has included the

    vfp?t.dll which is the MT VFP runtime.

    It looks to me that most of what Calvin is posting is VFP9 specific.  I redid the first 2 multi-threading examples to be backward compatible with VFP7 – that can be found on http://codegallery.gotdotnet.com/SednaY

    I’m hoping to be able to do the same with the WebCrawler…

  15. MSP says:

    Am I the only one experiencing problems with this code. It bombs in the WaitForThreads() method on the line, CloseHandle(this.hThreads[i])

    this.nThreads is 10, but, the array is dimensioned 1!

  16. claudefox says:

    Are you using VFP9?  I had to rewrite some functions to be backwards compat before it worked for me on VFP 7.  This example worked alot more smoothly for me than the next example (threadmanager class).  BTW, I also had better luck with msgwaitformultipleobjects…

  17. MSP says:

    Claude, I am using VFP9SP1 when I’m getting this error.

    I’ve seen a link to your modified version on the wiki and will give that a try.

  18. Mike says:

    Doesn’t seem to work when (as multithreaded) .DLL and .EXE are recompiled in Fox9 SP1. Running the test prog exits quickly and the ThreadLog table is not created.

  19. I received a question: Simply, is there a way of interrupting a vfp sql query once it has started short

  20. Abhay says:

    Gargon’s example of trying to run Multiple threads using a VFP class compiled into a COM EXE is not working in my case.

    What could be wrong?

    Abhay