# Visual Basic > Visual Basic 6 and Earlier >  using VbTrickThreading-master  examples without the typelibs for Callback and Marshal

## jsvenu

Dear Trick,

In the *Callback* demo of *VbTrickThreading-master* I have *directly* called  the APIs and other data present  in import.tlb which are being accessed in callback.dll by writing their declares and types and *avoided* it by unchecking import.tlb in project references.Now when I run the callbacktest.exe in compiled mode I get nothing displayed in log window and the application hangs and I had to close it using task manager.I am attaching the modified callback sample.

1.Please clarify what are the modifications in addition I have to make it to work without import tlb just as  it was running before when we were using import.tlb.


2.Similarly can you provide me the code to directly make *MarshalUserInterface* example to work without using user_typelib.tlb type library.

regards,
JSVenu

----------


## wqweto

> 1.Please clarify what are the modifications in addition I have to make it to work without import tlb just as it was running before when we were using import.tlb.


For each API declare VB6 compiler generates a small function that loads the dll, finds the entry point by name/ordinal, converts params ANSI<->Unicode and then calls the function. Finally after the call is executed it checks the stack for mismatches (wrong number of parameters) and sets Err.LastDllError from GetlastError API. In threading scenarios this last step fails if the VB6 runtime (which includes the Err object among other thigs) is not initialized on the running thread.

Using typelib APIs does *not* set Err.LastDllError after calling the entry point, *unless* specifically instructed w/ the usesgetlasterror attribute in the IDL declaration.

cheers,
</wqw>

----------


## The trick

You can't use an arbitrary code if you don't initialize the DLL-project-context, particularly the API calls declared in VB6.
The proper way is to call *DLLGetClassObject* and then to call the exported functions when you use the ActiveX Dll project type.
I had the thoughts to make the module which add the ability to initialize a context in DLL created as Standard EXE but there are some pitfalls like when you receive *DLL_PROCESS_DETACH* how you uninitialize the projects-context created in the other threads.

The attached archive contains the module and several examples of usage in 3 different languages (VB6, C, PureBasic):
*Simple* - just show message box in DLL;*ShowForm* - show the Form from DLL;*CallbackThread* - create a thread in DLL and then call the callback function in EXE.

Because of the code module is quite raw one i don't publish it in the CodeBank:



```
' //
' // modDllInitialize.bas - The module provides support for runtime-initization for dynamic link libraries
' // Version 2
' // © Krivous Anatoly Anatolevich (The trick), 2015-2020
' // If you want to use additional callback from DllMain use DLL_USE_DLLMAIN conditional compilation adrgument
' // with the DllEntry callback function
' //

Option Explicit

Private Const PAGE_EXECUTE_READWRITE    As Long = &H40&
Private Const FADF_AUTO                 As Long = 1
Private Const VB_MAGIC                  As Long = &H21354256
Private Const HEAP_ZERO_MEMORY          As Long = &H8
Private Const HEAP_NO_SERIALIZE         As Long = &H1
Private Const TLS_OUT_OF_INDEXES        As Long = &HFFFF&

' // Lazy GUID structure
Private Type tCurGUID
    c1          As Currency
    c2          As Currency
End Type

Private Type SAFEARRAYBOUND
    cElements   As Long
    lLbound     As Long
End Type

Private Type SAFEARRAY
    cDims       As Integer
    fFeatures   As Integer
    cbElements  As Long
    cLocks      As Long
    pvData      As Long
    Bounds      As SAFEARRAYBOUND
End Type

Private Type tVBHeaderString
    pNames(3)   As Long
End Type

Private Const DLL_PROCESS_ATTACH    As Long = 1
Private Const DLL_PROCESS_DETACH    As Long = 0
Private Const DLL_THREAD_ATTACH     As Long = 2
Private Const DLL_THREAD_DETACH     As Long = 3

Private mlTlsSlot   As Long     ' // Index of the item in the TLS. There will be data specific to the thread.
Private mpVbHeader  As Long     ' // Pointer to VBHeader structure.
Private mhInstance  As Long     ' // Base address of the module

' // Unused
Private Sub Main()

End Sub

' // This function is called when the module is being loaded/unloaded to a process or a thread is created/destroyed
Public Function DllMain( _
                ByVal hinstDLL As Long, _
                ByVal fdwReason As Long, _
                ByVal lpvReserved As Long) As Long

    Select Case fdwReason
    Case DLL_PROCESS_ATTACH
        
        If VBDll.UserDllMain(mhInstance, 0, hinstDLL, fdwReason, lpvReserved) = 0 Then
            GoTo CleanUp
        End If

        mlTlsSlot = VBDll.TlsAlloc()
        If mlTlsSlot = TLS_OUT_OF_INDEXES Then GoTo CleanUp
        
        DllMain = InitializeRuntimeForProject(hinstDLL, True) And 1
        
#If DLL_USE_DLLMAIN Then
        If DllMain Then
            DllMain = DllEntry(hinstDLL, fdwReason, lpvReserved)
        End If
#End If

    Case DLL_THREAD_ATTACH

        If VBDll.UserDllMain(mhInstance, 0, hinstDLL, fdwReason, lpvReserved) = 0 Then
            GoTo CleanUp
        End If

        DllMain = InitializeRuntimeForProject(hinstDLL, False) And 1

#If DLL_USE_DLLMAIN Then
        If DllMain Then
            DllMain = DllEntry(hinstDLL, fdwReason, lpvReserved)
        End If
#End If

    Case DLL_THREAD_DETACH

#If DLL_USE_DLLMAIN Then
        DllEntry hinstDLL, fdwReason, lpvReserved
#End If

        If VBDll.UserDllMain(mhInstance, 0, hinstDLL, fdwReason, lpvReserved) = 0 Then
            GoTo CleanUp
        End If

        UninitializeRuntimeForProject hinstDLL
        
        FreeHeaderForCurrentThread
        
        DllMain = 1

    Case DLL_PROCESS_DETACH

#If DLL_USE_DLLMAIN Then
        DllEntry hinstDLL, fdwReason, lpvReserved
#End If
        
        CanUnloadNowCall

        VBDll.UserDllMain mhInstance, 0, hinstDLL, fdwReason, lpvReserved
        
        FreeHeaderForCurrentThread
        
        VBDll.TlsFree mlTlsSlot

        mlTlsSlot = 0
        mpVbHeader = 0
        
        DllMain = 1
        
        Exit Function
        
    End Select
      
CleanUp:

End Function

' // Uninitialize the runime
Public Function UninitializeRuntimeForProject( _
                ByVal hInstance As Long) As Boolean
    Dim pNewHeader  As Long
    
    ' // Check if the module is initialized
    pNewHeader = VBDll.TlsGetValue(mlTlsSlot)
    If pNewHeader = 0 Then Exit Function

    VBDll.CoUninitialize
    
    UninitializeRuntimeForProject = True
    
End Function

' // Free the current header
Public Function FreeHeaderForCurrentThread() As Boolean
    Dim pNewHeader  As Long
    
    ' // Check if the module is initialized
    pNewHeader = VBDll.TlsGetValue(mlTlsSlot)
    If pNewHeader = 0 Then Exit Function
    
    VBDll.HeapFree VBDll.GetProcessHeap(), 0, pNewHeader
    
End Function

' // Initilaize the runtime
Public Function InitializeRuntimeForProject( _
                ByVal hInstance As Long, _
                ByVal bIsFirst As Boolean) As Boolean
    Dim pNewHeader  As Long
    Dim tClsId      As tCurGUID
    Dim tIID        As tCurGUID
    Dim lUnused     As Long
    
    pNewHeader = VBDll.TlsGetValue(mlTlsSlot)
    
    ' // Check if the module already initialized
    If pNewHeader Then

        InitializeRuntimeForProject = True
        Exit Function
        
    End If
    
    VBDll.CoInitialize ByVal 0&
    
    If mpVbHeader = 0 Then
        
        ' // Search for VBHeader (EXEPROJECTINFO)
        mpVbHeader = SearchForVbHeader(hInstance)
        If mpVbHeader = 0 Then Exit Function
        
        ' // Modify header
        ModifyVBHeader
        
    End If
    
    ' // Create the new copy of headers for new instance
    pNewHeader = CreateVBHeaderCopy()
    
    ' // Save it
    VBDll.TlsSetValue mlTlsSlot, ByVal pNewHeader

    If pNewHeader = 0 Then
        Exit Function
    End If
    
    ' // IID_IUnknown
    tIID.c2 = 504403158265495.5712@
    
    ' // Call CThreadPool::InitDllAccess
    VBDll.VBDllGetClassObject hInstance, 0, pNewHeader, tClsId, tIID, 0
    
    If bIsFirst Then
        ' // Initialize App object
        lUnused = App.ThreadID
    End If
    
    InitializeRuntimeForProject = True
    
End Function

Private Sub CanUnloadNowCall()
    Dim pNewHeader  As Long
    Dim bThreading  As Byte
    
    ' // Check if the module is initialized
    pNewHeader = VBDll.TlsGetValue(mlTlsSlot)
    If pNewHeader = 0 Then Exit Sub

    ' // To ensure cleaning of the project data set the threading model to the apartment one.
    VBDll.GetMem1 ByVal pNewHeader + &H3C, bThreading
    bThreading = bThreading Or 1
    VBDll.GetMem1 bThreading, ByVal pNewHeader + &H3C
    
    ' // The runtime will call CThreadPool::CheckForProjectUnload and CVBThreadAction::CleanupProjData
    If VBDll.VBDllCanUnloadNow(pNewHeader) Then
        Exit Sub
    End If

End Sub

' // Search for VBHeader(EXEPROJECTINFO)
Private Function SearchForVbHeader( _
                 ByVal hInstance As Long) As Long
    Dim ptr             As Long
    Dim lSignature      As Long
    Dim pImportDesc     As Long
    Dim pStartSearch    As Long
    Dim pEndSearch      As Long
    Dim bData()         As Byte
    Dim tArrDesc        As SAFEARRAY
    Dim lIndex          As Long
    
    ' // VBHeader is placed within the end of IAT and beginning of IMAGE_IMPORT_DESCRIPTOR
    
    ' // Get e_lfanew
    VBDll.GetMem4 ByVal hInstance + &H3C, ptr
    ' // Get IAT
    VBDll.GetMem4 ByVal ptr + &H80 + hInstance, pImportDesc
    
    pEndSearch = hInstance + pImportDesc - 4
    
    Do
    
        ' // Get IMAGE_IMPORT_DESCRIPTOR.FirstThunk
        VBDll.GetMem4 ByVal pImportDesc + &H10 + hInstance, ptr
        
        If ptr > pStartSearch Then
            pStartSearch = ptr
        End If
        
        pImportDesc = pImportDesc + &H14
        
    Loop While ptr
    
    ' // Search for null-thunk (skip valid IAT entries)
    Do
        
        VBDll.GetMem4 ByVal hInstance + pStartSearch, ptr
        pStartSearch = pStartSearch + 4
        
    Loop While ptr
    
    pStartSearch = pStartSearch + hInstance
    
    If pEndSearch < pStartSearch Then Exit Function
    
    ' // Map the array to the data
    tArrDesc.cbElements = 1
    tArrDesc.cDims = 1
    tArrDesc.fFeatures = FADF_AUTO
    tArrDesc.pvData = pStartSearch
    tArrDesc.Bounds.cElements = pEndSearch - pStartSearch + 5
    
    VBDll.MoveArray bData(), VarPtr(tArrDesc)
    
    For lIndex = 0 To tArrDesc.Bounds.cElements - 5
        
        ' // __vbaS of an exe module has the following structure:
        ' // PUSH OFFSET VbHeader
        ' // CALL ThunRTMain
        
        ' // Search for PUSH IMM opcode
        If bData(lIndex) = &H68 Then
            
            ' // Get Immediate value
            VBDll.GetMem4 bData(lIndex + 1), ptr
            
            ' // Check range
            If ptr >= pStartSearch And ptr < pEndSearch Then
                
                ' // Check signature (VB5!)
                VBDll.GetMem4 ByVal ptr, lSignature
                
                If lSignature = VB_MAGIC Then
                    
                    SearchForVbHeader = ptr
                    Exit Function
                    
                End If
                
            End If
            
        End If
        
    Next
    
End Function

' // Modify VBHeader to replace Sub Main
Private Sub ModifyVBHeader()
    Dim ptr             As Long
    Dim lOldProtect     As Long
    Dim lFlags          As Long
    Dim lFormsCount     As Long
    Dim lModulesCount   As Long
    Dim lStructSize     As Long
    
    ' // Allow to write to that page
    VBDll.VirtualProtect ByVal mpVbHeader, &H64, PAGE_EXECUTE_READWRITE, lOldProtect
    
    ' // Remove Sub Main
    ptr = mpVbHeader + &H2C
    VBDll.GetMem4 0&, ByVal ptr

    VBDll.VirtualProtect ByVal mpVbHeader, &H64, lOldProtect, 0
    
    ' // Remove startup form
    VBDll.GetMem4 ByVal mpVbHeader + &H4C, ptr
    ' // Get number of forms
    VBDll.GetMem2 ByVal mpVbHeader + &H44, lFormsCount
    
    Do While lFormsCount > 0
    
        ' // Get structure size
        VBDll.GetMem4 ByVal ptr, lStructSize
        
        ' // Get flag (unknown5) from current form
        VBDll.GetMem4 ByVal ptr + &H28, lFlags
        
        ' // When set, bit 5,
        If lFlags And &H10 Then
        
            ' // Unset bit 5
            lFlags = lFlags And &HFFFFFFEF
            ' // Are allowed to write in the page
            VBDll.VirtualProtect ByVal ptr, 4, PAGE_EXECUTE_READWRITE, lOldProtect
            ' // Write changet lFlags
            VBDll.GetMem4 lFlags, ByVal ptr + &H28
            ' // Restoring the memory attributes
            VBDll.VirtualProtect ByVal ptr, 4, lOldProtect, 0
            
        End If
        
        lFormsCount = lFormsCount - 1
        ptr = ptr + lStructSize
        
    Loop

End Sub

' // Create copy of VBHeader and other structures
Private Function CreateVBHeaderCopy() As Long
    Dim pHeader         As Long
    Dim pOldProjInfo    As Long
    Dim pProjInfo       As Long
    Dim pObjTable       As Long
    Dim pOldObjTable    As Long
    Dim lDifference     As Long
    Dim lIndex          As Long
    Dim lSubIndex       As Long
    Dim tNames          As tVBHeaderString
    Dim lModulesCount   As Long
    Dim pDescriptors    As Long
    Dim pOldDesc        As Long
    Dim pVarBlock       As Long
    Dim lSizeOfHeaders  As Long
    Dim lExtCount       As Long
    Dim lNewExtCount    As Long
    Dim pOldExtApi      As Long
    Dim pExtApi         As Long
    Dim lExtFlags       As Long

    ' // Get size of all headers
    lSizeOfHeaders = &H6A + &H23C + &H54 + &HC
    
    VBDll.GetMem4 ByVal mpVbHeader + &H30, pOldProjInfo
    VBDll.GetMem4 ByVal pOldProjInfo + &H4, pOldObjTable
    VBDll.GetMem4 ByVal pOldObjTable + &H30, pOldDesc
    VBDll.GetMem2 ByVal pOldObjTable + &H2A, lModulesCount
    
    lSizeOfHeaders = lSizeOfHeaders + &H30 * lModulesCount
    
    ' // Free API external block
    VBDll.GetMem4 ByVal pOldProjInfo + &H238, lExtCount
    VBDll.GetMem4 ByVal pOldProjInfo + &H234, pOldExtApi
    
    For lIndex = 0 To lExtCount - 1
        
        VBDll.GetMem4 ByVal pOldExtApi + lIndex * 8, lExtFlags
        
        If lExtFlags <> 7 Then
            lNewExtCount = lNewExtCount + 1
        End If
        
    Next
    
    lSizeOfHeaders = lSizeOfHeaders + lNewExtCount * 8
                
    ' // Allocate memory for header
    pHeader = VBDll.HeapAlloc(VBDll.GetProcessHeap(), HEAP_ZERO_MEMORY, lSizeOfHeaders)
    If pHeader = 0 Then GoTo CleanUp

    lDifference = pHeader - mpVbHeader
    
    VBDll.CopyMemory ByVal pHeader, ByVal mpVbHeader, &H6A
    
    ' // Update strings offsets
    VBDll.CopyMemory tNames.pNames(0), ByVal mpVbHeader + &H58, &H10
    
    For lIndex = 0 To 3
        tNames.pNames(lIndex) = tNames.pNames(lIndex) - lDifference
    Next
        
    VBDll.CopyMemory ByVal pHeader + &H58, tNames.pNames(0), &H10

    ' // In order to keep global variables
    ' // Change the VbPublicObjectDescriptor.lpPublicBytes, VbPublicObjectDescriptor.lpStaticBytes
    pProjInfo = pHeader + &H6A

    VBDll.CopyMemory ByVal pProjInfo, ByVal pOldProjInfo, &H23C

    ' // Update on VBHeader
    VBDll.GetMem4 pProjInfo, ByVal pHeader + &H30

    ' // Create copy of Object table
    pObjTable = pProjInfo + &H23C

    VBDll.CopyMemory ByVal pObjTable, ByVal pOldObjTable, &H54

    ' // Update
    VBDll.GetMem4 pObjTable, ByVal pProjInfo + &H4

    ' // Allocate descriptors
    pDescriptors = pObjTable + &H54

    VBDll.CopyMemory ByVal pDescriptors, ByVal pOldDesc, lModulesCount * &H30

    ' // Update
    VBDll.GetMem4 pDescriptors, ByVal pObjTable + &H30

    ' // Allocate variables block
    pVarBlock = pDescriptors + lModulesCount * &H30

    For lIndex = 0 To lModulesCount - 1

        ' // Zero number of public and local variables
        VBDll.GetMem4 pVarBlock, ByVal pDescriptors + lIndex * &H30 + &H8
        VBDll.GetMem4 0&, ByVal pDescriptors + lIndex * &H30 + &HC

    Next
    
    ' // Free API
    pExtApi = pVarBlock + &HC
    lSubIndex = 0
    
    For lIndex = 0 To lExtCount - 1
        
        VBDll.GetMem4 ByVal pOldExtApi + lIndex * 8, lExtFlags
        
        If lExtFlags <> 7 Then
            
            VBDll.GetMem8 ByVal pOldExtApi + lIndex * 8, ByVal pExtApi + lSubIndex * 8
            lSubIndex = lSubIndex + 1
            
        End If
        
    Next
    
    ' // Update
    VBDll.GetMem4 pExtApi, ByVal pProjInfo + &H234
    VBDll.GetMem4 lNewExtCount, ByVal pProjInfo + &H238
    
    CreateVBHeaderCopy = pHeader
    
CleanUp:

End Function
```

Usage in C:



```
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <initguid.h>

#include "interfaces.h"

volatile DWORD g_TlsSlot;

LONG __stdcall CallBack(IUnknown *pObj) {
	HRESULT hr;

	_Form *pForm;
	float fWidth, fHeight;

	if (FAILED(hr = pObj->lpVtbl->QueryInterface(pObj, &IID__Form, (void**)&pForm))) {
		return E_UNEXPECTED;
	}

	// Check if already initialized
	if (!TlsGetValue(g_TlsSlot)) {
		TlsSetValue(g_TlsSlot, (LPVOID)1);
		srand(time(NULL));
	}

	if (SUCCEEDED(hr = pForm->lpVtbl->get_ScaleWidth(pForm, &fWidth)) &&
		SUCCEEDED(hr = pForm->lpVtbl->get_ScaleHeight(pForm, &fHeight))) {
		hr = pForm->lpVtbl->Circle(pForm, 0, rand() % (int)fWidth, rand() % (int)fHeight, rand() % 500, 0, 0, 0, 0);
	}

	pForm->lpVtbl->Release(pForm);

	return hr;

}

int main(int argc, char **argv) {
	HINSTANCE hLib = LoadLibrary("CallbackThread.dll");
	DWORD g_TlsSlot = TlsAlloc();

	VOID (__stdcall *SetCallback)(LONG (__stdcall *)(IUnknown *)) = 
		(VOID (__stdcall *)(LONG (__stdcall *)(IUnknown *)))GetProcAddress(hLib, "SetCallback");

	if (!SetCallback)
		return 1;

	SetCallback(CallBack);

	printf("press a button to exit\r\n");
	getchar();

	FreeLibrary(hLib);
	TlsFree(g_TlsSlot);

	return 0;

}
```

----------


## jsvenu

> You can't use an arbitrary code if you don't initialize the DLL-project-context, particularly the API calls declared in VB6.
> The proper way is to call *DLLGetClassObject* and then to call the exported functions when you use the ActiveX Dll project type.
> I had the thoughts to make the module which add the ability to initialize a context in DLL created as Standard EXE but there are some pitfalls like when you receive *DLL_PROCESS_DETACH* how you uninitialize the projects-context created in the other threads.
> 
> The attached archive contains the module and several examples of usage in 3 different languages (VB6, C, PureBasic):
> *Simple* - just show message box in DLL;*ShowForm* - show the Form from DLL;*CallbackThread* - create a thread in DLL and then call the callback function in EXE.
> 
> Because of the code module is quite raw one i don't publish it in the CodeBank:
> 
> ...



Dear Trick,

Thankyou very much for the reply.

1. I understand that if we *convert* the attachment (Callback.zip) I sent you can be first converted to activex dll which requires a multiuse class to be added to it by default for it to function.I *already tried* it like this but it was also giving the same problem like dll using standard exe already sent without type library.ie., 
Callback demo is not working  if we either use *standard dll* or *activex dll* *without* type library.
Here we are using *implicit linking using declare* instead of explicit linking you showed in Graphics native dll example using LoadLibrary.

