Page 3 of 3 FirstFirst 123
Results 81 to 96 of 96

Thread: [VB6] - Module for working with multithreading.

  1. #81
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: [VB6] - Module for working with multithreading.

    Hello Trick,

    I tried the following two ways by removing the lib from references so that atl lib is accessible to vb client exe:


    1.Copied Project1.exe to atl project debug folder so that the vb client app can access atlsimpleobjdeno project dll and run Project1.exe.

    2.Copied atlsimpleobjdeno dll to vb client project folder so that the vb client app can access atlsimpleobjdeno project dll and run Project1.exe.

    But still I get the same invalid procedure call or argument error in both ways.


    Thanks
    Last edited by smkperu; Sep 6th, 2023 at 08:24 AM.

  2. #82

  3. #83
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    This problem is related to registering your Ax-component not module. Just register it properly.
    Hello trick,

    It does'nt matter whether I use

    Dim x As ATLSIMPLEOBJDENOLib.atlsimpleobjforvb or Dim x As Object in the simplevbatlclient standard exe project already attached

    If I comment

    Set x = CreateActiveXObjectInNewThread2("ATLSIMPLEOBJDENOLib.atlsimpleobjforvb", , asyncid)

    and use

    Set x = New atlsimpleobjforvb

    the simplevbatlclient project works fine .

    This implies that the atl component is registered properly in the already attached atlsimpleobjdeno atl project.

    What I observed was that CLSIDFromProgID returns < 0 making Err.Raise 5 being called in modMultiThreading2.bas in CreateActiveXObjectInNewThread2 function

    Code:
    ' // Create new ActiveX object in the new thread by ProgID
    Public Function CreateActiveXObjectInNewThread2( _
                    ByRef sProgID As String, _
                    Optional ByVal pIID As Long, _
                    Optional ByRef lAsynchId As Long) As IUnknown
        Dim tClsId  As tCurGUID
        
        If CLSIDFromProgID(StrPtr(sProgID), tClsId) < 0 Then
            Err.Raise 5
        End If
        
        Set CreateActiveXObjectInNewThread2 = CreateActiveXObjectInNewThread(VarPtr(tClsId), pIID, lAsynchId)
        
    End Function
    I tried to compile simplevbatlclient in admin mode and also run the Project1.exe in admin mode but I get same error.

    thanks

  4. #84

  5. #85
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    This error related to the registration because OLE can't find your Ax-DLL ProgID entry in the registry. I've already wrote the solution please read carefully.
    Hello Trick,


    Thank you.I corrected the ProgID.

    I have used atl object atlsimpleobjforvb which was not supporting events.

    Can I pass Atl object which supports events and using WithEvents can I call in vb client in CreateActiveXObjectInNewThread2 and receive events like callback examples.

    I am asking this since all your examples have no mention of receiving events.

    Thanks

  6. #86

  7. #87
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    Events should work. This is the same principle like there.
    Hello Trick,

    In order to test receiving events of private class Class1 in new thread I did a standard exe application with form1 having single button command1 and Class1 as follows:


    form1 code
    Code:
    Dim WithEvents x As Class1
    Private thid As Long
    
    Private Sub Command1_Click()
        'Set x = New Class1 'works fine
         Set x = CreatePrivateObjectByNameInNewThread("Class1", , thid) ' gives run time err 13 type mishmatch
         x.classmem
    End Sub
    
    Private Sub Form_Load()
         modMultiThreading.Initialize
         modMultiThreading.EnablePrivateMarshaling True
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
    modMultiThreading.Uninitialize
    End Sub
    
    Private Sub x_abc()
    MsgBox "in abc evt" ' not called in new thread
    End Sub
    Class1 code

    Code:
    Event abc()
    Sub classmem()
    RaiseEvent abc
    End Sub


    When I run and click the command1 button on displayed form1 I get Run-time error '13'. Type mismatch instead of getting Class1 abc event called and application exits.


    Project attached.


    Thanks
    Attached Files Attached Files

  8. #88

  9. #89

  10. #90
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    39,142

    Re: [VB6] - Module for working with multithreading.

    The discussion that wandered onto other subjects were moved to a new thread, since they were essentially questions, not a discussion of the topic of this CodeBank submission.
    My usual boring signature: Nothing

  11. #91
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    Guys, stop hijacking this thread. If you have any troubles with the module in the original post please report here. If you have other questions related multithreading/multiprocessing create a separate thread.
    Hello Trick,

    I started a new thread at https://www.vbforums.com/showthread....=1#post5619017

    Thanks

  12. #92
    New Member
    Join Date
    Mar 2024
    Posts
    1

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    Hello everyone!

    I present the module for working with multithreading in VB6 for Standard EXE projects. This module is based on this solution with some bugfixing and the new functionality is added. The module doesn't require any additional dependencies and type libraries, works as in the IDE (all the functions work in the main thread) as in the compiled form.


    To start working with the module, you need to call the Initialize function, which initializes the necessary data (it initializes the critical sections for exclusive access to the heaps of marshalinig and threads, modifies VBHeader (here is description), allocates a TLS slot for passing the parameters to the thread).

    The main function of thread creation is vbCreateThread, which is an analog of the CreateThread function.

    Code:
    ' // Create a new thread
    Public Function vbCreateThread(ByVal lpThreadAttributes As Long, _
                                   ByVal dwStackSize As Long, _
                                   ByVal lpStartAddress As Long, _
                                   ByVal lpParameter As Long, _
                                   ByVal dwCreationFlags As Long, _
                                   ByRef lpThreadId As Long, _
                                   Optional ByVal bIDEInSameThread As Boolean = True) As Long
    The function creates a thread and calls the function passed in the lpStartAddress parameter with the lpParameter parameter.
    In the IDE, the call is reduced to a simple call by the pointer implemented through DispCallFunc. In the compiled form, this function works differently. Because a thread requires initialization of project-specific data and initialization of the runtime, the parameters passed to lpStartAddress and lpParameter are temporarily stored into the heap by the PrepareData function, and the thread is created in the ThreadProc function, which immediately deals with the initialization and calling of the user-defined function with the user parameter. This function creates a copy of the VBHeader structure via CreateVBHeaderCopy and changes the public variable placement data in the VbPublicObjectDescriptor.lpPublicBytes, VbPublicObjectDescriptor.lpStaticBytes structures (BTW it wasn't implemented in the previous version) so that global variables are not affected during initialization. Further, VBDllGetClassObject calls the FakeMain function (whose address is written to the modified VBHeader structure). To transfer user parameters, it uses a TLS slot (since Main function doesn't accept parameters, details here). In FakeMain, parameters are directly extracted from TLS and a user procedure is called. The return value of the function is also passed back through TLS. There is one interesting point related to the copy of the header that wasn't included in the previous version. Because the runtime uses the header after the thread ends (with DLL_THREAD_DETACH), we can't release the header in the ThreadProc procedure, therefore there will be a memory leak. To prevent the memory leaks, the heap of fixed size is used, the headers aren't cleared until there is a free memory in this heap. As soon as the memory ends (and it's allocated in the CreateVBHeaderCopy function), resources are cleared. The first DWORD of header actually stores the ID of the thread which it was created in and the FreeUnusedHeaders function checks all the headers in the heap. If a thread is completed, the memory is freed (although the ID can be repeated, but this doesn't play a special role, since in any case there will be a free memory in the heap and if the header isn't freed in one case, it will be released later). Due to the fact that the cleanup process can be run immediately from several threads, access to the cleanup is shared by the critical section tLockHeap.tWinApiSection and if some thread is already cleaning up the memory the function will return True which means that the calling thread should little bit waits and the memory will be available.

    The another feature of the module is the ability to initialize the runtime and the project and call the callback function. This can be useful for callback functions that can be called in the context of an arbitrary thread (for example, InternetStatusCallback). To do this, use the InitCurrentThreadAndCallFunction and InitCurrentThreadAndCallFunctionIDEProc functions. The first one is used in the compiled application and takes the address of the callback function that will be called after the runtime initialization, as well as the parameter to be passed to this function. The address of the first parameter is passed to the callback procedure to refer to it in the user procedure:

    Code:
    ' // This function is used in compiled form
    Public Function CallbackProc( _
                    ByVal lThreadId As Long, _
                    ByVal sKey As String, _
                    ByVal fTimeFromLastTick As Single) As Long
        ' // Init runtime and call CallBackProc_user with VarPtr(lThreadId) parameter
        InitCurrentThreadAndCallFunction AddressOf CallBackProc_user, VarPtr(lThreadId), CallbackProc
    End Function
    
    ' // Callback function is called by runtime/window proc (in IDE)
    Public Function CallBackProc_user( _
                    ByRef tParam As tCallbackParams) As Long
    
    End Function
    CallBackProc_user will be called with the initialized runtime.

    This function doesn't work in the IDE because in the IDE everything works in the main thread. For debugging in the IDE the function InitCurrentThreadAndCallFunctionIDEProc is used which returns the address of the assembler thunk that translates the call to the main thread and calls the user function in the context of the main thread. This function takes the address of the user's callback function and the size of the parameters in bytes. It always passes the address of the first parameter as a parameter of a user-defined function. I'll tell you a little more about the work of this approach in the IDE. To translate a call from the calling thread to the main thread it uses a message-only window. This window is created by calling the InitializeMessageWindow function. The first call creates a WindowProc procedure with the following code:

    Code:
        CMP DWORD [ESP+8], WM_ONCALLBACK
        JE SHORT L
        JMP DefWindowProcW
    L:  PUSH DWORD PTR SS:[ESP+10]
        CALL DWORD PTR SS:[ESP+10]
        RETN 10
    As you can see from the code, this procedure "listens" to the WM_ONCALLBACK message which contains the parameter wParam - the function address, and in the lParam parameters. Upon receiving this message it calls this procedure with this parameter, the remaining messages are ignored. This message is sent just by the assembler thunk from the caller thread. Futher, a window is created and the handle of this window and the code heap are stored into the data of the window class. This is used to avoid a memory leak in the IDE because if the window class is registered once, then these parameters can be obtained in any debugging session. The callback function is generated in InitCurrentThreadAndCallFunctionIDEProc, but first it's checked whether the same callback procedure has already been created (in order to don't create the same thunk). The thunk has the following code:

    Code:
    LEA EAX, [ESP+4]
    PUSH EAX
    PUSH pfnCallback
    PUSH WM_ONCALLBACK
    PUSH hMsgWindow
    Call SendMessageW
    RETN lParametersSize
    As you can see from the code, during calling a callback function, the call is transmitted via SendMessage to the main thread. The lParametersSize parameter is used to correctly restore the stack.

    The next feature of the module is the creation of objects in a separate thread, and you can create them as private objects (the method is based on the code of the NameBasedObjectFactory by firehacker module) as public ones. To create the project classes use the CreatePrivateObjectByNameInNewThread function and for ActiveX-public classes CreateActiveXObjectInNewThread and CreateActiveXObjectInNewThread2 ones. Before creating instances of the project classes you must first enable marshaling of these objects by calling the EnablePrivateMarshaling function. These functions accept the class identifier (ProgID / CLSID for ActiveX and the name for the project classes) and the interface identifier (IDispatch / Object is used by default). If the function is successfully called a marshaled object and an asynchronous call ID are returned. For the compiled version this is the ID of thread for IDE it's a pointer to the object. Objects are created and "live" in the ActiveXThreadProc function. The life of objects is controlled through the reference count (when it is equal to 1 it means only ActiveXThreadProc refers to the object and you can delete it and terminate the thread).
    You can call the methods either synchronously - just call the method as usual or asynchronously - using the AsynchDispMethodCall procedure. This procedure takes an asynchronous call ID, a method name, a call type, an object that receives the call notification, a notification method name and the list of parameters. The procedure copies the parameters to the temporary memory, marshals the notification object, and sends the data to the object's thread via WM_ASYNCH_CALL. It should be noted that marshaling of parameters isn't supported right now therefore it's necessary to transfer links to objects with care. If you want to marshal an object reference you should use a synchronous method to marshal the objects and then call the asynchronous method. The procedure is returned immediately. In the ActiveXThreadProc thread the data is retrieved and a synchronous call is made via MakeAsynchCall. Everything is simple, CallByName is called for the thread object and CallByName for notification. The notification method has the following prototype:

    Code:
    Public Sub CallBack (ByVal vRet As Variant)
    , where vRet accepts the return value of the method.

    The following functions are intended for marshaling: Marshal, Marshal2, UnMarshal, FreeMarshalData. The first one creates information about the marshaling (Proxy) of the interface and puts it into the stream (IStream) that is returned. It accepts the interface identifier in the pInterface parameter (IDispatch / Object by default). The UnMarshal function, on the contrary, receives a stream and creates a Proxy object based on the information in the stream. Optionally, you can release the thread object. Marshal2 does the same thing as Marshal except that it allows you to create a Proxy object many times in different threads. FreeMarshalData releases the data and the stream accordingly.
    If, for example, you want to transfer a reference to an object between two threads, it is enough to call the Marshal / UnMarshal pair in the thread which created the object and in the thread that receives the link respectively. In another case, if for example there is the one global object and you need to pass a reference to it to the multiple threads (for example, the logging object), then Marshal2 is called in the object thread, and UnMarshal with the bReleaseStream parameter is set to False is called in client threads. When the data is no longer needed, FreeMarshalData is called.

    The WaitForObjectThreadCompletion function is designed to wait for the completion of the object thread and receives the ID of the asynchronous call. It is desirable to call this function always at the end of the main process because an object thread can somehow interact with the main thread and its objects (for example, if the object thread has a marshal link to the interface of the main thread).

    The SuspendResume function is designed to suspend/resume the object's thread; bSuspend determines whether to sleep or resume the thread.

    In addition, there are also several examples in the attacment of working with module:
    1. Callback - the project demonstrates the work with the callback-function periodically called in the different threads. Also, there is an additional project of native dll (on VB6) which calls the function periodically in the different threads;
    2. JuliaSet - the Julia fractal generation in the several threads (user-defined);
    3. CopyProgress - Copy the folder in a separate thread with the progress of the copy;
    4. PublicMarshaling - Creating public objects (Dictionary) in the different threads and calling their methods (synchronously / asynchronously);
    5. PrivateMarshaling - Creating private objects in different threads and calling their methods (synchronously / asynchronously);
    6. MarshalUserInterface - Creating private objects in different threads and calling their methods (synchronously / asynchronously) based on user interfaces (contains tlb and Reg-Free manifest).
    7. InitProjectContextDll - Initialization of the runtime in an ActiveX DLL and call the exported function from the different threads. Setup callbacks to the executable.
    8. InternetStatusCallback - IternetStatusCallback usage in VB6. Async file downloading.


    The module is poorly tested so bugs are possible. I would be very glad to any bug-reports, wherever possible I will correct them.
    Thank you all for attention!

    Project on GitHub.
    Best Regards,
    The trick.
    What is the need for making number of public and local variables to zero while creating a new vbheader in CreateVBHeaderCopy function of modMultiThreading2.bas module.

    How is the following code effecting variables scope in main and new threads.

    Code:
    For lIndex = 0 To lModulesCount - 1
    
            ' // Zero number of public and local variables
            GetMem4 pVarBlock, ByVal pDescriptors + lIndex * &H30 + &H8
            GetMem4 0&, ByVal pDescriptors + lIndex * &H30 + &HC
    
        Next
    Can you demonstrate this using a simple demo example about its importance as it is mentioned that a bug is fixed in previous tlb based modMultiThreading.bashttps://www.vbforums.com/showthread....n-Standart-EXE in this new tlb independent modMultiThreading2.bas module.

  13. #93
    Member
    Join Date
    Oct 2018
    Posts
    61

    Re: [VB6] - Module for working with multithreading.

    Hello The trick,

    Thank you very much for your project. It's helped a lot here

    Now I need an anointing to stop the threads immediately without waiting for them to finish.

    I tried the code below and things seem to be working fine. Please, do you have any other suggestions?

    Code:
    Private Declare Function TerminateThread Lib "kernel32" (ByVal hThread As Long, ByVal dwExitCode As Long) As Long
    
    Public Function StopThreadImmediately(ByVal lAsynchId As Long) As Long
        Dim hThread As Long
        Const THREAD_TERMINATE As Long = 1
        ' // Unsupported in IDE
        If bIsInIDE Then Exit Function
            If lAsynchId > 0 Then
                hThread = OpenThread(THREAD_TERMINATE, 0, lAsynchId)
                If Not (hThread = 0) Then
                    StopThreadImmediately = TerminateThread(hThread, 0)
                    CloseHandle hThread
                End If
            End If
    End Function

  14. #94

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,728

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by hennyere View Post
    Hello The trick,

    Thank you very much for your project. It's helped a lot here

    Now I need an anointing to stop the threads immediately without waiting for them to finish.

    I tried the code below and things seem to be working fine. Please, do you have any other suggestions?

    Code:
    Private Declare Function TerminateThread Lib "kernel32" (ByVal hThread As Long, ByVal dwExitCode As Long) As Long
    
    Public Function StopThreadImmediately(ByVal lAsynchId As Long) As Long
        Dim hThread As Long
        Const THREAD_TERMINATE As Long = 1
        ' // Unsupported in IDE
        If bIsInIDE Then Exit Function
            If lAsynchId > 0 Then
                hThread = OpenThread(THREAD_TERMINATE, 0, lAsynchId)
                If Not (hThread = 0) Then
                    StopThreadImmediately = TerminateThread(hThread, 0)
                    CloseHandle hThread
                End If
            End If
    End Function
    It's the bad practice to use TerminateThread because there is no resource releasing in this case. It's better to notify a thread (for example PostMessage/PostThreadMessage in certain cases) you want to finish it. If your thread is in waiting state you can use alertable state and notify by posting an APC request.

  15. #95
    Member
    Join Date
    Oct 2018
    Posts
    61

    Re: [VB6] - Module for working with multithreading.

    Quote Originally Posted by The trick View Post
    It's the bad practice to use TerminateThread because there is no resource releasing in this case. It's better to notify a thread (for example PostMessage/PostThreadMessage in certain cases) you want to finish it. If your thread is in waiting state you can use alertable state and notify by posting an APC request.
    Hi The Trick,

    I'm glad to see you here while I'm having a coffee.

    Thanks for letting me know about using TerminateThread.

    I'm trying to adapt your module into a project, and I would like to ask a question:

    In a very simple approach, we have some sql statements that need to be executed asynchronously. Please keep in mind that this is a summary code, therefore the RunSqlAssync function is called in several places in the project.

    Code:
    Form1.frm
    
    Private Sub Form_Load()
        modMultiThreading.Initialize
        modMultiThreading.EnablePrivateMarshaling True
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
        modMultiThreading.Uninitialize
    End Sub
    
    Private Sub Command1_Click()
        RunSqlAssync "UPDATE mytable SET myfield1=100 WHERE myfield1=1000;"
        RunSqlAssync "UPDATE mytable SET myfield1=200 WHERE myfield1=2000;"
    End Sub
    Code:
    Module1.bas
    
    Public Sub RunSqlAssync(ByVal xSql As String) 
        Dim zTObj As Object, zTAsynchID As Long
        Set zTObj = modMultiThreading.CreatePrivateObjectByNameInNewThread("VBThreadClass", , zTAsynchID)
        modMultiThreading.AsynchDispMethodCall zTAsynchID, "RunSqlEx", VbMethod, Nothing, "", xSql
    End Function
    Code:
    VBThreadClass.cls
    
    Public Sub RunSqlEx(ByVal xSql As String)
        Dim xCmd As Object
        Set xCmd = CreateObject("ADODB.Command")
        xCmd.ActiveConnection = Db
        xCmd.CommandType = adCmdText
        xCmd.CommandText = xSql
        xCmd.Execute , , adExecuteNoRecords
        Set xCmd = Nothing
    End Sub

    As you noticed, every time I call RunSqlAssync, this Sub asynchronously executes RunSqlEx. This seems to work fine, but I have 2 questions:

    1. What happens to the Thread when RunSqlEx reaches the end? Is it simply finished?
    2. How many Threads can be executed in parallel?

    I've been working with C# for years and don't have much experience with Vb6.
    Last edited by hennyere; Apr 24th, 2024 at 07:12 AM.

  16. #96

Page 3 of 3 FirstFirst 123

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width