2. The Release.zip  example sent by you also uses VBDll.tlb.

Is there any way to *avoid type library* and *solve* the above *two* cases and *MarshalUserInterface* demo also.
Can  *__vbaS* be of any help here.

regards,
JSVenu

----------


## jsvenu

> For each API declare VB6 compiler generates a small function that loads the dll, finds the entry point by name/ordinal, converts params ANSI<->Unicode and then calls the function. Finally after the call is executed it checks the stack for mismatches (wrong number of parameters) and sets Err.LastDllError from GetlastError API. In threading scenarios this last step fails if the VB6 runtime (which includes the Err object among other thigs) is not initialized on the running thread.
> 
> Using typelib APIs does *not* set Err.LastDllError after calling the entry point, *unless* specifically instructed w/ the usesgetlasterror attribute in the IDL declaration.
> 
> cheers,
> </wqw>


Dear wqw,
Thankyou for the reply.
regards,
JSVenu

----------


## The trick

I don't know what should i answer. I gave you example and *wqweto* explained why you can't avoid type libraries.

----------


## jsvenu

> I don't know what should i answer. I gave you example and *wqweto* explained why you can't avoid type libraries.


Dear Trick,

In *Creation of native dll* in code bank you have shown how to use loadlibrary and use import.tlb.I tried to replace the calls in import.tlb directly and have run GraphicsTestdll project *without* import tlb *successfully*.
My question is when it is *possible* with loadlibrary ie., *using explicit linking* *why it is not possible using declare or implicit linking*.

regards,
JSVenu

----------


## yokesee

Trick Hi,

your example modify it to make it work with a class module.
but the only works returned class during that call, automatically closes the thread.
and the object is deleted
only it works with form, or can make a loop that does not close.

in exe:


```
'plugin.cls
Option Explicit
Public cFrm    As Object
Public Function ThreadProc_(ByVal pObj As Long) As Long
    Call InitCurrentThreadAndCallFunction(AddressOf ThreadProc, pObj, ThreadProc_)
End Function

Public Function ThreadProc( _
                ByVal cObj As IUnknown) As Long
    Set cFrm = cObj
    cFrm.ThreadID
End Function
```

dll:


```
Private Const CC_STDCALL    As Long = 4

Private Declare Function DispCallFunc Lib "oleaut32.dll" ( _
                         ByRef pvInstance As Any, _
                         ByVal oVft As Long, _
                         ByVal cc As Long, _
                         ByVal vtReturn As VbVarType, _
                         ByVal cActuals As Long, _
                         ByRef prgvt As Any, _
                         ByRef prgpvarg As Any, _
                         ByRef pvargResult As Variant) As Long

Private mpfn    As Long

Public Property Let Callback(ByVal lValue As Long)
    mpfn = lValue
    MsgBox DispCallFunc(ByVal 0&, mpfn, CC_STDCALL, vbEmpty, 1, vbDataObject, VarPtr(CVar(Me)), CVar(0))
End Property

Public Sub threadid()
    MsgBox App.threadid
End Sub
```



```
'modMain
Option Explicit
Public Declare Function CreateThread Lib "kernel32" ( _
                        ByRef lpThreadAttributes As Any, _
                        ByVal dwStackSize As Long, _
                        ByVal lpStartAddress As Long, _
                        ByRef lpParameter As Any, _
                        ByVal dwCreationFlags As Long, _
                        ByRef lpThreadId As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" ( _
                        ByVal hObject As Long) As Long

' //
' // Create thread and create frmThread in that thread and set callback
' // function to pfn in that thread
' //
Sub SetCallback( _
    ByVal pfn As Long)
        
    CloseHandle CreateThread(ByVal 0&, 0, AddressOf ThreadProc, ByVal pfn, 0, 0)
        
End Sub

Private Function ThreadProc( _
                ByVal pfn As Long) As Long
    Dim cFrm    As plugin
    Set cFrm = New plugin
    cFrm.Callback = pfn
End Function
```


sorry I'm a little new to the multithreading issue
Greetings

----------


## The trick

*yokesee*,
If you want to create an object in new thread and control it you can use *CreatePrivateObjectByNameInNewThread* or *CreateActiveXObjectInNewThread* functions. See examples here. Usually it depends on the end goal.

----------


## yokesee

I use your module in my project to load many dll without registration.
and it works really well.
https://pastebin.com/hPxJf4r4
I could not find the post code sorry for this pastebin.

as I use it in conjunction with your module multithreading.
could you please make some example.
sorry for bothering

----------


## The trick

yokesee,
What's your end goal? What should the example do? Please describe and i'll provide the example.

----------


## yokesee

Sorry to express myself so badly.

how to use the module "libraries without registration" that creates objects of the dll.
and those dll do the multithreading.
or if regfree multihreading objects can be created.
because it is simpler to execute calls to objects than through apis.
because it is easier to communicate between objects as well as through callback.
or so I see it.

greetings thank you for answering and for your work

----------


## jsvenu

> I don't know what should i answer. I gave you example and *wqweto* explained why you can't avoid type libraries.


Dear Trick,

Can you tell me how to find the *instance handle of the dll which can be implicitly loaded into our exe* without using LoadLibrary api .Then  we can use that loaded instance of the dll and *initialize runtime of the dll from our exe* instead of doing the same from  dll using dllmain.*In this way typelibrary can be avoided*.

regards,
JSVenu

----------


## The trick

*yokesee*,
The simplest method is to create a class in EXE which provides a object from external dll. Then you can use *CreatePrivateObjectByNameInNewThread* to create this object in its own apartments. I'll make example later.




> Can you tell me how to find the instance handle of the dll which can be implicitly loaded into our exe without using LoadLibrary api .Then we can use that loaded instance of the dll and initialize runtime of the dll from our exe instead of doing the same from dll using dllmain.In this way typelibrary can be avoided.


This is bad approach to solve problem. I gave some hints how to do it with and without tlb.

----------


## wqweto

@The trick (mailbox quota exceeded)

RE: jsvenu user

Hi,

I'm suspecting this guy is a malware creator wannabe and I already reported him based on the research he is doing.

Just use Add to Ignore List from his profile or at least try *not* to help him weaponize whatever rootkits/resources he is fantasizing to hide from anti-virus apps.

cheers,
</wqw>

----------


## jsvenu

> This is bad approach to solve problem. I gave some *hints* how to do it *with and without tlb*.


Dear Trick,

You already said in   http://www.vbforums.com/showthread.p...n-standard-dll post #19 as follows




> Re: initializing runtime in standard dll 
> *You can initialize the runtime in the DLLMain as well but that isn't good approach*. Just make an exported function which initializes the runtime and just call it after LoadLibrary.


Can you tell me *hints* on how to do without tlb.

regards,
JSVenu

----------


## The trick

> @The trick (mailbox quota exceeded)


I've cleaned the storage.

----------


## yokesee

> *yokesee*,
> The simplest method is to create a class in EXE which provides a object from external dll. Then you can use *CreatePrivateObjectByNameInNewThread* to create this object in its own apartments. I'll make example later.


Yes please
Thanks a lot

----------


## jsvenu

> @The trick (mailbox quota exceeded)
> 
> RE: jsvenu user
> 
> Hi,
> 
> I'm suspecting this guy is a malware creator wannabe and I already reported him based on the research he is doing.
> 
> Just use Add to Ignore List from his profile or at least try *not* to help him weaponize whatever rootkits/resources he is fantasizing to hide from anti-virus apps.
> ...


Dear wqw,

Already in the discussion of http://www.vbforums.com/showthread.p...n-standard-dll
Trick has given  example of *InitProjectContextDll* of initializing project context of dll thru exe without tlb.
But here he uses LoadLibrary.No *malware* here as you think.I am trying to use vb6 code directly instead of using a tlb for apis .

regards,
JSVenu

----------


## jsvenu

Dear Trick and Wqewto,

*Wqewto*'s quote: 



> For each API declare VB6 compiler generates a small function that loads the dll, finds the entry point by name/ordinal, converts params ANSI<->Unicode and then calls the function. Finally after the call is executed it checks the stack for mismatches (wrong number of parameters) and sets Err.LastDllError from GetlastError API. In threading scenarios this last step fails if the VB6 runtime (which includes the Err object among other thigs) is not initialized on the running thread.
> 
> Using typelib APIs does *not* set Err.LastDllError after calling the entry point, *unless* specifically instructed w/ the usesgetlasterror attribute in the IDL declaration.


I got the problem solved by using *RemoveLastDllError* module function at the beginning in Form_Load by *directly* using the API *without* having to use type lib from "*Multithreading in VB6 part 1*" of *Trick*'s code bank.

I thank both *Trick* for the example  and *Wqewto* for the  explanation.

regards,
JSvenu

----------


## The trick

As i promised i made the example of using *modTrickUnregCOM* and *modMultiThreading2* modules. It creates each external object in its own thread and you can access to it from main thread. Notice, you should compile the example to see threading. It works in main thread in IDE.


```
Option Explicit

' // Each plugin is created in its own thread.
' // You should compile the example to see result.
' // It creates the plugins in main thread in IDE.

Dim m_cPlugins  As Collection   ' // List of CPluginItem marshaled instances
Dim m_cAsyncIDs As Collection   ' // List of appropriate async ids (used to wait for thread completion)

' // Scan folder
Private Sub ScanPlugins()
    Dim cFso        As FileSystemObject
    Dim cFolder     As Folder
    Dim cFile       As File
    Dim bIsInIDE    As Boolean
    
    Set cFso = New FileSystemObject
    
    lstPlugins.Clear
    Set m_cPlugins = New Collection
    Set m_cAsyncIDs = New Collection
    
    Debug.Assert MakeTrue(bIsInIDE)
    
    ' // In IDE we use other folder
    If bIsInIDE Then
        Set cFolder = cFso.GetFolder(App.Path & "\Binaries")
    Else
        Set cFolder = cFso.GetFolder(App.Path)
    End If
    
    For Each cFile In cFolder.Files
        ' // Try to load file
        LoadPlugin cFile.Path
    Next
    
    cmdShowThreadID.Enabled = m_cPlugins.count > 0
    
End Sub

Private Sub LoadPlugin( _
            ByRef sPath As String)
    Dim cItem       As Object
    Dim lAID        As Long
    Dim tClsIds()   As GUID
    Dim sNames()    As String
    Dim lCount      As Long
    
    On Error GoTo error_handler
    
    ' // Get list of all co-clasees (plugins in dll)
    If Not GetAllCoclasses(sPath, tClsIds(), sNames(), lCount) Then Exit Sub

    Do While lCount > 0
        
        ' // Create CPluginItem in new thread
        Set cItem = CreatePrivateObjectByNameInNewThread("CPluginItem", , lAID)
        If cItem Is Nothing Then Exit Sub
        
        ' // This method is called from current (main) to different thread
        If Not cItem.Initialize(sPath, sNames(lCount - 1)) Then GoTo continue
        
        m_cAsyncIDs.Add lAID
        m_cPlugins.Add cItem
        lstPlugins.AddItem cItem.GetInfo()  ' // Call between threads
        
continue:
        
        lCount = lCount - 1
        
    Loop

error_handler:
    
End Sub

Private Sub cmdShowThreadID_Click()
    ' // Call the method of plugin which is in other thread
    ' // You can use asynch call if need. The messagebox is
    ' // appeared in other thread and it isn't modal related
    ' // to current thread
    m_cPlugins(lstPlugins.ListIndex + 1).ShowThreadID
End Sub

Private Sub Form_Load()
    modMultiThreading.Initialize
    modMultiThreading.EnablePrivateMarshaling True
    ScanPlugins
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Dim vItem   As Variant
    
    ' // This runs the threads completions
    Set m_cPlugins = Nothing
    
    ' // Wait until all the threads are finised
    For Each vItem In m_cAsyncIDs
        modMultiThreading.WaitForObjectThreadCompletion vItem
    Next
    
    modMultiThreading.Uninitialize
    
End Sub

Private Function MakeTrue( _
                 ByRef bValue As Boolean) As Boolean
    MakeTrue = True
    bValue = True
End Function
```



```
Option Explicit

Private m_cPlugin   As IPluginInfo
Private m_sPath     As String

Public Property Get Path() As String
    Path = m_sPath
End Property

' // IDispatch layer. You can use IPluginInfo::ShowThreadID directly
' // but it requires user interface marshaling. You can see examples
' // of that in VbTrickThreading demos.
Public Sub ShowThreadID()
    If Not m_cPlugin Is Nothing Then
        m_cPlugin.ShowThreadID
    End If
End Sub

' // The same
Public Property Get GetInfo() As String
    If Not m_cPlugin Is Nothing Then
        GetInfo = m_cPlugin.GetPluginInfo
    End If
End Property

' // Initialize the plugin
' // This method is called from main thread using proxy-stub layer
Public Function Initialize( _
                ByRef sPath As String, _
                ByRef sName As String) As Boolean
                    
    Set m_cPlugin = modUnregCOM.CreateObjectEx2(sPath, sPath, sName)
     
    Initialize = Not m_cPlugin Is Nothing
    
    If Initialize Then
        m_sPath = sPath
    Else
        m_sPath = vbNullString
    End If
    
End Function

Public Sub Uninitialize()

    If Len(m_sPath) Then
        
        Set m_cPlugin = Nothing
        
        UnloadLibrary m_sPath
        
        m_sPath = vbNullString
        
    End If
    
End Sub

Private Sub Class_Terminate()
    Uninitialize
End Sub
```

----------


## yokesee

Thank you very much, Trick.
The first tests performed work perfectly.
I don't understand much about Marshaling and TLB,
although assigning it as an object I can make direct calls to the object.
So it works perfect as I wanted, I will study Marshaling and TLB better.


Thanks a lot.

----------


## yokesee

sorry

----------


## The trick

????

----------


## jsvenu

Dear Trick,

You were using .res file  and .xml file in TrickMTDownloader and MarshalUserInterface applications in vb6.
Can you tell me why we use here and what is the purpose of using them as I am unable to run both the applications.

TrickMTDownloader 
When I build and run Downloader.exe I get the following error in message box

The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.

MarshalUserInterface 
When I built MarshalUserInterface and run the compiled exe it is crashing.

Please clarify.

regards,
JSVenu

----------


## jsvenu

Dear FunkyDexter,Wqewto,Eduardo,



I can tell you that I do not know the questions I have been asking are suspicious in nature and associated with the sort of techniques typically used to produce malware since I was asking only to understand and improve my understanding in VB6.In all the queries you can see that I am asking questions which are related to  learn  *multithreading* and *COM* in easy way thru vb6 similar to win32 c/c++ programming.
In win32 programming I can use free threading in non-COM.But I was having little knowledge about COM even in C/C++ which I understood somewhat better thru vb6.In vb6 use of Type libraries was new to me and we have to seperately write it so I asked can there be any other way to use apis directly without typelib.In MSDN I have seen ADO uses free threading and it can be used in vb6.The I thought that there can be easy way to do free threading in vb6 also and asked question in vb6 from which I could understand COM better.

This is the whole explanation I can sincerely give you and I am very sorry for making you think that my behaviour is suspicious.


regards,
JSVenu

----------


## FunkyDexter

The problem is that you're asking questions about how to make VB do things which VB is generally unsuited to doing.  VB (or more accurately the Com programming model) provides mechanisms to achieve comparable end goals which are considered "safe" and do not require you to pursue the techniques you're pursuing.  That begs the question "why would you pursue these techniques instead of using those provided by VB and it's framework?"

There are legitimate answers to that question.  As coders we sometimes need to step outside the safe constraints a framework places upon us to achieve some specific goal.  However, we recognise that people generally shouldn't be doing so without good reason.  That's why, when we see someone heading down those paths, we legitimately ask them _why_.  If they fail to provide a good answer that acts as a red flag that they're likely to be trying to do something they shouldn't.

So far the only answer you've given us is that you _want to know_ those techniques but that was already obvious from the fact that you asked the question and it really doesn't speak to your ultimate motives.  Essentially, all you've said is "teach me to be a better hacker... because I want to be a better hacker".  That position is going to get short shrift around here.


Edit> By the way, your use of *bolding* comes across as shouting and may turn people off.  I don't think that's how you mean it to come across but it's something you might want to consider.

----------


## xiaoyao

i write more words,use more hours,while delete my reply?
maybe my answer is not like this subject?
but i think my Createthread method is easy.
maybe i don't know VbTrickThreading，It's really useful in some ways

----------


## xiaoyao

it's my thread bbs page,maybe my method is more easy.
your question will  Find a solution
=========

the best VB6 CreateThread（support ocx，Support UserControl）-VBForums
http://www.vbforums.com/showthread.p...t-UserControl)

----------


## The trick

> maybe i don't know VbTrickThreading


It contains *VBCreateThread* function the analog of *CreateThread* API. The Julia fractal demo contains the usage of that function:


```
' // Generate Julia Set to glPixels() array
' // lThreadCount - number of threads to draw
' // The function divides whole canvas into areas and creates threads which draw these areas
Public Sub GenerateJulia( _
           ByVal cPic As PictureBox, _
           ByVal lThreadCount As Long)
 
    .  .  .
    
    For lIndex = 0 To lThreadCount - 1
        
        .  .  .
        
        ' // Create thread
        gHandles(lIndex) = vbCreateThread(0, 0, AddressOf ThreadProc, VarPtr(mtThreadsData(lIndex)), 0, 0)
    
    Next

End Sub

' // This function calculates the part of Julia Set
Private Function ThreadProc( _
                 ByRef tData As tThreadData) As Long
    
    .  .  .
    
    For lY = 0 To tData.lHeight - 1
        
        .  .  .
        
        For lX = 0 To tData.lWidth - 1
            
            glPixels(lX + tData.lX, lY + tData.lY) = glPalette(Julia(fX, fY))
            fX = fX + fStepX
            
        Next
        
        fX = tData.lX / tData.lTotalWidth * 2 - 1
        fY = fY + fStepY
        
    Next
    
End Function
```

The function *ThreadProc* is called from the different threads to draw the Julia set simultaneously.

----------


## xiaoyao

```
Private Sub Command3_Click()
CreateNewThread AddressOf ThreadTest, 33&, 0&

End Sub

Public Sub ThreadTest(ByVal lpParam As Long)
    InitFun
    msgbox "put your code here in a *.bas file"
    ExitFun
End Sub
```

----------


## jsvenu

> The problem is that you're asking questions about how to make VB do things which VB is generally unsuited to doing.  VB (or more accurately the Com programming model) provides mechanisms to achieve comparable end goals which are considered "safe" and do not require you to pursue the techniques you're pursuing.  That begs the question "why would you pursue these techniques instead of using those provided by VB and it's framework?"
> 
> There are legitimate answers to that question.  As coders we sometimes need to step outside the safe constraints a framework places upon us to achieve some specific goal.  However, we recognise that people generally shouldn't be doing so without good reason.  That's why, when we see someone heading down those paths, we legitimately ask them _why_.  If they fail to provide a good answer that acts as a red flag that they're likely to be trying to do something they shouldn't.
> 
> So far the only answer you've given us is that you _want to know_ those techniques but that was already obvious from the fact that you asked the question and it really doesn't speak to your ultimate motives.  Essentially, all you've said is "teach me to be a better hacker... because I want to be a better hacker".  That position is going to get short shrift around here.
> 
> 
> Edit> By the way, your use of *bolding* comes across as shouting and may turn people off.  I don't think that's how you mean it to come across but it's something you might want to consider.


Dear FunkyDexter,

Thankyou for the awesome clarification.
I understood what you mean and I can say that I will try to sincerely learn and use vb6 including advanced tecchniques of c/c++ in vb6 in a safe way to use them in good way rather than becoming hacker since I really love VB6.

regards,
JSVenu

----------


## jsvenu

> ```
> Private Sub Command3_Click()
> CreateNewThread AddressOf ThreadTest, 33&, 0&
> 
> End Sub
> 
> Public Sub ThreadTest(ByVal lpParam As Long)
>     InitFun
>     msgbox "put your code here in a *.bas file"
> ...


Dear xiaoyao,

   Thankyou for the reply.

*Your*  thread and *Trick*'s thread use same api code for initializing runtime in *new thread* as follows:

   CreateIExprSrvObj 
   CoInitialize 
   VBDllGetClassObject (...)

   Your *InitFun* module function does  the above *initialisation*.
   Upto here *both* are *same*.

   But the question here is initializing project context of activex control(or activex dll) which is user control from *another* activex control project and *not* activex control(user control) in the *same*  project in which the activex control is used.

  Here you were using activex control(or activex dll) in the *same exe project* and *no project context initialization is required* as it is the same exe project whose context is *initialized* by *default*.

   When you are working with *two different projects* in which one is the *main exe project* and the other is the* activex  dll or activex  ocx* which is *loaded* to the *main exe*, 
  the *project context of the dll or ocx  project has to be initialized in main exe project*.

*If you don't initialize runtime* you can use any *APIs* declared *only* in *typelib* (Using typelib APIs does *not* set Err.LastDllError after calling the entry point, *unless* specifically instructed w/ the 
   usesgetlasterror attribute in the IDL declaration.) as already explained by *Wqewto* in post #2.
*Still* if you want to use API *directly* *without runtime init** instead of typelib* use the following *RemoveLastDllError* module function at the startup code in activex ocx or dll project from "Multithreading in VB6 part 1" of *Trick*'s code bank which is as follows(which I already mentined in  post #20):



```
Public Sub RemoveLastDllError()
    Dim hMod    As Long
    Dim lpProc  As Long
    hMod = GetModuleHandle(StrPtr("msvbvm60"))
    lpProc = GetProcAddress(hMod, "__vbaSetSystemError")
    VirtualProtect lpProc, 1, PAGE_EXECUTE_READWRITE, 0
    GetMem1 &HC3, ByVal lpProc
End Sub

```

regards,
JSVenu

----------


## Schmidt

> But the question here is initializing project context of activex control(or activex dll) 
> which is user control from *another* activex control project ...


Hmm, if it is all that this discussion about (managing Forms on their own Threads, 
loading Controls from external OCXes onto these threaded Forms) - 
then please take a look at this new CodeBank entry: http://www.vbforums.com/showthread.p...-(simple-Demo)

Just a handful of normal VB6-Code is needed (no API-calls, and no ASM-hacks) for "threaded Forms, loading an OCX".

Olaf

----------


## jsvenu

> Hmm, if it is all that this discussion about (managing Forms on their own Threads, 
> loading Controls from external OCXes onto these threaded Forms) - 
> then please take a look at this new CodeBank entry: http://www.vbforums.com/showthread.p...-(simple-Demo)
> 
> Just a handful of normal VB6-Code is needed (no API-calls, and no ASM-hacks) for "threaded Forms, loading an OCX".
> 
> Olaf


Dear Schmidt,
  Thank you for the example.
  I have gone thru the activex exe example.

  Yes it is simple nice inproc activex application .


regards,
JSVenu

----------


## Schmidt

> I have gone thru the activex exe example.The main problem here is each form is loaded into STA in seperate exe.


No, the threads (the STAs) are created InProcess ... there's only one Exe-Process ("AxThreading.exe") which is running, 
no matter how often you press the Button to create another threaded Form (which all show-up in the Task*Bar* of course).




> So when we click the button ten times ten exes are created 
> as can be seen in task manager.


That observation is wrong, please test properly...

Olaf

----------


## The trick

The AX-Exe approach isn't applicable in several cases. For example it requires administrative rights to Run. You can't use AX-EXE approach to accept callbacks from different threads. And so on...

----------


## Schmidt

> The AX-Exe approach isn't applicable in several cases.


Well, for beginners (who want to learn, how VB6's STA-based threading works),
it is certainly the place to "start from".




> For example it requires administrative rights to Run.


Only on the very first run, one has to "run as Administrator" - or (to avoid that) -
a decent Setup could be used, to pre-register the Ax-Exe (along with the external OCXes).

It's also quite easy, to move (in a second step, after understanding Ax-Exe-based STA-handling),
from the Ax-Exe-approach to a "Standard-Exe + Ax-Dll-based STA"-approach (to avoid these "registry issues" entirely).




> You can't use AX-EXE approach to accept callbacks from different threads.


One can (by doing the usual thing, handling the incoming thread-call via a TypeLib-defined PostMessage-delegation).

Besides, JSVenus threads are all about "showing Forms on new threads", 
and that's covered by my example quite nicely I think.

He also stated, that he wants to learn about threading in VB6 (which is all about handling of ThreadObjects on STAs).
Any attempts at "FreeThreading" or "VB6-Runtime-hacks via ASM" are inherently instable and not generically usable.

Olaf

----------


## The trick

> Well, for beginners (who want to learn, how VB6's STA-based threading works),
> it is certainly the place to "start from".


Yes, that's applicable for beginners and on some other cases. I just told about the cases when it isn't applicable  :Wink: . Understanding how does STA work (generally) like Message Pumping, Marshaling etc. is exactly hidden from a developer. I think it's quite bad regarding to learning "how does STA works". If one wants to migrate then to an other language that knowledge are useful.




> Only on the very first run, one has to "run as Administrator" - or (to avoid that) -
> a decent Setup could be used, to pre-register the Ax-Exe (along with the external OCXes).


Yes, you're right. Seems (if i'm not wrong, i don't remember exactly) there is a way to run it without rights and registration.




> It's also quite easy, to move (in a second step, after understanding Ax-Exe-based STA-handling),
> from the Ax-Exe-approach to a "Standard-Exe + Ax-Dll-based STA"-approach (to avoid these "registry issues" entirely).


Not-always. For example SxS is incompatible with some VB6-runtime things. Using my module/framework it's quite easy to work with threading at more low-level (and more control) manner (like in C, C++ for example) but you can use high-level function if you need.




> One can (by doing the usual thing, handling the incoming thread-call via a TypeLib-defined PostMessage-delegation).


One can, but one may not know the restrictions/pitfails. For example if you receive call from a thread you can't use global/static variables because they are stored at TLS location offset. Using my module one can just receive callback using couple functions. If you tell about simple approach for beginners it's seems not suitable.




> He also stated, that he wants to learn about threading in VB6 (which is all about handling of ThreadObjects on STAs).


Threading isn't "all about handling of ThreadObjects on STAs".




> Any attempts at "FreeThreading" or "VB6-Runtime-hacks via ASM" are inherently instable and not generically usable.


JSVenus thread is for usage/internal structure VbTrickThreading/msvbvm module to achieve multithreading. If you hate it or it's unstable for you or you afraid ASM or something like that you can report about errors etc here/CodeBank/GitHub.

----------


## jsvenu

> Hmm, if it is all that this discussion about (managing Forms on their own Threads, 
> loading Controls from external OCXes onto these threaded Forms) - 
> then please take a look at this new CodeBank entry: http://www.vbforums.com/showthread.p...-(simple-Demo)
> 
> Just a handful of normal VB6-Code is needed (no API-calls, and no ASM-hacks) for "threaded Forms, loading an OCX".
> 
> Olaf


Dear olaf,
Yes you are right it is inproc example only.*Sorry* for understanding  in a wrong way.

Dear Trick,

If we use any *api* call  with declare in active control project like when we click on a added button in ocx project and open it in exe I think project context for the ocx is also initialized automatically in the *new thread* and *still* we have to use your  *RemoveLastDllError* module function to avoid crash due to API usage.Please clarify am I correct.




regards,
JSVenu

----------


## jsvenu

Dear Olaf,

I added a button to the ExternalControl activex control and added button1 control as follows:
Private Sub Command1_Click()
Dim hUser32         As Long
hUser32 = GetModuleHandle(StrPtr("user32"))
MsgBox "in button1"
End Sub

When I call from AxThreading exe and click on button1 of activex control the declared api  GetModuleHandle works 
like a *charm* *without crash even if we don't use RemoveLastDllError module function*.Very *nice* example.Thank you olaf,

regards,
JSVenu

----------


## The trick

> If we use any api call with declare in active control project like when we click on a added button in ocx project and open it in exe I think project context for the ocx is also initialized automatically in the new thread and still we have to use your RemoveLastDllError module function to avoid crash due to API usage.Please clarify am I correct.


When you create an object using DllGetClassObject (the standard ActiveX way) it always initializes the context for current thread.

----------


## jsvenu

> When you create an object using DllGetClassObject (the standard ActiveX way) it always initializes the context for current thread.


Dear Trick,
Thankyou for the *clear* clarification.
regards,
JSVenu

----------


## Schmidt

> I added a button to the ExternalControl activex control and added button1 control as follows:
> Private Sub Command1_Click()
> Dim hUser32         As Long
> hUser32 = GetModuleHandle(StrPtr("user32"))
> MsgBox "in button1"
> End Sub
> 
> When I call from AxThreading exe and click on button1 of activex control the declared api  GetModuleHandle works 
> like a *charm* *without crash even if we don't use RemoveLastDllError module function*.


Of course, because the example adheres to the "VB6-COM-STA threading-rules"
(implicitely, hidden from the User).

You can write basically the same thing (which adheres to these rules) also with the tricks Helper-module 
(using AX-Dll-Classes instead of Ax-Exe-Classes) - 
but then you'd have to learn, which of the provided "threading-helper-functionality" you'd have to avoid - 
and which parts of it are "safe to use" (to not break those COM-threading-rules).

Olaf

----------


## The trick

> When I call from AxThreading exe and click on button1 of activex control the declared api GetModuleHandle works
> like a charm without crash even if we don't use RemoveLastDllError module function.




```
Private Sub cmdCreateThread_Click()
    CloseHandle vbCreateThread(0, 0, AddressOf ThreadProc, 0, 0, 0)
End Sub

Private Sub Form_Load()
    Me.Caption = App.ThreadID
    Controls.Add("TestOCX.ctlTest", "Ctl1").Visible = True
End Sub
```



```
Option Explicit

Sub Main()
    modMultiThreading.Initialize
    frmMain.Show vbModal
    modMultiThreading.Uninitialize
End Sub

Public Function ThreadProc( _
                ByVal l As Long) As Long
    Dim cFrm As frmMain
    
    Set cFrm = New frmMain
    
    cFrm.Show vbModal
        
End Function
```


The buttons creates a thread with form with external OCX. It requires no administrative right (for EXE), no install (for EXE), shared global variables, etc.

----------


## jsvenu

Dear *Trick* and *Olaf*,

Thank you very much for the *nice threading* examples along with *clear* explanation.

regards,
JSVenu

----------


## xiaoyao

you do like on thread:Controls.Add("TestOCX.ctlTest", "Ctl1").Visible = True

when i add usercontrol  on form by my hand,not use"controls.add"。
createthread for load the form with usercontrol or ocx,it,s error。
my english is not good,i can't understand your mean。maybe i need down your code for test.
,someone tell me,can use method(EbLoadRunTime
EbCreateContext
EbSetContextWorkerThread)

----------


## The trick

xiaoyao, please attach the example.

----------


## xiaoyao

// This method is called from main thread using proxy-stub layer
Public Function Initialize( _
                ByRef sPath As String, _
                ByRef sName As String) As Boolean

    Set m_cPlugin = modUnregCOM.CreateObjectEx2(sPath, sPath, sName)
     your method need load ocx?by clsid?
I want  createthread ( address of test) only in stand exe,no need ocx,no need stand dll,no need com dll,no need activex exe.
my method code lines so much,i want to find a easy way,can you help me?


```
sub test()
dim f as form2
set f=new form2 ' a usercontrol in this form.
f.show 1
end sub
```

----------


## The trick

I'll answer tomorow.

----------


## Schmidt

> I want  createthread ( address of test) only in stand exe,
> ...no need ocx,no need stand dll,no need com dll,no need activex exe.


Why do you want to avoid using other (additional) libraries 
(later shipping with your Exe, e.g. in a \Bin\-sub-folder)?

You are using VB6 after all - a high-level-language which has its strengths exactly there
(in glueing selfdescribing COM-Objects and COM-Controls together in a RADish way).

If it is because of "avoiding to make a proper installer" - then you should learn about regfree-COM mechanisms first.

VB6 was not made for "normal, free-threading"...
It supports a different kind of threading instead, which requires you to make use of ActiveX-Dlls 
(or ActiveX-Exes), when you want to play things "by the (VB6)book" (to avoid instabilities).

Olaf

----------


## xiaoyao

where can i download project
i want test add usercontrol on thread form by toolbar ,not by ocx,how can i do?
 Controls.Add("TestOCX.ctlTest",

----------


## jsvenu

> where can i download project
> i want test add usercontrol on thread form by toolbar ,not by ocx,how can i do?
>  Controls.Add("TestOCX.ctlTest",


Dear xiaoyao,

  Goto *project* ->*components* and *browse* for the .*ocx* file which you want to add to the project 
and select *open* and then click *ok*.Automatically it will appear in tool bar which you can add to the form in the standard exe project.

regards,
JSVenu

----------


## xiaoyao

Code:
Private Sub cmdCreateThread_Click()
    CloseHandle vbCreateThread(0, 0, AddressOf ThreadProc, 0, 0, 0)
End Sub

Private Sub Form_Load()
    Me.Caption = App.ThreadID
    Controls.Add("TestOCX.ctlTest", "Ctl1").Visible = True
End Sub
Code:
Option Explicit

Sub Main()
    modMultiThreading.Initialize
    frmMain.Show vbModal
    modMultiThreading.Uninitialize
End Sub

Public Function ThreadProc( _
                ByVal l As Long) As Long
    Dim cFrm As frmMain

    Set cFrm = New frmMain

    cFrm.Show vbModal

End Function

where is the project link for download?

----------


## jsvenu

> Code:
> Private Sub cmdCreateThread_Click()
>     CloseHandle vbCreateThread(0, 0, AddressOf ThreadProc, 0, 0, 0)
> End Sub
> 
> Private Sub Form_Load()
>     Me.Caption = App.ThreadID
>     Controls.Add("TestOCX.ctlTest", "Ctl1").Visible = True
> End Sub
> ...


Dear xiaoyao,

I am attaching the project.

You first build the ExternalControl*.vbp* activex project in the attachment folder and generate ExternalControl.ocx.

Then open the standard exe project(AxThreading*.vbp*) and add the ExternalControl.ocx thru project components to the tool bar and then add it to the form in the exe project and build it and run the compiled exe.Click on the button on the form.

regards,
JSVenu

----------


## The trick

> // This method is called from main thread using proxy-stub layer
> Public Function Initialize( _
> ByRef sPath As String, _
> ByRef sName As String) As Boolean
> 
> Set m_cPlugin = modUnregCOM.CreateObjectEx2(sPath, sPath, sName)
> your method need load ocx?by clsid?
> I want createthread ( address of test) only in stand exe,no need ocx,no need stand dll,no need com dll,no need activex exe.
> my method code lines so much,i want to find a easy way,can you help me?


This is other project which i did for yokesee which shows the plugin-based concept with multithreading.
To create a thread you need to use this module. There are the examples of usage. You don't need any dependencies or dlls. This example just shows how to use an external OCX.




> where can i download project


I've attached the one. The modMultiThreading module you can get if you go to the link i provided above.

----------


## jsvenu

> you do like on thread:Controls.Add("TestOCX.ctlTest", "Ctl1").Visible = True
> 
> when i add usercontrol  on form by my hand,not use"controls.add"。
> createthread for load the form with usercontrol or ocx,it,s error。
> my english is not good,i can't understand your mean。maybe i need down your code for test.
> ,someone tell me,can use method(*EbLoadRunTime
> EbCreateContext
> EbSetContextWorkerThread*)


Dear xiaoyao,

Here is the Link http://www.devx.com/vb2themax/CodeDownload/19804

for *FreeThreader component for VB6* which uses above bold API in the same order  for runtime initialisation in *CreateMT* and *EnableEvents*  functions in assembly language in the file MULTITHREAD.Asm .
It is described as follows:
*VB6 does not natively support free threading, but with this ActiveX library you can finally create multiple threads and make them communicate, write delegate functions (similarly to VB.NET) and use Structured Exception Handling. The full source code (in VB6 and Assembler) and some sample projects are provided.*

This is also reply to http://www.vbforums.com/showthread.p...t-UserControl) post #9 

Regarding internal working and stability  of this code you can take help from *Trick* since I have less knowledge in ASM and already he helped me a lot regarding multithreading and he can better understand undocumented API like this.

regards,
JSVenu

----------


## xiaoyao

stand exe-thread ,can't support (usercontrols),how to fix it?
it's my thread bbs page :Frown: code is to hard ,and lines to long,use asm,hook)

http://www.vbforums.com/showthread.p...t-UserControl)

 download :http://www.vbforums.com/attachment.p...3&d=1579799200

----------


## xiaoyao

> This is other project which i did for yokesee which shows the plugin-based concept with multithreading.
> To create a thread you need to use this module. There are the examples of usage. You don't need any dependencies or dlls. This example just shows how to use an external OCX.
> 
> 
> I've attached the one. The modMultiThreading module you can get if you go to the link i provided above.


==============
*how to add usercontrols on forms object in thread?
(your project can add ocx,but can't support vb6 usercontrol)*


```
Private obj As Object

Private Sub Command1_Click()
On Error GoTo ERR

Set obj = Me.Controls.Add("Project1.UserControl1", "UserControl1B")
obj.Visible = True
MsgBox obj.Width
  Exit Sub
ERR:
  MsgBox "ERR:" & ERR.Number & "," & ERR.Description
End Sub

sub test2
Dim ctlName As Control
Set ctlName = Form1.Controls.Add("Project1.UserControl1", "Text1", Form1)
ctlName.Visible = True
end sub
```

----------


## The trick

> how to add usercontrols on forms object in thread?
> (your project can add ocx,but can't support vb6 usercontrol)


Seems it's the problem with the runtime. It registers the "Global Factory" thru *CoRegisterClassObject* from the main STA and when you create an usercontrol it calls *CoGetClassObject*. *CoGetClassObject* checks the registered Apartment ID i.e. you can't get this "Global Factory" from the different apartment. The same problem you'll see with ActiveX EXE project type. To avoid this you can for example use *Load* statement with a control array. Additionally you can set the Threading Model to Apartment (1) in *VBHeader.dwThreadFlags* when you create a VBHeader. In this case each thread (namely STA) will have its own HXMod internal object like in ActiveX DLL. Only in this case you need to performs additional cleaning like VBCanUnloadNow, etc.

----------


## xiaoyao

CoGetClassObject,maybe my chinese friend fix error by hook "CoGetClassObject",use asm set eip,But the code is too long.
the vb project down here,can you chang for sample?
thank you very much。
http://www.vbforums.com/showthread.p...t-UserControl)


method 2:{HOOK DllFunctionCall} and change argument.(like hook messageboxa,can change msg text or title)
http://www.m5home.com/bbs/thread-3965-1-1.html
What do you think about?

----------


## The trick

Just change this function as follows:


```
' // Create copy of VBHeader and other structures
' // The first four bytes contain thread ID. We use that ID to clean unused headers
Private Function CreateVBHeaderCopy() As Long
    
   . . .
    
    CopyMemory ByVal pHeader, ByVal pVBHeader, &H6A
    
    ' // ++++++++++++++++++++++++++++
    ' // to xiaoyao
    ' // Make Apartment threading model
    ' //
    GetMem4 1&, ByVal pHeader + &H3C
    ' //
    ' // You should ensure extra cleaning like VBDllCanUnloadNow, etc.
    ' // ++++++++++++++++++++++++++++
    
    ' // Update strings offsets
    CopyMemory pNames(0), ByVal pVBHeader + &H58, &H10
    
    . . .

End Function
```

----------


## xiaoyao

> Just change this function as follows:
> 
> 
> ```
> ' // Create copy of VBHeader and other structures
> ' // The first four bytes contain thread ID. We use that ID to clean unused headers
> Private Function CreateVBHeaderCopy() As Long
>     
>    . . .
> ...




```
Public Function GETVBHeaderID() As Long                                      '取VB头,全新的取VB头方法,速度比OPEN文件快得多
    Dim lPtr          As Long, lRet              As Long, isvb              As String, ll                As Long, mdat(1033)        As Byte
    lPtr = VBGetModuleHandle(ByVal 0&)
    isvb = StrConv("VB5!", vbFromUnicode)
    Do
        If ReadProcessMemory(-1, ByVal lPtr, mdat(0), 1034, ll) = 0 Then Exit Function
        lRet = InStrB(mdat, isvb)
        If lRet <> 0 Then Exit Do
        lPtr = lPtr + 1024
    Loop
    GETVBHeaderID = lPtr + lRet - 1
    
End Function

SUB ThreadTest()
CreateIExprSrvObj 0, 4, 0
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
Call VBDllGetClassObject(U1, U2, pVBHeader, pDummy, pIID, pDummy2)
msgbox "abc"
dim f as form2 'with usercontrol
set f=new form2
f.show 1
end sub

'in bas
public pVBHeader as long ,U1 as long 

'in main form
sub test()
pVBHeader=GETVBHeaderID
U1=app.hInstance

createthread *,addressof ThreadTest,*

end sub
```

i use simple thread method, don't use "HeapAlloc",how to change my code?

HOW TO ADD YOUR CODE TO My project ( for support usercontrol)?
my project only use  three lines code for createthread.
not use HeapAlloc,not use tls,not same like you.

----------


## jsvenu

> ```
> Public Function GETVBHeaderID() As Long                                      '取VB头,全新的取VB头方法,速度比OPEN文件快得多
>     Dim lPtr          As Long, lRet              As Long, isvb              As String, ll                As Long, mdat(1033)        As Byte
>     lPtr = VBGetModuleHandle(ByVal 0&)
>     isvb = StrConv("VB5!", vbFromUnicode)
>     Do
>         If ReadProcessMemory(-1, ByVal lPtr, mdat(0), 1034, ll) = 0 Then Exit Function
>         lRet = InStrB(mdat, isvb)
>         If lRet <> 0 Then Exit Do
> ...


Dear xiaoyao,

With some guidance already I got from Trick I can tell you one thing that you use startup code(Here sub main) with code for avoiding re-entrancy in new thread since you are using the same vbheader in vbdllgetclassobject api in new thread.To solve this problem Trick modifies the existing vbheader and creates a new copy of this vbheader and in this new copy he is setting dwThreadFlags field to 1 as he explained.

May be as per Trick's quote 




> Seems it's the problem with the runtime. It registers the "Global Factory" thru CoRegisterClassObject from the main STA and when you create an usercontrol it calls CoGetClassObject. CoGetClassObject checks the registered Apartment ID i.e. you can't get this "Global Factory" from the different apartment. The same problem you'll see with ActiveX EXE project type. *To avoid this you can for example use Load statement with a control array*. Additionally you can set the Threading Model to Apartment (1) in VBHeader.dwThreadFlags when you create a VBHeader. In this case each thread (namely STA) will have its own HXMod internal object like in ActiveX DLL. Only in this case you need to performs additional cleaning like VBCanUnloadNow, etc.


Trick can tell if there is any other way like *using Load statement with a control array* without need for modifying VBHeader.dwThreadFlags to 1 so that using the same vbheader you can support usercontrol in form in a simple way by using submain containing code to avoid re-entrancy in new thread.

regards,
JSVenu

----------


## xiaoyao

> Dear xiaoyao,
> 
> With some guidance already I got from Trick I can tell you one thing that you use startup code(Here sub main) with code for avoiding re-entrancy in new thread since you are using the same vbheader in vbdllgetclassobject api in new thread.To solve this problem Trick modifies the existing vbheader and creates a new copy of this vbheader and in this new copy he is setting dwThreadFlags field to 1 as he explained.
> 
> May be as per Trick's quote 
> 
> 
> 
> Trick can tell if there is any other way like *using Load statement with a control array* without need for modifying VBHeader.dwThreadFlags to 1 so that using the same vbheader you can support usercontrol in form in a simple way by using submain containing code to avoid re-entrancy in new thread.
> ...




```
 hModule = App.hInstance
  lpVBHeader = GetVBHeader()

Public U1 As Long, U2 As Long, U3 As Long, G3 As Long, pDummy As Long, pDummy2 As Long, IsVb6 As Boolean

Public Declare Function VBDllGetClassObject2 Lib "msvbvm60.dll" Alias "VBDllGetClassObject" (g1 As Long, g2 As Long, ByVal g3_vbHeader As Long, REFCLSID As Long, REFIID As Any, ppv As Long) As Long

Sub Test2(ByVal lpParameter As Long)
CreateIExprSrvObj 0&, 4&, 0&            'VB6运行库初始化
QueryPerformanceCounter CPUv2
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
Call VBDllGetClassObject2(hModule, U2, lpVBHeader, pDummy, Iid, pDummy2) 'GOOD
' Call VBDllGetClassObject(VarPtr(hModule), U2, lpVBHeader, pDummy, Iid, pDummy2)   

MsgBox "NOW=" & Now & ",lpParameter=" & lpParameter, , "APP.TITLE=" & App.Title

Thread_Form_VSFlexGrid
    vbCoUninitialize
End Sub
’in form
CreateThread ByVal 0&, ByVal 0&, AddressOf Test2, ByVal 345&, 0&, 0&
```

this method   use windows api "CreateThread ",not use "vbCreateThread(**)"
"Thread_Form_VSFlexGrid" is a sub for show form with ocx(vsflexgrid or webbrowser ),it's ok,succesful;
but not support usercontrol

i like easy createthread methoad,Complete the function with a few lines of code。

If you're interested, you want to make a very simple way to call multithreaded methods together

----------


## xiaoyao

> Just change this function as follows:
> 
> 
> ```
> ' // Create copy of VBHeader and other structures
> ' // The first four bytes contain thread ID. We use that ID to clean unused headers
> Private Function CreateVBHeaderCopy() As Long
>     
>    . . .
> ...


 


```
Private Sub ThreadProc(lpParameter As threadData)
CreateIExprSrvObj 0&, 4&, 0&
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
TlsSetValue tlsIndex, lpParameter

VBDllGetClassObject VarPtrHModule, 0, PublpNewHdr, clsid, Iid, 0 '这句才是调用目标“函数”

HeapFree hHeap, 0, lpParameter
vbCoUninitialize
End Sub

public hModule  as long 
sub main()
*** more code for Initialize the thread info

hHeap = GetProcessHeap()

PublpNewHdr = HeapAlloc(hHeap, 0, &H6A)
 hModule = App.hInstance
VarPtrHModule=VarPtr(hModule
sub main
```

this method   use tls api for "vbCreateThread(**)"
i change for simple,use public var (VarPtrHModule=VarPtr(hModule))

  i dont' khnow what bug will happend,i use the same "PublpNewHdr " in every thread.
in china ,Fewer and fewer people use VB6，one Programmer elite,he use hook api to fix for support "usercontrol"

The level is very high for you. Thank you for your help. I hope VB6 can be used for several years


you can send me a email :Frown: ## is @),i reply email to send the project to you
xiaoyaocode#163.com

----------


## The trick

xiaoyao,
you can't "just use" the header because it has many things like for example global/static variables which are zeroed. 
If you want to write me you can use Private Message here.

----------


## xiaoyao

> Why do you want to avoid using other (additional) libraries 
> (later shipping with your Exe, e.g. in a \Bin\-sub-folder)?
> 
> You are using VB6 after all - a high-level-language which has its strengths exactly there
> (in glueing selfdescribing COM-Objects and COM-Controls together in a RADish way).
> 
> If it is because of "avoiding to make a proper installer" - then you should learn about regfree-COM mechanisms first.
> 
> VB6 was not made for "normal, free-threading"...
> ...


if new thread support *.ocx ot vb6 usercontrols, it's also support createobject(*) , it's only com object like xmlhttp or adodb.stream.
ocx.and usercontrol that with ui,it's hard then com object.
why i use ocx? i think if webbrowser or usercontrol (more thread will use more cpu)
if  every ocx run in every cpu，  i think it will be run quickly。
buy webbrowser maybe only run in sta,maybe it can't write buffer file in disk on the same time(by more thread)?

----------


## xiaoyao

use public vb header,tls,the same GetProcessHeap(),my code run successful,but i'm afraid,if more new thread run the same time in sub "ThreadProc"，maybe read the same memory address,so errors will happend?

----------


## jsvenu

> Seems it's the problem with the runtime. It registers the "Global Factory" thru *CoRegisterClassObject* from the main STA and when you create an usercontrol it calls *CoGetClassObject*. *CoGetClassObject* checks the registered Apartment ID i.e. you can't get this "Global Factory" from the different apartment. The same problem you'll see with ActiveX EXE project type. To avoid this you can for example use *Load* statement with a control array. Additionally you can set the Threading Model to Apartment (1) in *VBHeader.dwThreadFlags* when you create a VBHeader. In this case each thread (namely STA) will have its own HXMod internal object like in ActiveX DLL. Only in this case you need to performs additional cleaning like VBCanUnloadNow, etc.


Dear Trick,

When we use *normal* usercontrol with VBHeader.dwThreadFlags updated to 1 it works perfect.

But when we add usercontrol of type "*Colorful Control*" or "*Control Events*" and save as say xyz(xyz.ctl)
and try to load it at runtime to the form thru its button in *new thread* as follows:



```
Private Sub Command1_Click()
Dim x As Control
On Error GoTo err1

Controls.Add("FormThreading.xyz", "Ctl").Visible = True

Exit Sub
err1:
MsgBox "err:" & Err.Number & "," & Err.Description & "," & Err.Source

End Sub
```

 I am getting the same following error

*---------------------------
FormThreading
---------------------------
err:711,Invalid class string.
Looking for object with ProgID: FormThreading.xyz,FormThreading
---------------------------
OK   
---------------------------
*

Why is this difference happening.Is there a way to *solve* the problem.

Please clarify.

regards,
JSVenu

----------


## The trick

You should uncheck "Remove Information About Unused Controls" checkbox in the project properties.

----------


## jsvenu

> You should uncheck "Remove Information About Unused Controls" checkbox in the project properties.


Dear Trick,

Thankyou . Now it works perfect after uncheck.

You tell me one thing that when I use 

CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED instead of CoInitialize ByVal 0& in 
your modmultithreading ThreadProc as follows:




```
' // Initialize runtime for new thread and run procedure
Private Function ThreadProc( _
                 ByVal pParameter As Long) As Long
    Dim cExpSrv     As IUnknown
    Dim bIsInIDE    As Boolean
    Dim tClsId      As tCurGUID
    Dim tIID        As tCurGUID
    Dim tThreadData As tThreadData
    Dim hHeap       As Long
    Dim pNewHeader  As Long
    Dim COINIT_APARTMENTTHREADED As Long
    COINIT_APARTMENTTHREADED = 1
    Debug.Assert MakeTrue(bIsInIDE)
    
    If Not bIsInIDE Then
        Set cExpSrv = CreateIExprSrvObj(0, 4, 0)
    End If
    
    'CoInitialize ByVal 0&
    CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
    hHeap = GetProcessHeap()
    
    TlsSetValue lTlsSlot, ByVal pParameter
    
    GetMem8 ByVal pParameter, tThreadData

    If bIsInIDE Then
        FakeMain
    Else
        
        pNewHeader = CreateVBHeaderCopy()
```

where CreateVBHeaderCopy contains 


GetMem4 1&, ByVal pHeader + &H3C

the new thread crashes,

If I *comment* the code
 GetMem4 1&, ByVal pHeader + &H3C in *CreateVBHeaderCopy 
*and use  
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
Then *no* crash is there but the thread *behaves* like *STA* giving same error msg for usercontrol.

Can you tell me why the new thread *crashes* when we use *both

*

GetMem4 1&, ByVal pHeader + &H3C in CreateVBHeaderCopy *and 
*CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED in ThreadProc

regards,
JSVenu

----------


## jsvenu

> Dear xiaoyao,
> 
> Here is the Link http://www.devx.com/vb2themax/CodeDownload/19804
> 
> for *FreeThreader component for VB6* which uses above bold API in the same order  for runtime initialisation in *CreateMT* and *EnableEvents*  functions in assembly language in the file MULTITHREAD.Asm .
> It is described as follows:
> *VB6 does not natively support free threading, but with this ActiveX library you can finally create multiple threads and make them communicate, write delegate functions (similarly to VB.NET) and use Structured Exception Handling. The full source code (in VB6 and Assembler) and some sample projects are provided.*
> 
> This is also reply to http://www.vbforums.com/showthread.p...t-UserControl) post #9 
> ...


Dear Trick,

Somewhere I read that ThunRTMain calls EbLoadRuntime.What does this Eb mean.
Can we use EbLoadRunTime, EbCreateContext, EbSetContextWorkerThread apis in *pure vb6* for 

runtime initialisation,write delegate functions (similarly to VB.NET)  .

Can you show me a small threading example of their usage like you have shown for CreateIExprSrvObj, CoInitializeEx and vbdllgetclassobject apis.

regards,
JSVenu

----------


## xiaoyao

hModule = App.hInstance
  lpVBHeader = GetVBHeader()

Public U1 As Long, U2 As Long, U3 As Long, G3 As Long, pDummy As Long, pDummy2 As Long, IsVb6 As Boolean

Public Declare Function VBDllGetClassObject2 Lib "msvbvm60.dll" Alias "VBDllGetClassObject" (g1 As Long, g2 As Long, ByVal g3_vbHeader As Long, REFCLSID As Long, REFIID As Any, ppv As Long) As Long

Sub Test2(ByVal lpParameter As Long)

CreateIExprSrvObj 0&, 4&, 0&           
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
Call VBDllGetClassObject(VarPtr(hModule), U2, lpVBHeader, pDummy, Iid, pDummy2)   

MsgBox "NOW=" & Now & ",lpParameter=" & lpParameter, , "APP.TITLE=" & App.Title
vbCoUninitialize

End Sub

in form
CreateThread ByVal 0&, ByVal 0&, AddressOf Test2, ByVal 345&, 0&, 0

----------


## xiaoyao

hHeap = GetProcessHeap()
    TlsSetValue lTlsSlot, ByVal pParameter
it's for support usercontrol,

if only for sta thread (createthread api),no need GetProcessHeap()。
support ocx,support com-dll,not support usercontrol

----------


## The trick

> Can you tell me why the new thread crashes when we use both
> 
> 
> 
> GetMem4 1&, ByVal pHeader + &H3C in CreateVBHeaderCopy and
> CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED in ThreadProc
> 
> regards,
> JSVenu


Please attach the project. I think *CoInitializeEx* has the bad declaration maybe because CoInitialize initializes STA.




> Dear Trick,
> 
> Somewhere I read that ThunRTMain calls EbLoadRuntime.What does this Eb mean.
> Can we use EbLoadRunTime, EbCreateContext, EbSetContextWorkerThread apis in pure vb6 for
> 
> runtime initialisation,write delegate functions (similarly to VB.NET) .


*Eb* means - Excel Basic.
You can use it in the restricted scenarios as i think. The solution depends on the task.

----------


## jsvenu

Dear Trick,




> The trick;5448235]Please attach the project. I think *CoInitializeEx* has the bad declaration maybe because CoInitialize initializes STA.



I am attaching the project.
Please clarify why the new thread crashes on clicking *Create Thread* button when we use both

GetMem4 1&, ByVal pHeader + &H3C in CreateVBHeaderCopy and 
CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED in ThreadProc





> *Eb* means - Excel Basic.
> You can use it in the restricted scenarios as i think. The solution *depends* on the *task*.


Can you give me a small example of their (*EbLoadRunTime*, *EbCreateContext*, *EbSetContextWorkerThread* ) *usage* like we use vbdllgetclassobject  api using declare.
For example we have 
Declare Sub *EbLoadRunTime* Lib "msvbvm60" (ByVal BaseAddress As Long, ByVal BaseInit As Long)

But how to use *EbLoadRunTime * for* runtime loading* and *EbCreateContext* for *creating  context* and *setting context of any  thread* using *EbSetContextWorkerThread* .

regards,
JSVenu

----------


## The trick

As i expected the error in the declarations. From ObjBase.h


```
// COM initialization flags; passed to CoInitialize.
typedef enum tagCOINIT
{
  COINIT_APARTMENTTHREADED  = 0x2,      // Apartment model

#if  (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM
  // These constants are only valid on Windows NT 4.0
  COINIT_MULTITHREADED      = 0x0,      // OLE calls objects on any thread.
  COINIT_DISABLE_OLE1DDE    = 0x4,      // Don't use DDE for Ole1 support.
  COINIT_SPEED_OVER_MEMORY  = 0x8,      // Trade memory for speed.
#endif // DCOM
} COINIT;
```

Whereas in your code COINIT_APARTMENTTHREADED = 1.

----------


## jsvenu

> As i expected the error in the declarations. From ObjBase.h
> 
> 
> ```
> // COM initialization flags; passed to CoInitialize.
> typedef enum tagCOINIT
> {
>   COINIT_APARTMENTTHREADED  = 0x2,      // Apartment model
> 
> ...



Dear Trick,

COINIT_APARTMENTTHREADED  is *already* there in ThreadProc code as already mentioned post #72 as follow:




```
Private Function ThreadProc( _
                 ByVal pParameter As Long) As Long
    Dim cExpSrv     As IUnknown
    Dim bIsInIDE    As Boolean
    Dim tClsId      As tCurGUID
    Dim tIID        As tCurGUID
    Dim tThreadData As tThreadData
    Dim hHeap       As Long
    Dim pNewHeader  As Long
    Dim COINIT_APARTMENTTHREADED As Long
    COINIT_APARTMENTTHREADED = 1
    Debug.Assert MakeTrue(bIsInIDE)
    
    If Not bIsInIDE Then
        Set cExpSrv = CreateIExprSrvObj(0, 4, 0)
    End If
    
    'CoInitialize ByVal 0&
    CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
    hHeap = GetProcessHeap()
    
    TlsSetValue lTlsSlot, ByVal pParameter
    
    GetMem8 ByVal pParameter, tThreadData

    If bIsInIDE Then
        FakeMain
    Else
        
        pNewHeader = CreateVBHeaderCopy()
```

If I use 1 for  COINIT_APARTMENTTHREADED and use *GetMem4 1&, ByVal pHeader + &H3C* in *CreateVBHeaderCopy* I get *crash* when I click on* Create Thread* Button.
* Thankyou very much trick and I understood that I have to use 0 and 2 as value for STA and MTA.Now it works fine.*.





> Eb means - Excel Basic.
> You can use it in the restricted scenarios as i think. The solution depends on the task.


Can you give me a small example of their (EbLoadRunTime, EbCreateContext, EbSetContextWorkerThread ) usage like we use vbdllgetclassobject api using declare.
For example we have
Declare Sub EbLoadRunTime Lib "msvbvm60" (ByVal BaseAddress As Long, ByVal BaseInit As Long)

But how to use EbLoadRunTime for runtime loading and EbCreateContext for creating context and setting context of any thread using EbSetContextWorkerThread .

regards,
JSVenu

----------


## The trick

> Can you give me a small example of their (EbLoadRunTime, EbCreateContext, EbSetContextWorkerThread ) usage like we use vbdllgetclassobject api using declare.
> For example we have
> Declare Sub EbLoadRunTime Lib "msvbvm60" (ByVal BaseAddress As Long, ByVal BaseInit As Long)
> 
> But how to use EbLoadRunTime for runtime loading and EbCreateContext for creating context and setting context of any thread using EbSetContextWorkerThread .


I can’t come up with a practical situation when this can come in handy.

----------


## jsvenu

> I cant come up with a practical situation when this can come in handy.


Dear Trick,

Practically in the  link http://www.devx.com/vb2themax/CodeDownload/19804

In MULTITHREAD.Asm the following code is there which uses the above API and this generates MultiT.dll.This dll is in turn used for demonstrating various threading examples.




```
    .586                      ; force 32 bit code
      .model flat, stdcall      ; memory model & calling convention
      option casemap :none      ; case sensitive

include MULTITHREAD.INC

ExitProc PROTO 
EnterProc PROTO :DWORD

.data



Ct dd 0
Guids dd 0,0,0,0

MSVB db "msvbvm60.dll",0
LDLIB db "EbLoadRunTime",0
CCLIB db "EbCreateContext",0
SCLIB db "EbSetContextWorkerThread",0
LDPROC dd 0,0,0

.code





;Param1=AddressToCall,Param2=StackSize,Param3=
CreateMT proc Param1:DWORD,Param2:DWORD,Param3:DWORD,Param4:DWORD,Param5:DWORD,Param6:DWORD,Param7:DWORD

assume FS: nothing

sub esp,4
push esi
push edi
push ecx
mov eax,DWORD PTR fs:[018h]
add eax,0e14h
mov Ct,eax
lea esi,[ebp+014h]
mov edi,OFFSET Guids
cld
mov ecx,4
rep movsd

invoke CreateEvent,NULL,0,0,Param6
mov DWORD PTR [ebp-4],eax
invoke CreateThread,0,Param2,EnterProc,Param1,0,Param3
push eax
invoke WaitForSingleObject,DWORD PTR [ebp-4],0FFFFFFFFh
invoke CloseHandle,DWORD PTR [EBP-4]
pop eax
pop ecx
pop edi
pop esi
ret

CreateMT endp


EnterProc proc Param1:DWORD
assume FS: nothing	

sub esp,4

push Guids+0ch
push FS:[0]
mov FS:[0],esp


mov edi,DWORD PTR fs:[018h]
add edi,0e14h
mov esi,Ct
cld
mov ecx,1
rep movsd


invoke CoInitialize,0
invoke OleInitialize,0

lea eax,[ebp-4]
;push eax
invoke CoCreateInstance,Guids,0,017h,Guids+4,eax


invoke OpenEvent,01f0003h,0,Guids+8
invoke SetEvent,eax

call Param1


invoke CoUninitialize
invoke OleUninitialize
invoke ExitThread,0



	ret

EnterProc endp

EnableEvents proc

invoke TlsGetValue,3
mov eax,DWORD PTR [eax+028h] ;GET CX
lea ebx,[eax+04ch]

push DWORD PTR [ebx]
push ebx
call LDPROC+4 ;EbCreateContext
call LDPROC+8 ;EbSetContextWorkerThread


;INIT RUNTIME!
mov eax,040003ch
mov eax,DWORD PTR [eax]
lea eax,[eax+0400000h]
mov eax,DWORD PTR [eax+028h]
lea eax,[eax+0400001h]
mov eax,DWORD PTR [eax]
add eax,030h
push DWORD PTR[eax]
push 0400000h
call LDPROC

ret
EnableEvents endp




DllEntry proc hInstance:DWORD, reason:DWORD, reserved1:DWORD
sub esp,4
invoke GetModuleHandle,addr MSVB
mov DWORD PTR [ebp-4],eax
invoke GetProcAddress,eax,addr LDLIB
mov LDPROC,eax
invoke GetProcAddress,DWORD PTR [ebp-4],addr CCLIB
mov LDPROC+4,eax
invoke GetProcAddress,DWORD PTR [ebp-4],addr SCLIB
mov LDPROC+8,eax
mov  eax,1
ret
DllEntry Endp

end DllEntry
```

regards,

JSVenu

----------


## xiaoyao

> Dear Trick,
> 
> Practically in the  link http://www.devx.com/vb2themax/CodeDownload/19804
> 
> In MULTITHREAD.Asm the following code is there which uses the above API and this generates MultiT.dll.This dll is in turn used for demonstrating various threading examples.
> 
> 
> 
> 
> ...


 i test the project :VB6Multithread\StandardThread, it's not support vb6 form object
it's only support msgbox or some api?
It could be an example of incomplete functionality


 (like 


```
Public Sub Thread()
MsgBox "22New Thread Id:" & GetCurrentThreadId, , "Info"
'On Error GoTo err
Dim f As Form2
Set f = New Form2 
f.Show 1 'can't support 

Dim f3 As Form3
Set f3 = New Form3
f3.Show 1

Exit Sub
err:
MsgBox "err:" & err.Number & "," & err.Description
End Sub
```

----------


## xiaoyao

```
 
   ' push  tlsIndex
    ' call  TLSGetValue
    ' pop   ecx
    ' push  DWORD [eax]
    ' push  ecx
    ' jmp   DWORD [eax + 4]
    
    GetMem4 &H68, ByVal ptr + &H0:          GetMem4 &HE800, ByVal ptr + &H4
    GetMem4 &HFF590000, ByVal ptr + &H8:     GetMem4 &H60FF5130, ByVal ptr + &HC
    GetMem4 &H4, ByVal ptr + &H10:   GetMem4 tlsIndex, ByVal ptr + 1\
    GetMem4 lpProc - ptr - 10, ByVal ptr + 6

PublpNewHdr = HeapAlloc(hHeap, 0, &H6A)

 TlsSetValue tlsIndex, lpParameter 'question 1:how to delete this line?

 VBDllGetClassObject VarPtrHModule, 0, PublpNewHdr, clsid, Iid, 0
```

how can't delete tls?
i don't want use tls api.
i want use :


```
Sub ThreadTest(ByVal lpParameter As Long)
    CreateIExprSrvObj 0&, 4&, 0&            'VB6运行库初始化
    CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
    Call VBDllGetClassObject(VarPtrHModule, 0, lpVBHeader , clsid, Iid, 0)

    msgbox "go"
    Dim frm As Form
    Set frm = New Form1 'with usercontrol (object)
    frm.Show 1
end sub

'call method:
CreateThread ByVal 0&, ByVal 0&, AddressOf ThreadTest, ByVal 321&, 0&, 0&


'it's run ok,support(msgbox ,ocx),bug not support usercontrol.
'question 2:how to fix to support usercontrol without tls api?
```

if “createthread （**） api ,no need HeapAlloc,no need  TlsSetValue (no need tls api),i think It's going to be easier

----------


## xiaoyao

Is it necessary for CreateThread to wait for the result to be returned?

In general, you pass a parameter or a structure with multiple parameters. What if you wait for the result to be returned?

What happens if you want to call a procedure with multiple parameters?

CreateThread addressof sum

Public function sum(byval a as long,byval b as long)
Sum = a + b
End the function

Can we only use assembly or add another process package for processing? Can't call directly?

CreateThread creates thread A, where thread A "creates thread B" to handle another large computation loop (with less CPU usage, such as A long wait to download A web page), and then waits for the results to be returned.

This way, whether can use CPU more fully.

Let's say you have a 6 core CPU, you have 6 threads. If you create a child thread in a thread, will the CPU automatically choose a spare core to assign to the new thread?
===\-\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

chinese:

```
 CreateThread是否有必要等待返回结果？
一般情况是传递一个参数或者一个包含多个参数的结构，如果要等返回结果怎么做？
如果要调用多个参数的过程怎么处理？
CreateThread addressof sum
public function sum(byval a as long,byval b as long)
sum=a+b
end function
是不是只能用汇编或者另外加一个过程包装进行处理？没办法直接调用？

CreateThread创建了一个线程A，在线程A再新建线程B"处理 另一个大计算循环（CPU用的不多，像是下载网页，需要等待较长时间），然后等待返回结果。
这种方式，是否可以更加充分的利用CPU。
比如6核CPU，建了6个多线程。再线程中创建子线程，CPU是否会自动选择一个比较空闲的核心分配给新线程？
```

----------


## The trick

xiaoyao, it's difficult to understand you, sorry. What do you want? Do you ask something or?

----------


## jsvenu

> i test the project :VB6Multithread\StandardThread, it's not support vb6 form object
> it's only support msgbox or some api?
> It could be an example of incomplete functionality
> 
> 
>  (like 
> 
> 
> ```
> ...


Dear Trick,

 You stated 



> There is ability to write a MTA ActiveX dll in VB6 but it’s such a nontrivial task (and *rarely used*)


The answer is the description in the link  http://www.devx.com/vb2themax/CodeDownload/19804 which  I have already sent you as follows:

VB6 does not natively support free threading(MTA ActiveX dll), but with this ActiveX library you can finally create multiple threads and *make them communicate*, *write delegate functions* (similarly to VB.NET) and use *Structured Exception Handling*. 

The above underlined bold things can be achieved thru writing a MTA ActiveX dll in VB6 *which are already being used in VB.NET or from VB7 practically*.

May be as xiaoyao stated the code in  http://www.devx.com/vb2themax/CodeDownload/19804  may not be stable or incomplete since it was tried in 2003
and it is glad to know that you how to do it.Please clarify how to write MTA ActiveX dll  in pure vb6.

regards,
JSVenu

----------


## xiaoyao

PublpNewHdr = HeapAlloc(hHeap, 0, &H6A)

CopyMemory ByVal PublpNewHdr, ByVal lpVBHeader, &H6A
GetMem4 1&, ByVal PublpNewHdr + &H3C  '加的，支持自定义控件
why use &h6A?&H3C?
WHEN I USE THESE PublpNewHdr to other vb crearethread ptoject,Without using any of your other code. When the msgbox window pops up, the title becomes garbled, and the process is different each time it is opened, and it is the same before it is closed. .
chinese:没有使用你的任何其他代码。msgbox弹出窗口的时候标题变成乱码，每次打开进程都不一样，没有关闭之前都一样。
maybe app.title happened err,vb header Is it not copy fully?

 ' TlsSetValue tlsIndex, lpParameter 'question 1: I delete this line

Sub ThreadTest(ByVal lpParameter As Long)
    CreateIExprSrvObj 0&, 4&, 0&            'VB6运行库初始化
    CoInitializeEx ByVal 0&, ByVal COINIT_APARTMENTTHREADED
    Call VBDllGetClassObject(VarPtrHModule, 0, PublpNewHdr, , clsid, Iid, 0)

    msgbox "go"
    Dim frm As Form
    Set frm = New Form1 'with usercontrol (object)
    frm.Show 1
end sub

'call method:
CreateThread ByVal 0&, ByVal 0&, AddressOf ThreadTest, ByVal 321&, 0&, 0&

----------


## jsvenu

> PublpNewHdr = HeapAlloc(hHeap, 0, &H6A)
> 
> CopyMemory ByVal PublpNewHdr, ByVal lpVBHeader, &H6A
> GetMem4 1&, ByVal PublpNewHdr + &H3C  '加的，支持自定义控件
> why use &h6A?&H3C?
> WHEN I USE THESE PublpNewHdr to other vb crearethread ptoject,Without using any of your other code. When the msgbox window pops up, the title becomes garbled, and the process is different each time it is opened, and it is the same before it is closed. .
> chinese:没有使用你的任何其他代码。msgbox弹出窗口的时候标题变成乱码，每次打开进程都不一样，没有关闭之前都一样。
> maybe app.title happened err,vb header Is it not copy fully?
> 
> ...


Dear xiaoyao,

 &h6A,&H3C are the offsets of the corresponding fields in vbheader structure on which *Trick* has worked a lot.
 You can use the code by Trick as it is since creating and copying new vbheader without making the global/static variable from being *cleared* involves 
 lot of internals to be understood and it is the *Trick* who did all the *great work* for which I *really appreciate* him.
You can get required details of the vb6 internal structures which are linked to one another in the link https://docplayer.net/83403902-Relso...re-format.html..

regards,
JSVenu

----------


## xiaoyao

> Dear xiaoyao,
> 
>  &h6A,&H3C are the offsets of the corresponding fields in vbheader structure on which *Trick* has worked a lot.
>  You can use the code by Trick as it is since creating and copying new vbheader without making the global/static variable from being *cleared* involves 
>  lot of internals to be understood and it is the *Trick* who did all the *great work* for which I *really appreciate* him.
> You can get required details of the vb6 internal structures which are linked to one another in the attachment.
> 
> regards,
> JSVenu


The simplest VB6 multithreading, and support for custom controls, is quite perfect. I spent a lot of time on multithreading research and was laughed at or didn't understand by many Chinese developers. VB6 itself does not support multithreading, and some examples are hundreds of thousands of lines of code that are too complex to understand.

In fact, most people only need the simplest multithreading, no form, no OCX control, haha.

As Chinese, we are difficult. Now the forum of VB6 is almost dead, many people are using "E language "invented by Chinese people.

The Google website is blocked, unable to access, unable to search the source code, open your forum is also very slow, several hundred times slower than the Chinese website.

CHINESE:最简单的VB6多线程，并且支持自定义控件，那是相当完美。在多线程研究上我花了大量时间，被很多中国的程序开发员耻笑或者不理解。VB6本身不支持多线程，有的例子代码几百几千行，而且非常复杂  ，难以看懂。
其实大部分人只需要最简单的多线程，用不着窗体，也用不着OCX控件，哈哈。
作为中国人，我们很难。现在VB6的论坛几乎全死了，很多人在用中国人发明的"E语言"。
谷歌网站被封锁无法访问，无法搜索源码，打开你们的论坛也是非常慢，比中国的网站慢了几百上千倍。

----------


## xiaoyao

---------------------------
,\H
---------------------------
PubValue=321

UsedTime Log(microseconds)：
CreateThread：99.271
CoInitializeEx：15.6444
VBDllGetClassObject：5528.4559
AllUsedTime：5643.3714microseconds

GetCurrentThreadId=16096,title=prjTest1
---------------------------
公共变量=321
以下时间单位微秒：
创建线程用时：115.1999
加载 COM用时：15.6444
vb初始化用时：5054.2876
线程准备总共用时：5185.1319微秒
GetCurrentThreadId=9308,title=prjTest1
---------------------------
确定
=======================
VBDllGetClassObject this api ,used the most times(5.1 ms)

PublpNewHdr2 = HeapAlloc(hHeap, 0, &H6A)' the msgbox window title is err.
PublpNewHdr2 = HeapAlloc(hHeap, 0, &H6A*10 ) is good

What is the best value? If you want to copy all the VB headers in, what length do you have?

----------


## jsvenu

> ---------------------------
> ,\H
> ---------------------------
> PubValue=321
> 
> UsedTime Log(microseconds)：
> CreateThread：99.271
> CoInitializeEx：15.6444
> VBDllGetClassObject：5528.4559
> ...


Dear xiaoyao,

Have you gone thru the link    *https://docplayer.net/83403902-Relso...re-format.html* which contains 12 pages containing all vb internal format structures.Then you can understand better.

regards,
JSVenu

----------


## xiaoyao

> Dear xiaoyao,
> 
> Have you gone thru the link    *https://docplayer.net/83403902-Relso...re-format.html* which contains 12 pages containing all vb internal format structures.Then you can understand better.
> 
> regards,
> JSVenu


PublpNewHdr2 = HeapAlloc(hHeap, 0, &H6A * 10)

I can't read it. My English is so poor.
Now I'm just making this number 10 times bigger, how much is the best number to put in?
can you help me?thank you!!!!

chinese:我看不懂，我的英文水平太差了。
现在我只是把这个数字放大了10倍，到底应该输多少数值最佳？

----------


## The trick

JSVenu, as i already told you can't use free threading as usual. The library you showed seems has some bugs. Just i tried to execute the *StandardThread* example and i see it doesn't performs the context initialization. You can check it by adding an user class and then try to create an instance as follows:


```
Public Sub Thread()
    MsgBox "New Thread Id:" & GetCurrentThreadId, , "Info"

    Dim Z As Class1
    
    Set Z = New Class1
    
    Z.XXX
    
End Sub
```

Z.XXX won't be called because the context isn't initialized you don't see an error because it setups a SEH handler. Just you can see the error of access violation after the context was not found:


Moreover this example creates the memory leak because it creates an instance of *VBFreeThreading.FreeThreadInterface* but doesn't performs resource cleaning like *IUnknown::Release*.

To write a MTA library you need to consider many restrictions.

----------


## jsvenu

> JSVenu, as i already told you can't use free threading as usual. The library you showed seems has some bugs. Just i tried to execute the *StandardThread* example and i see it doesn't performs the context initialization. You can check it by adding an user class and then try to create an instance as follows:
> 
> 
> ```
> Public Sub Thread()
>     MsgBox "New Thread Id:" & GetCurrentThreadId, , "Info"
> 
>     Dim Z As Class1
>     
> ...


Dear Trick,

First thankyou for the reply.
Sincerely I can say that I have seen link but I did not execute it since I already told I had less idea of ASM.So I was thinking that we can do free threading when I understand it in pure vb6 from some expert like you,Xiaoyao and yourself have gone thru it and have given your comments.Slowly I could understand multithreading in vb6 in a better way  from you.
Can you please provide me a *simple* MTA library in vb6 considering the  restrictions to follow as you said.

regards,
JSVenu

----------


## jsvenu

> PublpNewHdr2 = HeapAlloc(hHeap, 0, &H6A * 10)
> 
> I can't read it. My English is so poor.
> Now I'm just making this number 10 times bigger, how much is the best number to put in?
> can you help me?thank you!!!!
> 
> chinese:æçä¸æï¼æçè±ææ°´å¹³å¤ªå·®äºã
> ç°å¨æåªæ¯æè¿ä¸ªæ°å*æ¾å¤§äº10åï¼å°åºåºè¯¥è¾å¤å°æ°å¼æä½³ï¼


Dear xiaoyao,

Just put msgbox in *CreateVBHeaderCopy* module function as follows:

' // Create copy of VBHeader and other structures
' // The first four bytes contain thread ID. We use that ID to clean unused headers


```
Private Function CreateVBHeaderCopy() As Long
    Dim pHeader         As Long
    Dim pOldProjInfo    As Long
    Dim pProjInfo       As Long
    Dim pObjTable       As Long
    Dim pOldObjTable    As Long
    Dim lDifference     As Long
    Dim lIndex          As Long
    Dim pNames(3)       As Long
    Dim lModulesCount   As Long
    Dim pDescriptors    As Long
    Dim pOldDesc        As Long
    Dim pVarBlock       As Long
    Dim lSizeOfHeaders  As Long
    
    ' // Get size of all headers
    lSizeOfHeaders = &H6A + &H23C + &H54 + &HC + 4
    
    GetMem4 ByVal pVBHeader + &H30, pOldProjInfo
    GetMem4 ByVal pOldProjInfo + &H4, pOldObjTable
    GetMem4 ByVal pOldObjTable + &H30, pOldDesc
    GetMem2 ByVal pOldObjTable + &H2A, lModulesCount
    MsgBox "lModulesCount  " & lModulesCount
    lSizeOfHeaders = lSizeOfHeaders + &H30 * lModulesCount
    MsgBox "lSizeOfHeaders  " & lSizeOfHeaders
 ' // Allocate memory for header
    Do
        
        pHeader = HeapAlloc(hHeadersHeap, HEAP_ZERO_MEMORY, lSizeOfHeaders)
```

regards,
JSVenu

----------


## jsvenu

> Dear xiaoyao,
> 
> Just put msgbox in *CreateVBHeaderCopy* module function as follows:
> 
> ' // Create copy of VBHeader and other structures
> ' // The first four bytes contain thread ID. We use that ID to clean unused headers
> 
> 
> ```
> ...


Dear xiaoyao,

The following are the values at runtime in *CreateVBHeaderCopy*  module function for  *lModulesCount * and  *lSizeOfHeaders  


*lModulesCount  *8*
lSizeOfHeaders  *1162* (*48A*)


So we have 

pHeader = HeapAlloc(hHeadersHeap, HEAP_ZERO_MEMORY, lSizeOfHeaders)  as

pHeader = HeapAlloc(hHeadersHeap, HEAP_ZERO_MEMORY, &*H48A*)

Hope this helps you.

regards,
JSVenu

----------


## xiaoyao

mabe it's good,I saw this piece of code earlier, but I don't know what it's for.I'll test it tomorrow.
The best method is still inseparable from the assembly code support. After all, the programming tools are upgraded, and finally they run as assembly code.
Use createthread to directly call the function address, and add vb initialization inside the function process, which is equivalent to the cdecl mode dll, which requires the user to initialize and reclaim memory and unload the com object.
vbcreatethread is a simple wrapper that handles automatically, equivalent to stdcall calls
chinese:最好的方法还是离不开汇编代码支持，毕竟编程工具升级，最后还是变成汇编代码运行。
直接用createthread调用函数地址，函数过程里面加vb初始化，相当于cdecl方式dll,需要用户初始化和回收内存，卸载com对象。
vbcreatethread 是一个简易包装，自动处理，相当于stdcall调用

----------


## xiaoyao

Activate any event in multiple threads or in any other form or class

We can take over the COM object's ConnectionPoint connection pointer,
For example, set up two COM objects in the main form and activate events directly in any multithread.
Or call the "global variable. Object.fireevents("events2",arg1,arg2)" directly in the thread.
Example of Class1:


```
'in form1
Dim WithEvents ClassA AS Class1
Dim WithEvents ClassB AS Class1

ClassA.DoRaiseEventByName "Event3", 11, 22, 33

'in Class1 file:
Public Event Event1(msg As String)
Public Event Event2(Arg1 As String, Arg2 As String)
Public Event Event3(A As Long, B As Long, C As Long)
Public Event Event4(A As Variant, B As Variant, C As Variant, D As Variant)
Public Sub DoRaiseEventByName(EventsName As String, ParamArray Params() As Variant)
Select Case EventsName
Case "Event1"
    RaiseEvent Event1(Params(0) & "")
Case "Event2"
   RaiseEvent Event2(Params(0) & "", Params(1) & "")
Case "Event3"
   RaiseEvent Event3(Params(0) + 0, Params(1) + 0, Params(2) + 0)
Case "Event4"
   RaiseEvent Event4(Params(0), Params(1), Params(2), Params(3))
End Select
End Sub
```

----------------
chinese:
在多线程中或其他窗体或类中任意激活事件
我们可以接管COM对象的ConnectionPoint连接指针，
比如在主窗体中建立2个COM对象,直接在任何多线程中激活事件（FireEvents）
或者在线程中直接调用“全局变量.对象.
Class1的实例:
dim WithEvents ClassA AS Class1
dim WithEvents ClassB AS Class1

----------


## xiaoyao

question 1:how to run createthread  in vb6 ide,for support show form1
i run createthread in vb6 ide,it's successful,but can't show form
 (how to fix?),maybe use not the same "NewVBHeader_Heap"
NewVBHeader_Heap=HeapAlloc(hHeadersHeap, HEAP_ZERO_MEMORY, lSizeOfHeaders) 
we need new lSizeOfHeaders?
or need other way?

for test,it's support com object like "adodb.stream"

question2: how to debug thread in ide?
it's can suspended， how to run on?
use f5 or f8 or can't run
Quest3:if i use "end " in thread,APPCATION WILL BE collapse(ERR),WHY?



```
Dim f As Form1
Set f = New Form1
f.Show 1
VBDllGetClassObject  VarPtr(App.hInstance), 0, VBHeader, 0&, Iid, 0       '
```

VBDllGetClassObject VarPtrHModule, 0, NewVBHeader_Heap, 0&, Iid, 0       '[/CODE]
error from "VBDllGetClassObject":
Signature of question:
Problem event name: APPCRASH
Application name: vb6.exe
Application version: 6.0.97.82
Application timestamp: 403acf6c
Fault module name: msvbvm60. DLL
Fault module version: 6.0.98.15
Fault module timestamp: 49b01fc3
Exception code: c0000005
Exception offset: 000044da
OS version: 6.3.9600.2.0.0.256.4

Locale ID: 2052
Other information 1: ac05
Other information: 2 ac0507478d1c5bd693cfc4fe3987e900
Other information 3: ac05
Other information 4: ac0507478d1c5bd693cfc4fe3987e900
'===========
chinese:问题签名:
  问题事件名称:	APPCRASH
  应用程序名:	VB6.EXE
  应用程序版本:	6.0.97.82
  应用程序时间戳:	403acf6c
  故障模块名称:	MSVBVM60.DLL
  故障模块版本:	6.0.98.15
  故障模块时间戳:	49b01fc3
  异常代码:	c0000005
  异常偏移:	000044da
  OS 版本:	6.3.9600.2.0.0.256.4
  区域设置 ID:	2052
  其他信息 1:	ac05
  其他信息 2:	ac0507478d1c5bd693cfc4fe3987e900
  其他信息 3:	ac05
  其他信息 4:	ac0507478d1c5bd693cfc4fe3987e900

----------


## jsvenu

> question 1:how to run createthread  in vb6 ide,for support show form1
> i run createthread in vb6 ide,it's successful,but can't show form
>  (how to fix?),maybe use not the same "NewVBHeader_Heap"
> NewVBHeader_Heap=HeapAlloc(hHeadersHeap, HEAP_ZERO_MEMORY, lSizeOfHeaders) 
> we need new lSizeOfHeaders?
> or need other way?
> 
> for test,it's support com object like "adodb.stream"
> 
> ...


Dear xiaoyao,

Displaying form *also works* in IDE.But it is displayed in the *main Thread*.
You can check this  by running your *03_Edit-usercontrol_FormGood* project in IDE and click on *Create Thread* button on the displayed form.
You can put break points and debug the project.

regards,
JSVenu

----------


## xiaoyao

I TEST createthread in vb6 ide,need new thread,not run in main thread.(it's only for test this api,no need debug)
if can debug part,like break ,and run on again,Wouldn't that be better？

----------


## jsvenu

> I TEST createthread in vb6 ide,need new thread,not run in main thread.(it's only for test this api,no need debug)
> if can debug part,like break ,and run on again,Wouldn't that be better？


Dear xiaoyao,

I understood that you require attaching to a running application at runtime debug it and then detach from it so that it runs again standalone.
This dynamic runtime attach and detach to a running application are not available in vb6 IDE.They are available in vc++ 6.0 IDE but here also while detaching the application terminates.
If you want to run perfectly you have to use .Net IDE with which you can attach debug and detach and continue run again without problems.*Trick* has also suggested me *ollydbg*.

As an alternative if you want to change control paths in vb6 IDE itself you can use* set next statement* while debugging using break points.
You can also use *DebugBreak* api in vb6.
regards,
JSVenu

----------


## jsvenu

> Dear Trick,
> 
> First thankyou for the reply.
> Sincerely I can say that I have seen link but I did not execute it since I already told I had less idea of ASM.So I was thinking that we can do free threading when I understand it in pure vb6 from some expert like you,Xiaoyao and yourself have gone thru it and have given your comments.Slowly I could understand multithreading in vb6 in a better way  from you.
> Can you please provide me a *simple* MTA library in vb6 considering the  restrictions to follow as you said.
> 
> regards,
> JSVenu


Dear Trick,

I think if  we can have a module function which  can tell whether a thread in which we want to do any work  is initialized in terms of runtime like *isRunTimeinitialized* which returns true or false it can be of help in simple MTA library example.Please clarify.

regards,
JSVenu

----------


## The trick

> I think if we can have a module function which can tell whether a thread in which we want to do any work is initialized in terms of runtime like isRunTimeinitialized which returns true or false it can be of help in simple MTA library example.Please clarify.


Notice to function:


```
' // Init thread and call function
' // This function is useful for callback of WINAPI functions which can call function in arbitrary thread
Public Function InitCurrentThreadAndCallFunction( _
                ByVal pfnCallback As Long, _
                ByVal pParam As Long, _
                ByRef lReturnValue As Long) As Boolean

    .  .  .

    If lTlsSlot Then
    
        ' // Check if thread already initialized
        pThreadData = TlsGetValue(lTlsSlot)
        
        If pThreadData <> 0 Then
            
            ' // Just call by pointer
            hr = DispCallFunc(ByVal 0&, pfnCallback, CC_STDCALL, vbLong, 1, vbLong, VarPtr(CVar(pParam)), vRet)
            
            .  .  .
            
        End If
    
    End If

    .  .  .

End Function
```

*
ADDED:*

You can't write a MTA library using this module. Moreover you can't write MTA library using any other module. To write a MTA library you need to programming only in the procedural style, considers restrictions of uninitialized runtime (like using APIs declared in a typelib). Also you need to ensure manually data synchronization (because you use MTA), manually vTable creation, manually registration, etc.
I can write such library but i don't want because it has no sense. I've already written the kernel driver to prove vb6 can do it. The same with MTA library. Maybe sometime i'll write such library  :Smilie:

----------


## jsvenu

> Notice to function:
> 
> 
> ```
> ' // Init thread and call function
> ' // This function is useful for callback of WINAPI functions which can call function in arbitrary thread
> Public Function InitCurrentThreadAndCallFunction( _
>                 ByVal pfnCallback As Long, _
>                 ByVal pParam As Long, _
> ...


Dear Trick,

    First let me thankyou for the reply.
    you said 




> ' // Check if thread already initialized
>         pThreadData = TlsGetValue(lTlsSlot)


 but this is  in *InitCurrentThreadAndCallFunction* module function.

I was asking in general for example in a *client* application which  *uses* a MTA component which supports callbacks or events in different *threads * there we have to check whether the thread runtime is initialized if we want to write runtime dependant code in the callback otherwise we can proceed in a different way.The client here is using components and not calling *InitCurrentThreadAndCallFunction*.In order to call this *InitCurrentThreadAndCallFunction* first it should know initialization is required or not using the boolean module function which I asked for.

regards,
JSVenu

----------


## xiaoyao

> Dear xiaoyao,
> 
> I understood that you require attaching to a running application at runtime debug it and then detach from it so that it runs again standalone.
> This dynamic runtime attach and detach to a running application are not available in vb6 IDE.They are available in vc++ 6.0 IDE but here also while detaching the application terminates.
> If you want to run perfectly you have to use .Net IDE with which you can attach debug and detach and continue run again without problems.*Trick* has also suggested me *ollydbg*.
> 
> As an alternative if you want to change control paths in vb6 IDE itself you can use* set next statement* while debugging using break points.
> You can also use *DebugBreak* api in vb6.
> regards,
> JSVenu


if i open 4 Foms in thread,i saved the formobject to Array "Public FormArr(1 to 4) As Form"
when "DLL_THREAD_ATTACH",i close these windows,why i close formarr(4),it's crash?
close  window 1-3 is very good.

I create multiple threads in the DLL file, each with multiple forms.

Is there any way to close all form objects when the DLL exits?

How about this method? Send handle WM_CLOSE? Then loop through all window handles until they all fail.

What's a safe way to end a thread without causing a memory leak?

chinese:我在DLL文件里创建多线程，每个线程有多个窗体。
有什么办法可以在DLL要退出时，直接关闭所有窗体对象？
这个方法怎么样？发送句柄WM_CLOSE?然后循环检测所有窗口句柄，直到全部失效为止？
有什么安全的方法结束线程，又不会造成内存泄露？

----------


## The trick

xiaoyao
Please attach the example i don't understand what you tell about. If you want to use a DLL with a threading-scenario the simplest way is to just create an ActiveX DLL with the apartment threading model.




> I was asking in general for example in a client application which uses a MTA component which supports callbacks or events in different threads there we have to check whether the thread runtime is initialized if we want to write runtime dependant code in the callback otherwise we can proceed in a different way.The client here is using components and not calling InitCurrentThreadAndCallFunction.In order to call this InitCurrentThreadAndCallFunction first it should know initialization is required or not using the boolean module function which I asked for.


This function is intended for such cases. When you receive a callback you call this function which initializes the runtime and calls your callback function in the initialized thread. If the thread already initialized the function just calls method without initialization.

----------


## jsvenu

Dear Trick,




> This function is intended for such cases. When you receive a callback you call this function which initializes the runtime and calls your callback function in the initialized thread. If the thread already initialized the function just calls method without initialization.


Thankyou for the clarification.




> manually data synchronization,manually vTable creation, manually registration etc


I understand why manually data synchronization but why are *manually vTable creation, manually registration etc* required for MTA library.



In std exe project  for loading  user control of same project you told we have to set the new thread in which new control is loaded to  *apartment model*.When we set we were able to access the user control thru the thread.
But here* I don't understand why the main default thread not being apartment model is able to load the user control to the form in main default thread*.*Moreover when I try to make the main thread of the project to be apartment model as follows it crashes*.Please clarify.




```
' // Get VBHeader structure
Private Function GetVBHeader() As Long
    Dim ptr     As Long
    Dim bThreading As Long
    Dim tempheader As Long
    ' // Get e_lfanew
    GetMem4 ByVal hModule + &H3C, ptr
    ' // Get AddressOfEntryPoint
    GetMem4 ByVal ptr + &H28 + hModule, ptr
    ' // Get VBHeader
    GetMem4 ByVal ptr + hModule + 1, tempheader
    GetMem1 ByVal tempheader + &H3C, bThreading
    bThreading = bThreading Or 1
 
    GetMem1 bThreading, ByVal tempheader + &H3C 'crashes
  
    GetVBHeader = tempheader
End Function
```

regards,
JSVenu

----------


## xiaoyao

> xiaoyao
> Please attach the example i don't understand what you tell about. If you want to use a DLL with a threading-scenario the simplest way is to just create an ActiveX DLL with the apartment threading model.
> This function is intended for such cases. When you receive a callback you call this function which initializes the runtime and calls your callback function in the initialized thread. If the thread already initialized the function just calls method without initialization.


IT'S OK ,THANK YOU.

Save the form objects created with multiple threads in the VB standard DLL in an array and close them when the main thread exits. Closing the last thread causes the form to crash.

It crashed hundreds, thousands of times, and finally it's done, just save the handle to the form, and then sendmessage wm_close so it doesn't crash. Form objects in other threads access department is not safe, will cause some unknown reasons, can only see the assembly code to find out.

Or, alternatively, enumerate all the forms in the process and list the modules for that window.
CHINESE:
把VB标准DLL里用多线程创建的窗体对象保存在数组里，在主线程退出时关闭他们。关闭最后一个线程窗体就会崩溃。
崩溃了几百上千次，终于搞定了，只是保存窗体的句柄，然后sendmessage wm_close这样就不会崩溃了。窗体对象在其他线程中访部不安全，会造成一些未知原因，只能看汇编代码才能查清楚。
或者别外的方法，把该进程中的所有窗体枚举出来，列出该窗口对应的模块如果是这个DLL，就关闭。

----------


## xiaoyao

About what you said: my goal is to create threads and allow them to call back to any address, whether it's in bas, forms, classes, usercontrol, etc. It seems to work.

I just thought of it. You've been doing it for years.

Not only multithreading, but also asynchronous processing, which is nice
chinese:关于你说的：我的目标是创建线程，并允许它们回调到任何地址，无论它是在bas，窗体，类，usercontrol等中。这似乎起作用。
我刚刚才想起这方法，你都实现很多年了。
不仅需要多线程，还需要异步处理，真好

----------


## yokesee

I use this class to capture the events.
I use the example that made me trick multithreading and works well with objects.
if you find it useful
http://www.vbforums.com/showthread.p...=1#post5445811



```
Option Explicit

' clsObjectExtender
'
' event support for Late-Bound objects
' low level COM Projekt - by [rm_code] 2005

'
' HOW IT WORKS
'
' An object, wich supports events,
' should implement IConnectionPointContainer.
' This interface let's you enumerate all
' event interfaces.
' Once you have the event interface you want,
' you can get IConnectionPoint for this interface,
' advise your event sink to it, and start
' recieving events on it.
' If the event interface has IDispatch implemented,
' all raised events will go through
' IDispatch::Invoke.
'
' And that's what we use:
' We just take the first best event interface we
' get from IEnumConnectionPoints, create a
' event sink which implements IDispatch,
' and capture all events through Invoke().
'

Private Type IUnknown
    QueryInterface              As Long
    AddRef                      As Long
    Release                     As Long
End Type

Private Type IDispatch
    IUnk                        As IUnknown
    GetTypeInfoCount            As Long
    GetTypeInfo                 As Long
    GetIDsOfNames               As Long
    Invoke                      As Long
End Type

Private Type IConnectionPointContainer
    IUnk                        As IUnknown
    EnumConnectionPoints        As Long
    FindConnectionPoint         As Long
End Type

Private Type IConnectionPoint
    IUnk                        As IUnknown
    GetConnectionInterface      As Long
    GetConnectionPointContainer As Long
    Advise                      As Long
    Unadvise                    As Long
    EnumConnections             As Long
End Type

Private Type IEnumConnectionPoints
    IUnk                        As IUnknown
    Next                        As Long
    Skip                        As Long
    Reset                       As Long
    Clone                       As Long
End Type

Private Type ITypeInfo
    IUnk                        As IUnknown
    GetTypeAttr                 As Long
    GetTypeComp                 As Long
    GetFuncDesc                 As Long
    GetVarDesc                  As Long
    GetNames                    As Long
    GetRefTypeOfImplType        As Long
    GetImplTypeFlags            As Long
    GetIDsOfNames               As Long
    Invoke                      As Long
    GetDocumentation            As Long
    GetDllEntry                 As Long
    GetRefTypeInfo              As Long
    AddressOfMember             As Long
    CreateInstance              As Long
    GetMops                     As Long
    GetContainingTypeLib        As Long
    ReleaseTypeAttr             As Long
    ReleaseFuncDesc             As Long
    ReleaseVarDesc              As Long
End Type

Private Type ITypeLib
    IUnk                        As IUnknown
    GetTypeInfoCount            As Long
    GetTypeInfo                 As Long
    GetTypeInfoType             As Long
    GetTypeInfoOfGuid           As Long
    GetLibAttr                  As Long
    GetTypeComp                 As Long
    GetDocumentation            As Long
    IsName                      As Long
    FindName                    As Long
    ReleaseTLibAttr             As Long
End Type

Private Type DISPPARAMS
    rgPointerToVariantArray     As Long
    rgPointerToLONGNamedArgs    As Long
    cArgs                       As Long
    cNamedArgs                  As Long
End Type

Private Type SAFEARRAYBOUND
    cElements                   As Long
    lLBound                     As Long
End Type

Private Type SAFEARRAY_1D
   cDims                        As Integer
   fFeatures                    As Integer
   cbElements                   As Long
   cLocks                       As Long
   pvData                       As Long
   Bounds(0 To 0)               As SAFEARRAYBOUND
End Type

Private Enum VARENUM
    VT_EMPTY = 0
    VT_NULL = 1
    VT_I2 = 2
    VT_I4 = 3
    VT_R4 = 4
    VT_R8 = 5
    VT_CY = 6
    VT_DATE = 7
    VT_BSTR = 8
    VT_DISPATCH = 9
    VT_ERROR = 10
    VT_BOOL = 11
    VT_VARIANT = 12
    VT_UNKNOWN = 13
    VT_DECIMAL = 14
    VT_I1 = 16
    VT_UI1 = 17
    VT_UI2 = 18
    VT_UI4 = 19
    VT_I8 = 20
    VT_UI8 = 21
    VT_INT = 22
    VT_UINT = 23
    VT_VOID = 24
    VT_HRESULT = 25
    VT_PTR = 26
    VT_SAFEARRAY = 27
    VT_CARRAY = 28
    VT_USERDEFINED = 29
    VT_LPSTR = 30
    VT_LPWSTR = 31
    VT_FILETIME = 64
    VT_BLOB = 65
    VT_STREAM = 66
    VT_STORAGE = 67
    VT_STREAMED_OBJECT = 68
    VT_STORED_OBJECT = 69
    VT_BLOB_OBJECT = 70
    VT_CF = 71
    VT_CLSID = 72
    VT_STREAMED_PROPSET = 73
    VT_STORED_PROPSET = 74
    VT_BLOB_PROPSET = 75
    VT_VERBOSE_ENUM = 76
    VT_BSTR_BLOB = &HFFF
    VT_VECTOR = &H1000
    VT_ARRAY = &H2000
    VT_BYREF = &H4000
    VT_RESERVED = &H8000
    VT_ILLEGAL = &HFFFF
    VT_ILLEGALMASKED = &HFFF
    VT_TYPEMASK = &HFFF
End Enum

Public Event EventRaised(ByVal strName As String, params() As Variant)

Private oCPC        As IConnectionPointContainer
Private pCPC        As Long
Private pVTblCPC    As Long

Private oECP        As IEnumConnectionPoints
Private pECP        As Long
Private pVTblECP    As Long

Private oCP         As IConnectionPoint
Private pCP         As Long
Private pVTblCP     As Long

Private oUnk        As IUnknown
Private pUnk        As Long
Private pVTblUnk    As Long

Private oTarget     As Object
Private pTarget     As Long
Private pVTblTarget As Long

Private oSink       As Object
Private dwCookie    As Long

Private iid_event   As UUID

Private blnAttached As Boolean

' by Edanmo
Private Sub pvSetParamArray(ByVal ptr As Long, aParams() As Variant)
    Dim tDPAR       As DISPPARAMS
    Dim SafeArray   As SAFEARRAY_1D
    Dim iVarType    As Integer
    Dim lIdx        As Long
    Dim lPtr        As Long
    Dim lVarPtr     As Long

    ' DISPPARAMS structure
    CpyMem tDPAR, ByVal ptr, Len(tDPAR)
    If tDPAR.cArgs = 0 Then Exit Sub

    ' pointer to the first variant
    lVarPtr = tDPAR.rgPointerToVariantArray

    For lIdx = 0 To tDPAR.cArgs - 1

        ' get the variant's type
        CpyMem iVarType, ByVal lVarPtr + (lIdx * 16&), 2&

        If (iVarType And VT_BYREF) = VT_BYREF Then

            ' ByRef Parameter

            ' get the pointer
            CpyMem lPtr, ByVal lVarPtr + (lIdx * 16&) + 8&, 4&

            Select Case iVarType And VT_TYPEMASK
                Case vbString

                    Dim lStrPtr As Long
                    ' String Pointer
                    CpyMem lStrPtr, ByVal lPtr, 4&

                    If lStrPtr = 0 Then
                        ' new string
                        lStrPtr = SysAllocStringPtr(StrPtr(aParams(tDPAR.cArgs - lIdx)))
                    Else
                        ' reallocate the string
                        lStrPtr = SysReAllocString(lStrPtr, StrPtr(aParams(tDPAR.cArgs - lIdx)))
                    End If

                    ' copy the string pointer
                    CpyMem ByVal lPtr, lStrPtr, 4&

                Case vbInteger
                    CpyMem ByVal lPtr, CInt(aParams(tDPAR.cArgs - lIdx)), 2&

                Case vbBoolean
                    CpyMem ByVal lPtr, CBool(aParams(tDPAR.cArgs - lIdx)), 2&

                Case vbInteger
                    CpyMem ByVal lPtr, CInt(aParams(tDPAR.cArgs - lIdx)), 2&

                Case vbLong
                    CpyMem ByVal lPtr, CLng(aParams(tDPAR.cArgs - lIdx)), 4&

                Case vbSingle
                    CpyMem ByVal lPtr, CSng(aParams(tDPAR.cArgs - lIdx)), 4&

                Case vbDouble
                    CpyMem ByVal lPtr, CDbl(aParams(tDPAR.cArgs - lIdx)), 8&

                Case vbDate
                    CpyMem ByVal lPtr, CDate(aParams(tDPAR.cArgs - lIdx)), 8&

                Case vbByte
                    CpyMem ByVal lPtr, CByte(aParams(tDPAR.cArgs - lIdx)), 1&

                Case vbCurrency
                    CpyMem ByVal lPtr, CCur(aParams(tDPAR.cArgs - lIdx)), 8&

                Case vbVariant, vbDecimal
                    VariantCopyIndPtr lPtr, VarPtr(aParams(tDPAR.cArgs - lIdx))

            End Select

        End If

    Next

End Sub

' by Edanmo
Private Function pvGetParamArray(ByVal ptr As Long) As Variant()
    Dim tDPAR           As DISPPARAMS
    Dim SafeArray       As SAFEARRAY_1D
    Dim aTmpParams()    As Variant
    Dim aParams()       As Variant
    Dim lIdx            As Long

    ' DISPPARAMS structure
    CpyMem tDPAR, ByVal ptr, Len(tDPAR)
    If tDPAR.cArgs = 0 Then Exit Function

    ' array pointing to the param array
    With SafeArray
        .Bounds(0).cElements = tDPAR.cArgs
        .Bounds(0).lLBound = 0
        .cDims = 1
        .cbElements = 16
        .pvData = tDPAR.rgPointerToVariantArray
    End With
    CpyMem ByVal VarPtrArray(aTmpParams), VarPtr(SafeArray), 4&

    ' copy elements
    ReDim aParams(1 To tDPAR.cArgs)
    For lIdx = 1 To tDPAR.cArgs
        aParams(lIdx) = aTmpParams(tDPAR.cArgs - lIdx)
    Next

    ' return the parameters
    pvGetParamArray = aParams

    ' destroy the array
    CpyMem ByVal VarPtrArray(aTmpParams), 0&, 4&
End Function

Private Function GetMemberName(obj As Object, ByVal dispid As Long, iid As UUID) As String
    Dim oTypeLib    As ITypeLib
    Dim pTypeLib    As Long
    Dim pVTblTpLib  As Long

    Dim oTypeInfo   As ITypeInfo
    Dim pTypeInfo   As Long
    Dim pVTblTpInfo As Long

    Dim oDispatch   As IDispatch
    Dim hRet        As Long
    Dim dwIndex     As Long
    Dim pcNames     As Long
    Dim pVTbl       As Long

    Dim strName     As String

    ' get IDispatch from the object
    pVTbl = ObjPtr(obj)
    CpyMem pVTbl, ByVal pVTbl, 4
    CpyMem oDispatch, ByVal pVTbl, Len(oDispatch)

    ' get ITypeInfo
    hRet = CallPointer(oDispatch.GetTypeInfo, ObjPtr(obj), 0, lcid, VarPtr(pTypeInfo))
    If hRet Then Exit Function

    ' ITypeInfo VTable
    CpyMem pVTblTpInfo, ByVal pTypeInfo, 4
    CpyMem oTypeInfo, ByVal pVTblTpInfo, Len(oTypeInfo)

    ' let's first ty to get the name
    ' of the member by using the current TypeInfo
    hRet = CallPointer(oTypeInfo.GetNames, pTypeInfo, dispid, VarPtr(strName), 1, VarPtr(pcNames))
    If Len(strName) > 0 Then
        GetMemberName = strName
        Exit Function
    End If

    ' no, that didn't work.
    ' go for the whole type library

    ' GetContainingTypeLib
    hRet = CallPointer(oTypeInfo.GetContainingTypeLib, pTypeInfo, VarPtr(pTypeLib), VarPtr(dwIndex))
    If hRet Then
        GetMemberName = dispid
        Exit Function
    End If

    ' ITypeLib VTable
    CpyMem pVTblTpLib, ByVal pTypeLib, 4
    CpyMem oTypeLib, ByVal pVTblTpLib, Len(oTypeLib)

    ' GetTypeInfoOfGUID
    hRet = CallPointer(oTypeLib.GetTypeInfoOfGuid, pTypeLib, VarPtr(iid_event), VarPtr(pTypeInfo))
    If hRet Then
        GetMemberName = dispid
        Exit Function
    End If

    ' ITypeInfo VTable
    CpyMem pVTblTpInfo, ByVal pTypeInfo, 4
    CpyMem oTypeInfo, ByVal pVTblTpInfo, Len(oTypeInfo)

    ' GetNames
    hRet = CallPointer(oTypeInfo.GetNames, pTypeInfo, dispid, VarPtr(strName), 1, VarPtr(pcNames))

    If Len(strName) = 0 Then
        ' no string... :(
        ' instead return the dispip
        GetMemberName = dispid
    Else
        GetMemberName = strName
    End If
End Function

Public Sub FireEvent(ByVal dispid As Long, ByVal params As Long)
    Dim strEvent    As String
    Dim vParams()   As Variant

    ' get the name of the event
    strEvent = GetMemberName(oTarget, dispid, iid_event)

    ' param array
    vParams = pvGetParamArray(params)

    ' forward the event
    RaiseEvent EventRaised(strEvent, vParams)

    ' sync byref params
    pvSetParamArray params, vParams
End Sub

Public Sub Detach()
    Dim hRet    As Long

    If Not blnAttached Then Exit Sub

    ' clean up
    hRet = CallPointer(oCP.Unadvise, pCP, dwCookie)
    hRet = CallPointer(oCP.IUnk.Release, pCP)
    hRet = CallPointer(oECP.IUnk.Release, pECP)
    hRet = CallPointer(oCPC.IUnk.Release, pCPC)
    Set oTarget = Nothing

    blnAttached = False
End Sub

Public Function Attach(obj As Object) As Boolean
    Dim cReturned   As Long
    Dim hRet        As Long
    Dim iid_ICP     As UUID

    ' already connected to an object?
    If blnAttached Then Detach

    ' IUnknown VTable
    Set oTarget = obj
    pTarget = ObjPtr(obj)
    CpyMem pVTblTarget, ByVal pTarget, 4
    CpyMem oUnk, ByVal pVTblTarget, Len(oUnk)

    ' IID string -> GUID struct
    hRet = CLSIDFromString(StrPtr(IIDSTR_IConnectionPointContainer), iid_ICP)
    If hRet Then Exit Function

    ' get IConnectionPointContainer
    hRet = CallPointer(oUnk.QueryInterface, pTarget, VarPtr(iid_ICP), VarPtr(pCPC))
    If hRet Then Exit Function

    ' IConnectionPointContainer VTable
    CpyMem pVTblCPC, ByVal pCPC, 4
    CpyMem oCPC, ByVal pVTblCPC, Len(oCPC)

    ' get IEnumConnectionPoints
    hRet = CallPointer(oCPC.EnumConnectionPoints, pCPC, VarPtr(pECP))
    If hRet Then Exit Function

    ' IEnumConnectionPoints VTable
    CpyMem pVTblECP, ByVal pECP, 4
    CpyMem oECP, ByVal pVTblECP, Len(oECP)

    ' take the first best Connection Point
    hRet = CallPointer(oECP.Next, pECP, 1, VarPtr(pCP), VarPtr(cReturned))
    If hRet Then Exit Function

    ' IConnectionPoint VTable
    CpyMem pVTblCP, ByVal pCP, 4
    CpyMem oCP, ByVal pVTblCP, Len(oCP)

    ' IID of the event interface
    hRet = CallPointer(oCP.GetConnectionInterface, pCP, VarPtr(iid_event))
    If hRet Then Exit Function

    ' create a new event sink
    Set oSink = CreateEventSink(iid_event, Me)

    ' advise the event sink
    hRet = CallPointer(oCP.Advise, pCP, ObjPtr(oSink), VarPtr(dwCookie))
    If hRet Then Exit Function

    ' wohoo, done!
    blnAttached = True
    Attach = True
End Function

Private Sub Class_Initialize()
    InitObjExtender
End Sub
```



```
        Form2.show
        Set Form2.m = New clsObjectExtender
        If Not Form2.m.Attach(obj) Then
            MsgBox "couldn't connect to c", vbExclamation
        Else
            ' fire some events
            ' unadvise the event sink
            Form2.m.Detach
        End If
```

Form2


```
Public WithEvents m    As clsObjectExtender
Private Sub m_EventRaised(ByVal strName As String, params() As Variant)
    On Error Resume Next
    MsgBox "Event " & strName
End Sub
```

----------


## jsvenu

> Dear xiaoyao,
> 
> Just put msgbox in *CreateVBHeaderCopy* module function as follows:
> 
> ' // Create copy of VBHeader and other structures
> ' // The first four bytes contain thread ID. We use that ID to clean unused headers
> 
> 
> ```
> ...


Dear Trick,

  For knowing values of lModulesCount and lSizeOfHeaders
 I used msgbox in CreateVBHeaderCopy  for displaying the values.But I *did not get the msgbox displayed* but application *worked fine*.So to help xiaoyao I changed the msgbox to trace using outputdebugstring and could get the values thru 
other debug tools when the app was running.Can you clarify me why the app works fine but msgbox is not displayed if I use them.

regards,
JSVenu

----------


## xiaoyao

> Dear Trick,
> 
>   For knowing values of lModulesCount and lSizeOfHeaders
>  I used msgbox in CreateVBHeaderCopy  for displaying the values.But I *did not get the msgbox displayed* but application *worked fine*.So to help xiaoyao I changed the msgbox to trace using outputdebugstring and could get the values thru 
> other debug tools when the app was running.Can you clarify me why the app works fine but msgbox is not displayed if I use them.
> 
> regards,
> JSVenu


you can use the api "messageboxa" replace for "msgbox"
you must run "VBDllGetClassObject" ok,then you can use "msgbox"
if not load "msgvbvm60.dll" ok in thread,you can't use vb6 object  like "msgbox"


```
Public Declare Function MessageBox Lib "user32" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As Long) As Long

Function Msg(Info As String, Optional Title As String = "MyTip")
Msg = MessageBox(0, Info, Title, 0)
End Function
```

----------


## xiaoyao

> Dear Trick,
> 
>   For knowing values of lModulesCount and lSizeOfHeaders
>  I used msgbox in CreateVBHeaderCopy  for displaying the values.But I *did not get the msgbox displayed* but application *worked fine*.So to help xiaoyao I changed the msgbox to trace using outputdebugstring and could get the values thru 
> other debug tools when the app was running.Can you clarify me why the app works fine but msgbox is not displayed if I use them.
> 
> regards,
> JSVenu


you can use the api "messageboxa" replace for "msgbox"
you must run "VBDllGetClassObject" ok,then you can use "msgbox"
if not load "msgvbvm60.dll" ok in thread,you can't use vb6 object  like "msgbox"

----------


## xiaoyao

question:
vb make standad dll /EXPORT:ShowForm2
ShowForm2 is call "createthread" to show form1 in dll.
from abc.exe load this dll,call api "ShowForm2",when show more form1 windows,close all window
and close abc.exe  will crash from time to time
==============

[VBCompiler]
LinkSwitches=/DLL /ENTRY :Big Grin: llMain /EXPORT:Sum /EXPORT:ShowForm /EXPORT:ShowForm2
 ShowForm2 is a sub 


```
dll code:
sub ShowForm2()
createthread addressof NewFormInThread
end sub

sub NewFormInThread()

CreateIExprSrvObj 0, 4, 0
Call InitRuntime(lpInst_, lpUnk_, lpVBHdr_, hInstance, 1, 0)
dim f as form1
set f=new form1
f.show 1
set f=nothing
vbCoUninitialize

end sub
```

use vc++ make a standad exe(or use vb6 ) to call this api "ShowForm2"
There is an interface in the DLL that creates a new thread and displays a form in the thread.

If you call this API many times, then manually close all the Windows, and then end VC++ write the main program EXE will crash from time to time.
chinese:
dll中有个接口，他的功能是创建一个新线程，并且在线程中显示一个窗体。
如果多次调用这个API，然后手工关闭所有窗口，再结束VC++写的主程序EXE就会不时的崩溃。

----------


## The trick

> why are manually vTable creation, manually registration etc required for MTA library.


Because the runtime is STA and you can't use it to create a MTA library, particularly you can't uses classes. You need to "emulate" them through UDTs.




> I don't understand why the main default thread not being apartment model is able to load the user control to the form in main default thread.


The main thread is STA as well. VB6 can create only STA. The default model is the single-threaded one. It's like if you use such option when you create an ActiveX DLL. This option means all the objects from that library live in the main STA (even if you create them from other STA). The reason it can't load usercontrols in other project types (ActiveX EXE, Standard EXE with threading) related to the internal structure of the runtime (called Hxmod).




> Moreover when I try to make the main thread of the project to be apartment model as follows it crashes.


It occurs because you write to the non-writable page. Notice i used VirtualProtect to change attributes. 




> About what you said: my goal is to create threads and allow them to call back to any address, whether it's in bas, forms, classes, usercontrol, etc. It seems to work.


What's the thread context should process the callback? If the context isn't crucial you can use marshaling for object-calls. The COM will do all the work to synchronize theads. If you need to process the callback in its own thread you can use *InitCurrentThreadAndCallFunction*.




> Can you clarify me why the app works fine but msgbox is not displayed if I use them.


Because *MsgBox* is the project-depended function. When you call this function the project isn't initialized yet and runtime doesn't know if the *Unattended Execution* option enabled/disabled. The runtime queries for the thread data (*CThreadPool::GetThreadData*) and it returns nothing. The function behaves like it returns *vbAbort*.

----------


## The trick

> you can use the api "messageboxa" replace for "msgbox"
> you must run "VBDllGetClassObject" ok,then you can use "msgbox"
> if not load "msgvbvm60.dll" ok in thread,you can't use vb6 object like "msgbox"


The built-in MsgBox function isn't only show message window it may write an entry to a log.

----------


## xiaoyao

> The built-in MsgBox function isn't only show message window it may write an entry to a log.


use createthread api in standard exe,it's very easy。
how to use createthread (for show form1)in vb6 standard dll(not activex dll),do you have a simple demo?Purpose: injection of other processes, and multi-threaded display of multiple windows, the need for multiple injection and then uninstall.In the uninstall, it is often the main program to be injected to do the crash.
chinese:用途:注入其他进程，并且用多线程显示多个窗口，需要多次注入再卸载。在卸载的时候，经常会把被注入的主程序搞崩溃。

----------


## The trick

> use createthread api in standard exe,it's very easy。
> how to use createthread (for show form1)in vb6 standard dll(not activex dll),do you have a simple demo?Purpose: injection of other processes, and multi-threaded display of multiple windows, the need for multiple injection and then uninstall.In the uninstall, it is often the main program to be injected to do the crash.


Why don't you want to use ActiveX DLL? They work no worse than standard DLLs.

If you can't use an ActiveX DLL for some reason (for example if the interface is fixed) you can notice to that. If you have troubles please attach the example (a zip file) i'll fix it.

----------


## jsvenu

Dear Trick,


In the  following attached *activex exe code* running standalone  in modmain.bas  we display fMain in modeless way



```
Sub Main()                              
    If App.TaskVisible Then fMain.Show  
End Sub
```

 I add usercontrol to my activex exe standalone project thru  *Load User Control* button as follows:



```
Private Sub Command1_Click()              
    Controls.Add("AxThreading.UserControl1", "Ctl1").Visible = True             'load usercontrol Control onto this ThreadForm
End Sub
```

When I click on the *Load User Control* button  I get the following error in msgbox 

---------------------------
AxThreading
---------------------------
Run-time error '720':

'AxThreading.UserControl1' is not a valid control type
---------------------------
OK   
---------------------------





But when I make fMain to display in modal fashion as follows and click on the *Load User Control* button it *works fine*.

*Sub Main()                              
    If App.TaskVisible Then fMain.Show vbModal 
End Sub*

*1. Please clarify how to make this activex exe application to work when fMain is displayed in modeless way.*


When I click on *Create and show an additional threaded Form* button in the displayed form I get another instance of the same form displayed in new thread.

When I click on the* Load User Control* button  in this new form I get the following error

---------------------------
AxThreading
---------------------------
Run-time error '339':

Component '' or one of its dependencies not correctly registered: a file is missing or invalid
---------------------------
OK   
---------------------------

*2. Please clarify how to make this work fine in this new form displayed in new thread.*


regards,
JSVenu

----------


## jsvenu

> Dear Trick,
> 
> 
> In the  following attached *activex exe code* running standalone  in modmain.bas  we display fMain in modeless way
> 
> 
> 
> ```
> Sub Main()                              
> ...


Dear Trick,
For changing the activex exe to apartment model threading can we get vbheader of the activex exe in the same way as
standard exe using the same GetVBHeader function of modmultithreading module so that we can see that usercontrol works in main thread form and in new thread form.Please clarify.

regards,
JSVenu

----------


## xiaoyao

> xiaoyao, it's difficult to understand you, sorry. What do you want? Do you ask something or?


why standard exe only use "VBDllGetClassObject",but standard dll need "UserDllMain"

Private Function InitRuntime(ByVal lpInst As Long, ByVal lpUnk As Long, ByVal lpVBHdr As Long, ByVal hInstDll As Long, _
                             ByVal fdwReason As Long, ByVal lpvReserved As Long) As Long
    Dim iid     As uuid
    Dim clsid   As uuid

    InitRuntime = UserDllMain(lpInst, lpUnk, hInstDll, fdwReason, ByVal lpvReserved)
    If InitRuntime Then
        vbCoInitialize ByVal 0&
        iid.data4(0) = &HC0: iid.data4(7) = &H46                    ' IUnknown
        VBDllGetClassObject lpInst, lpUnk, lpVBHdr, clsid, iid, 0   ' Инициализация потока
    End If
End Function
For apartment threade

----------


## jsvenu

> Dear Trick,
> For changing the activex exe to apartment model threading can we get vbheader of the activex exe in the same way as
> standard exe using the same GetVBHeader function of modmultithreading module so that we can see that usercontrol works in main thread form and in new thread form.Please clarify.
> 
> regards,
> JSVenu


Dear Trick,
If I try to change the  *activex exe*(standalone mode) code for the link http://www.vbforums.com/attachment.p...1&d=1580720396   as follows:


Sub Main()                                                                 
*modMultiThreading.Initialize*
    If App.TaskVisible Then fMain.Show                                     
End Sub


where GetVBHeader() is modified as 


' // Get VBHeader structure
Private Function GetVBHeader() As Long
    Dim ptr     As Long
    Dim bThreading As Long
    Dim tempheader As Long
    Dim lOldProtect     As Long
    ' // Get e_lfanew
    GetMem4 ByVal hModule + &H3C, ptr
    ' // Get AddressOfEntryPoint
    GetMem4 ByVal ptr + &H28 + hModule, ptr
    ' // Get VBHeader
    GetMem4 ByVal ptr + hModule + 1, tempheader
* 'set to apartment model to support user control*
    GetMem1 ByVal tempheader + &H3C, bThreading
    bThreading = bThreading Or 1
    VirtualProtect ByVal tempheader, 4, PAGE_READWRITE, lOldProtect
*GetMem1 bThreading, ByVal tempheader + &H3C*
    VirtualProtect ByVal ptr, 4, lOldProtect, 0
    GetVBHeader = tempheader
End Function



when I built and run compiled exe I get the following *error* in msgbox:


*---------------------------
AxThreading
---------------------------
Run-time error '369':

Operation not valid in an ActiveX DLL
---------------------------
OK   
---------------------------*


Please clarify.

regards,
JSVenu

----------


## xiaoyao

how to run createthread in activex.exe, and support usercontrols in vb6 form1.frm (by createthread show form1)
=======================
Set OBJ = Controls.Add("actexexc2.UserControl1", "UserControl1AA")
'err: 720,' actexexc2. Usercontrol1 'is not a valid control type (load by from)
========
GetMem4 ByVal ptr + hModule + 1, lpVBHeader
VBDllGetClassObject VarPtrHModule, 0, lpVBHeader, 0&, Iid, 0

'display in multithread: err: 339, part' 'or one of its attachments is not registered correctly: a file is missing or invalid
(load by Createthread)
========
GetMem4 1&, ByVal NewVBHeader_Heap + &H3C   '加的，支持自定义控件
VBDllGetClassObject VarPtrHModule, 0, NewVBHeader_Heap, 0&, Iid, 0
'heap multithreading error: err: 438, object does not support this property or method
( heap by creathread)
=======================
chinese:'ERR:720,'actexexc2.UserControl1' 不是有效控件类型
'在多线程中显示：ERR:339,部件 '' 或其附件之一不能正确注册：一个文件丢失或无效
'heap多线程出错：ERR:438,对象不支持该属性或方法
Set OBJ = Controls.Add("actexexc2.UserControl1", "UserControl1AA")

----------


## xiaoyao

hHeap = GetProcessHeap()
  hHeap = HeapCreate(0, 0, 65536)
With HEAP, you can load custom controls, but it's unstable,

The first time multithreading was successful, closed and reopened, not the second time

----------


## xiaoyao

hHeap = GetProcessHeap()
  hHeap = HeapCreate(0, 0, 65536)
With HEAP, you can load custom controls, but it's unstable,

The first time multithreading was successful, closed and reopened, not the second time

maybe activex.exe not use the same lSizeOfHeaders ?

    lSizeOfHeaders = &H6A + &H23C + &H54 + &HC + 4
    GetMem4 ByVal lpVBHeader + &H30, pOldProjInfo 'vb6 ide Can't Run
    GetMem4 ByVal pOldProjInfo + &H4, pOldObjTable
    GetMem4 ByVal pOldObjTable + &H30, pOldDesc
    GetMem2 ByVal pOldObjTable + &H2A, lModulesCount
    lSizeOfHeaders = lSizeOfHeaders + &H30 * lModulesCount

GetMem4 1&, ByVal NewVBHeader_Heap + &H3C   '加的，支持自定义控件

----------


## Schmidt

> If I try to change the  *activex exe*(standalone mode) code  ...
> ...where GetVBHeader() is modified ...
> ...I get the following *error* ...


Well, you've changed a perfectly fine VB6-threading-example (which does everything by the book),
into something which uses the "VB-Header-hack" - and thus instabilities are normal.

Don't do that kind of hacking.

It is not needed at all, to accomplish proper threading in VB6.

Olaf

----------


## xiaoyao

> Well, you've changed a perfectly fine VB6-threading-example (which does everything by the book),
> into something which uses the "VB-Header-hack" - and thus instabilities are normal.
> 
> Don't do that kind of hacking.
> 
> It is not needed at all, to accomplish proper threading in VB6.
> 
> Olaf


active.exe may need to make a standard dll, using the multi-threaded api in the dll.

----------


## xiaoyao

> Dear Trick,
> If I try to change the  *activex exe*(standalone mode) code for the link http://www.vbforums.com/attachment.p...1&d=1580720396   as follows:
> 
> 
> Sub Main()                                                                 
> *modMultiThreading.Initialize*
>     If App.TaskVisible Then fMain.Show                                     
> End Sub
> 
> ...



1. Create multiple threads in activex.exe. You can only succeed once, and then the application crashes.
2. If you want to make a standard DLL (vbdll. DLL), add the interface API (createthread? API) for creating threads,
Then pass (app. Hsinstance, vbheader) to DLL in activex.exe and call createthread? API
You can also create multiple threads in a row, but you cannot use global variables.
3. You can add some global variables (public ABC as long) to the standard DLL,
The output function then accesses the variable / Export: getabc.
If you take out the process address of class 1 of activex.exe and call it directly with a thread, you may be able to modify the global variables

1. создание нескольких потоков внутри ACTIVEX. EXE может быть успешным только один раз, после чего приложение рухнет. 
 2, если выбран стандартный DLL (vbdll.dll), добавить API (CreateThread tmu api) для создания интерфейса дискуссии; 
 затем в ACTIVEX. EXE (App. HInstance, VBHeader) для DLL, call CreateThread au api 
 Это также позволяет создавать несколько потоков подряд, но не может использовать глобальные переменные. 
 3 в стандартный DLL можно добавить глобальные переменные (public ABC AS LONG); 
 затем функция вывода позволяет получить доступ к переменной / export: GetAbc. 
 Если взять операционные адреса класса Class1, то вызовите непосредственно дискуссии, возможно, можно изменить глобальные переменные

chinese:
1，在ACTIVEX.EXE里面创建多线程，只能成功一次，然后应用程序就崩溃了。
2，如果做一个标准DLL(vbdll.dll),添加创建线程的接口API(CreateThread_api)，
然后在ACTIVEX.EXE里面传递（App.hInstance，VBHeader）给DLL,调用CreateThread_api
这样也可以连续多次创建多线程，但是无法使用全局变量。
3，可以在标准DLL里面添加一些全局变量(public ABC AS LONG )，
然后输出函数进行存取变量 /export:GetAbc。
如果把Activex.exe的Class1这个类的过程地址取出来，直接用线程调用，说不定可以修改全局变量

----------


## jsvenu

> Well, you've changed a perfectly fine VB6-threading-example (which does everything by the book),
> into something which uses the "VB-Header-hack" - and thus instabilities are normal.
> 
> Don't do that kind of hacking.
> 
> It is not needed at all, to accomplish proper threading in VB6.
> 
> Olaf


Dear Olaf,

I am trying to make the activex exe application support *user control* in the same application because it is not working as I already mentioned.That is why I asked *Trick* for the solution since he has already helped me to do it in standard exe.You can tell me  how to *support* the *user control* in the application.

regards,
JSVenu

----------


## The trick

I'll answer later don't have time right now.

----------


## Schmidt

> I am trying to make the activex exe application support *user control* in the same application because it is not working as I already mentioned.


I thought you found a workaround already (by showing these Forms modally)...

But anyways, there's no hacks required to make "Project-Private-UserControls" work with threaded Forms.
You just have to embrace and learn about**:
- VB6-Classes
- how to implement and test them as "Private Classes" in an isolated Test-Project
- how to later move - and encapsulate them in Ax-Dlls (after they become stable)
- and how to load Ax-Classes from such Ax-Dlls regfree

The VB6-STA-based threading-model (which is all about "Public Classes on their own Threads"), works rock-solid -
whereas attempts at FreeThreading in VB6 will always be "fragile" at best.

So, the solution for your "Private UC on a threaded Form" is, to move the threaded Form out of the Ax-Exe-Project...
And into a separate ActiveX-Dll-Project (together with any Private UCs you want to use on these threaded Forms).

I've added a second example (which shows how to do that) to the AxExeThreading article here:
http://www.vbforums.com/showthread.p...-(simple-Demo)


HTH

Olaf

----------


## xiaoyao

> I'll answer later don't have time right now.


for my testing,activex.exe can support createthread。but can't add usercontrol on form2.frm。 I can  make standard exe first,add usercontrols to forms,than change to activex.exe。
now only can add usercontrol by code "controls.add(*)".
maybe activex.exe is a special format pe code exe。it's sta thread?createthread by a standard dll api (vbdll.dll ) ,so can't use Global variables when running in createthread。
without standard dll ,it's only can run once createthread,when run the second times,crashed!mabe need some hake code for change vb header?

but it There must be a way to support. .(like excel must be can support createthread)

only vb6 make more question, like not support standard createthread.
Microsoft engineers left too many issues at the time and gave up vb6 without handling them.

----------


## xiaoyao

> I thought you found a workaround already (by showing these Forms modally)...
> 
> But anyways, there's no hacks required to make "Project-Private-UserControls" work with threaded Forms.
> You just have to embrace and learn about**:
> - VB6-Classes
> - how to implement and test them as "Private Classes" in an isolated Test-Project
> - how to later move - and encapsulate them in Ax-Dlls (after they become stable)
> - and how to load Ax-Classes from such Ax-Dlls regfree
> 
> ...


Maybe activex exe is a packaged rot model? If we first develop a rot program instead of activex.exe, then we can achieve perfect multithreading as we want, it is very convenient to support usercontrol。
chinese:也许 activex exe 是一种包装好的 rot 模型?假如我们单独先开发一个 rot 程序代替 activex.exe ,这样就可以随心所欲的实现完美的多线程了，很方便支持 usercontrol

----------


## Schmidt

> vb6 make more question, like not support standard createthread
> Microsoft engineers left too many issues at the time and gave up vb6 without handling them.


The CreateThread-API is useful only from within languages, which support FreeThreading.

There is absolutely no point in "forcing" VB6 to handle FreeThreading as well,
because it was not designed for that ... instead it comes with its own "COM-Class based threading-support", 
which works differently - but in the end allows threading as well.

Instead of the CreateThread-API, you'll have to use a "CreateObjectFromPublicClassOnThreadedSTA" kind of call:
- which can be done either via CreateObject (called from within an AxExe-ProjectType)
- or via your own "STA-establishing implementation" in a Standard- or AxDll

This "COM-Class based threading-support" in VB6 works stable, requires no hacks - 
and makes it easy for threading-newbies  to implement solid threading-solutions,
because no "synchronizing" has to be implemented for cross-thread-communication.

There are no "issues" with VB6 threading, if there are - I'd really like to learn about them.

Just think about, what you'd like to implement as a "threaded solution" in VB6 (a concrete, useful scenario) - 
and we can show you how to do that.

Olaf

----------


## jsvenu

> I thought you found a workaround already (by showing these Forms modally)...
> 
> But anyways, there's no hacks required to make "Project-Private-UserControls" work with threaded Forms.
> You just have to embrace and learn about**:
> - VB6-Classes
> - how to implement and test them as "Private Classes" in an isolated Test-Project
> - how to later move - and encapsulate them in Ax-Dlls (after they become stable)
> - and how to load Ax-Classes from such Ax-Dlls regfree
> 
> ...


Dear olaf,

First thankyou for the example.

As I said when we add a button to fThreaded form in activex dll project MyForms and try to add the usercontrol through the button as follows:




```
Private Sub Command1_Click()
    On Error GoTo err1
    Controls.Add("MyForms.ucTest", "Ctl2").Visible = True
    Exit Sub
err1:
    MsgBox "err:" & Err.Number & "," & Err.Description & "," & Err.Source
End Sub
```


 which adds usercontrol to the form at *runtime* we get the following error( in msgbox )when we run the app in compiled mode and *click* on this button on displayed fThreaded form.

*---------------------------
MyForms
---------------------------
err:711,Invalid class string.
Looking for object with ProgID: MyForms.ucTest,MyForms
---------------------------
OK   
---------------------------
*


This is the reason I asked *Trick*  for help.


regards,
JSVenu

----------


## Schmidt

> As I said when we add a button to fThreaded form in activex dll project MyForms and try to add the usercontrol through the button as follows:
> ...
> ...we get the following error( in msgbox )when we run the app in conpiled mode and click on this button on displayed fThreaded form.
> *
> err:711,Invalid class string.
> Looking for object with ProgID: ucTest.ctlTest,MyForms
> *


No, I cannot reproduce that...
As said, this kind of threading is as safe as can be.

So (just in case), you should *not* include any of tricks "threading-support" modules -
neither into the AxExeThreading2-project, nor should they be included in MyForms-Project. 
They are not needed at all.

My guess is, that you need to recompile the OCX-Project (or re-register the OCX).
Or your ProgID ("*ucTest.ctlTest*") is wrong.

If I try that with "wellknown ProgIDs" (for the IE-WebBrowserControl, or Krools CCR-OCX),
it works without any problem (in IDE and compiled).

Here is the complete code of fThreaded.frm in Ax-Dll MyForms:


```
Option Explicit

Private Sub Form_Load()
  Caption = Caption & " ThreadID: " & App.ThreadID
End Sub

Private Sub Command1_Click()
  Controls.Add("Shell.Explorer.2", "WebBrowser1").Visible = True
  Controls.Add("VBCCR16.TextBoxW", "TextBox1").Visible = True
End Sub
```

HTH

Olaf

----------


## jsvenu

> No, I cannot reproduce that...
> As said, this kind of threading is as safe as can be.
> 
> So (just in case), you should *not* include any of tricks "threading-support" modules -
> neither into the AxExeThreading2-project, nor should they be included in MyForms-Project. 
> They are not needed at all.
> 
> My guess is, that you need to recompile the OCX-Project (or re-register the OCX).
> Or your ProgID ("*ucTest.ctlTest*") is wrong.
> ...


Dear Olaf,

I already corrected the ProgID to *MyForms.ucTest* in the post *#136*  but I was getting the same error which I  *solved* by using *Trick's* post 
http://www.vbforums.com/showthread.p...-Marshal/page2 *#71*
Thankyou for the example.

regards,
JSVenu

----------


## xiaoyao

Why haven't you moved in these two days?

----------


## The trick

Sorry for my long delay.

jsvenu, my module is intended to work with threading using Standard EXE projects. Why do you need this module for an AxEXE if the AxExe can create a thread without it (see Olaf's example)? Please tell me the reason and i give the solution. I gave you the example how to do it in STD EXE.




> why standard exe only use "VBDllGetClassObject",but standard dll need "UserDllMain"


Because if you want to initialize the runtime you need to call *CThreadPool::InitDllAccess* which is in *VBDllGetClassObject/ThunRTMain*




> Well, you've changed a perfectly fine VB6-threading-example (which does everything by the book),
> into something which uses the "VB-Header-hack" - and thus instabilities are normal.


What's the hack exactly? Okay, we don't doubt your "perfectly fine VB6-threading-example" (PFVB6TE) works well but jsvenu want to make it worked with the private controls.




> But anyways, there's no hacks required to make "Project-Private-UserControls" work with threaded Forms.
> You just have to embrace and learn about**:
> - VB6-Classes
> - how to implement and test them as "Private Classes" in an isolated Test-Project
> - how to later move - and encapsulate them in Ax-Dlls (after they become stable)
> - and how to load Ax-Classes from such Ax-Dlls regfree


Hmmm... Isn't that the "crutch"? The potential user just want to use a private UserControl like he added in the project. He don't want any external DLL/OCX and any additional code to implement the "PFVB6TE (which does everything by the book)".




> The VB6-STA-based threading-model (which is all about "Public Classes on their own Threads"), works rock-solid -
> whereas attempts at FreeThreading in VB6 will always be "fragile" at best.


Why then the "PFVB6TE" doesn't work with private controls?




> So, the solution for your "Private UC on a threaded Form" is, to move the threaded Form out of the Ax-Exe-Project...
> And into a separate ActiveX-Dll-Project (together with any Private UCs you want to use on these threaded Forms).
> 
> I've added a second example (which shows how to do that) to the AxExeThreading article here:
> http://www.vbforums.com/showthread.p...-(simple-Demo)


Okay, thank you. But you gave the example which contains the Form with the control in the AxDll. I think the question was how to load a private control to the AxExe form.




> There is absolutely no point in "forcing" VB6 to handle FreeThreading as well,
> because it was not designed for that ... instead it comes with its own "COM-Class based threading-support",
> which works differently - but in the end allows threading as well.


Okay. But an application can work with an environment which requires such FreeThreading like callbacks etc (like DirectShow, WinInet etc).




> This "COM-Class based threading-support" in VB6 works stable, requires no hacks -
> and makes it easy for threading-newbies to implement solid threading-solutions,
> because no "synchronizing" has to be implemented for cross-thread-communication.


Okay, if it works stable why jsvenu can't load a private control? Olaf, my module is intended for using Threading in Standard-EXE projects. It also provides the minimal framework to produce the behavior like in AxExe/AxDll projects which isn't required "synchronizing". In truth, COM does all synchronization regardless the project type not VB6. It isn't replacement for AxExe/AxDll solutions like you suggested.

----------


## jsvenu

> Sorry for my long delay.
> 
> jsvenu, my module is intended to work with threading using Standard EXE projects. Why do you need this module for an AxEXE if the AxExe can create a thread without it (see Olaf's example)? Please tell me the reason and i give the solution. I gave you the example how to do it in STD EXE.


Dear Trick,

Thankyou for the reply.

The first example I learnt in vb6 multithreading was thru activex exe.Olaf's example remainded me that again and  since you helped(by making a mention of activex exe also same problem occurs) in how to solve  when Xiaoyao asked you in standard exe , I thought I can try the same with Olaf's activex exe example also which I finally solved using http://www.vbforums.com/showthread.p...-(simple-Demo)* #16* using your module with modification to apartment model in GetVBHeader module function.
But the only clarification required for me is how to avoid global variables from being cleared in activex exe since there I did not use CreateVBHeaderCopy module function.


regards,
JSVenu

----------


## The trick

> But the only clarification required for me is how to avoid global variables from being cleared in activex exe since there I did not use CreateVBHeaderCopy module function.


You can't because all the variables are isolated by TLS. Only artificially. If you want to use shared variables you need to use either StandardEXE or Activex-DLL (single threaded) project types. You could to get the pointer to a variable in other apartment and read/write to/from it.

----------


## jsvenu

> You can't because all the variables are isolated by TLS. Only artificially. If you want to use shared variables you need to use either StandardEXE or Activex-DLL (single threaded) project types. *You could to get the pointer to a variable in other apartment and read/write to/from it*.


Dear Trick,

Thankyou for the reply. Can you show me a example code for * to get the pointer to a variable in other apartment and read/write to/from it*.

regards,
JSVenu

----------


## The trick

> Thankyou for the reply. Can you show me a example code for to get the pointer to a variable in other apartment and read/write to/from it.


Why do you need that? If you want to use shared variable just use STD-EXE and my module.

----------


## jsvenu

> Why do you need that? If you want to use shared variable just use STD-EXE and my module.


Dear Trick,
 I am asking this in the sense that since vb6 also supports activex exe project type  and if we can use multithreading in a better way here also  We can make work in activex exe in a comfortable way rather than working in restricted way and avoiding the activex exe project completely for multithreading since it is not supporting global variables.

regards,
JSVenu

----------


## The trick

> Dear Trick,
> I am asking this in the sense that since vb6 also supports activex exe project type and if we can use multithreading in a better way here also We can make work in activex exe in a comfortable way rather than working in
> restricted way and avoiding the activex exe project completely for multithreading since it is not supporting global variables.


What do you expected if you make shared variables? It becomes StdExe with restrictions like you have with my module. Do you see the restrictions of AxExe? Do you see the restrictions of VbTrickThreading? What's the more comfortable with AxExe compared with StdExe?

----------


## jsvenu

> What do you expected if you make shared variables? It becomes StdExe with restrictions like you have with my module. Do you see the restrictions of AxExe? Do you see the restrictions of VbTrickThreading? What's the more comfortable with AxExe compared with StdExe?


Dear Trick,

 I am not comparing std exe and actx exe in the sense of comfort. 
 I am asking *suppose we are already working* in activex exe project and want to use *module level global  variables* in the project .How to access them across apartments without being cleared in different threads.

regards,
JSVenu

----------


## The trick

By rules you should pass the data between COM-calls. Just if you need to transmit data to other apartment you should call a method of object in other apartment and pass your variable. Marshaling will do all the work to transmit the data and make copy (if need). It isn't safe to have shared global variables because you always should marshal the data. But if you want you can pass the pointer to apartment by a COM-call and then use it from other apartment.

----------


## jsvenu

> By rules you should pass the data between COM-calls. Just if you need to transmit data to other apartment you should call a method of object in other apartment and pass your variable. Marshaling will do all the work to transmit the data and make copy (if need). It isn't safe to have shared global variables because you always should marshal the data. But if you want you can pass the pointer to apartment by a COM-call and then use it from other apartment.


Dear Trick,
I can understand that we can use marshalling in the sense it uses GIT and using comarshal... and counmarshal.. api we can access data.But I did not understand *passing the  pointer to apartment by a COM-call and then use it from other apartment*.Can you explain me by providing a simple example code.

regards,
JSVenu

----------


## xiaoyao

> Dear Trick,
> 
> Thankyou for the reply. Can you show me a example code for * to get the pointer to a variable in other apartment and read/write to/from it*.
> 
> regards,
> JSVenu


in activex.exe( clsid  :Stick Out Tongue: roject1.class1 ) ,you have a variable like :Stick Out Tongue: ublic str1 as string,if you create a new object (thread2obj =createobject("project1.class1")) in new thread ,Normal way we cant collect that variable content .
but we can use public strarr(x) as string to read write value,then str1 changed。first,we need call thread2obj.setptr(objptr(str1))
,x=***
or share a class or form to all New thread.

Other methods :Big Grin: ispCallFunc

Private Declare Function DispCallFunc Lib "oleaut32" (ByVal pvInstance As Long, ByVal oVft As Long, ByVal lCc As Long, ByVal vtReturn As VbVarType, ByVal cActuals As Long, prgVt As Any, prgpVarg As Any, pvargResult As Variant) As Long

----------


## xiaoyao

Because if you want to initialize the runtime you need to call CThreadPool::InitDllAccess which is in VBDllGetClassObject/ThunRTMain

mabe we can hook api "VBDllGetClassObject",like your vbcreatethread "tlssetvalue+VBDllGetClassObject",
maybe vb6 use tlssetvalue The same context is intentionally blocked, so global variables cannot be shared anymore.

----------


## xiaoyao

in activex.exe vb6 certainly has a way to achieve multi-threaded and custom controls, ocx controls, is more difficult.
chinese:vb6肯定有办法实现activex exe的多线程和自定义控件，ocx 控件，就是难度比较大

----------


## jsvenu

> in activex.exe vb6 certainly has a way to achieve multi-threaded and custom controls, ocx controls, is more difficult.
> chinese:vb6肯定有办法实现activex exe的多线程和自定义控件，ocx 控件，就是难度比较大


Dear xiaoyao,

OCX controls problem in activex exe already solved link http://www.vbforums.com/showthread.p...-(simple-Demo) #16 

regards,
JSVenu

----------


## xiaoyao

> Dear xiaoyao,
> 
> OCX controls problem in activex exe already solved link http://www.vbforums.com/showthread.p...-(simple-Demo) #16 
> 
> regards,
> JSVenu




```
ActivexExeThread.bas
Public Src As String '主变量
Public MEM(0) As String
Public x As Long

Public ABC As Long, X2 As Long
Public AbcArr(0) As Long

in thread forms:
Private Sub ShowSrcAbc_Click()
Dim OldAbc As Long
'AbcArr(X2)=abc
'MEM(x) =src
OldAbc = AbcArr(X2)
MEM(x) = "test:" & Now
AbcArr(X2) = AbcArr(X2) + 1
MsgBox "Src=" & MEM(x) & vbCrLf & "Old Abc=" & OldAbc & vbCrLf & "New ABC=" & AbcArr(X2), , "ThreadID = " & App.ThreadID
End Sub

in main form:
MsgBox "Src=" & Src & vbCrLf & "ABC=" & ABC, , "threadid=" & App.ThreadID
```

----------


## xiaoyao

> Dear Trick,
> I can understand that we can use marshalling in the sense it uses GIT and using comarshal... and counmarshal.. api we can access data.But I did not understand *passing the  pointer to apartment by a COM-call and then use it from other apartment*.Can you explain me by providing a simple example code.
> 
> regards,
> JSVenu


I found a way to hake vb6 activex.exe,can support public abc as string(The global variable) for every thread ,every activex.exe objects from by createobject("project1.class1")
it like standard exe, support All the multi-threading tricks,The only difference is { it's a activex.exe}

----------


## jsvenu

> I found a way to hake vb6 activex.exe,can support public abc as string(The global variable) for every thread ,every activex.exe objects from by createobject("project1.class1")
> it like standard exe, support All the multi-threading tricks,The only difference is { it's a activex.exe}



http://www.vbforums.com/showthread.p...=1#post5451681 *#2*

----------


## xiaoyao

Application 2: Detailed analysis of vb header structure
SoftSnoop2009应用二：详细解析vb头结构
https://bbs.pediy.com/thread-101547.htm

----------


## The trick

> I can understand that we can use marshalling in the sense it uses GIT and using comarshal... and counmarshal.. api we can access data.But I did not understand passing the pointer to apartment by a COM-call and then use it from other apartment.Can you explain me by providing a simple example code.


If you want to use shared data you can use a shared section like i did here. Then you can map an array to that section and all the instances will have the shared data.

----------


## jsvenu

> If you want to use shared data you can use a shared section like i did here. Then you can map an array to that section and all the instances will have the shared data.


Dear trick,
Thankyou for the sample.
regards,
JSVenu

----------


## xiaoyao

> Well, you've changed a perfectly fine VB6-threading-example (which does everything by the book),
> into something which uses the "VB-Header-hack" - and thus instabilities are normal.
> 
> Don't do that kind of hacking.
> 
> It is not needed at all, to accomplish proper threading in VB6.
> 
> Olaf


Microsoft does not continue to upgrade vb6 ide, does not support multithreading, does not support standard dll, does not support console programs, and does not support 64-bit program development. Many programmers have implemented these functions one by one, without ide source code, can only use some similar hackers, assemblies, secure arrays, pointers and other methods, and the use is also very stable. It cannot be said that these people are useless and that they have invested a lot of time and technology. It's not just standard exe that supports multithreading, standard dll,com dll,activex.exe,ocx controls, and vba,vbs, all supports multithreading. Isn't that a very meaningful thing? It's also very interesting. No one provides funding or sponsorship. I understand this kind of person as "geek". One developer spent five years developing a vb6-like ide "visualFreebasic" that supports multithreading, pointers, 64-bit compilation, and continues to develop, updating dozens of versions. I think it makes a lot of sense.

----------


## smkperu

> You can't use an arbitrary code if you don't initialize the DLL-project-context, particularly the API calls declared in VB6.
> The proper way is to call *DLLGetClassObject* and then to call the exported functions when you use the ActiveX Dll project type.
> I had the thoughts to make the module which add the ability to initialize a context in DLL created as Standard EXE but there are some pitfalls like when you receive *DLL_PROCESS_DETACH* how you uninitialize the projects-context created in the other threads.
> 
> The attached archive contains the module and several examples of usage in 3 different languages (VB6, C, PureBasic):
> *Simple* - just show message box in DLL;*ShowForm* - show the Form from DLL;*CallbackThread* - create a thread in DLL and then call the callback function in EXE.
> 
> Because of the code module is quite raw one i don't publish it in the CodeBank:
> 
> ...


Hello the trick,

  1. How is the following GUID extracted in  the interfaces.h file of C based exe project for the frmThread form of the CallbackThread standard dll project.


(0x33AD4F39, 0x6699, 0x11CF, 0xB7, 0x0C, 0x00, 0xAA, 0x00, 0x60, 0xD3, 0x93);

2. Is there any way to programmatically extract GUID of any resource like form or class
in CallBackThread project.

Thanks

----------


## The trick

> Hello the trick,
> 
>   1. How is the following GUID extracted in  the interfaces.h file of C based exe project for the frmThread form of the CallbackThread standard dll project.
> 
> 
> (0x33AD4F39, 0x6699, 0x11CF, 0xB7, 0x0C, 0x00, 0xAA, 0x00, 0x60, 0xD3, 0x93);
> 
> Thanks


It's VB6-Form interface.

----------


## smkperu

> It's VB6-Form interface.


Hello Trick,

If we had used two different forms say frmThread1 and frmThread2 in CallbackThread how are they distinguised.

Thanks

----------


## The trick

> Hello Trick,
> 
> If we had used two different forms say frmThread1 and frmThread2 in CallbackThread how are they distinguised.
> 
> Thanks


You could use *Implement* keyword for example.

----------


## smkperu

> You could use *Implement* keyword for example.


Hello Trick,

   1. Can you show me how to do this for more than one form in *CallbackThread* vb6 dll and access in C based exe.

    2. How to do this for vb6 *Classes*   in addition to forms.


Thanks

----------


## smkperu

> It's VB6-Form interface.


Hello Trick,

  Now I understood that using Guid method of InterfaceInfo object created InterfaceInfoFromObject we get guid of class or form from Typelib Information.

Like you have done in *CallbackThread* project can you provide a *simple  example of how to access a vb6 form/Class method from c application  using vtbl*.

Thanks

----------


## smkperu

Hello Trick,

 I understood that by sending IUnknown pointer of vb6 class object I was able to access it in c application using its lpvtbl.

Thanks

----------


## smkperu

Hello Trick,

I am unable to send PM as your inbox is full.Please clear some messages.

Thanks

----------

