# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  VB6 Threaded Forms (simple Demo)

## Schmidt

As the title says, just a simple Demo which might help to "find ground again", when it comes to VB6-threading.
(there's certainly a whole lot of overcomplicating, unnecessary stuff out there).

The Demo shows real InProcess-Multithreading via a simple VB6-AX-Exe-Project.

There are no API-calls necessary, nor are there any Assembly-hacks - it's just plain VB6 - running threaded Forms in STAs.

Within the threaded Form, an external (OCX-based) Control gets loaded (since that recently came up).
To get things running:
- open and compile ExternalControl.vbp first
- then open and compile AxThreading.vbp

Ok, here the Zip:
AxExeThreading.zip

Addition:
Since the question came up, how to work with "Project-Private UserControls" (instead of loading a Control 
from an external OCX onto the threaded Form, as covered in the original Demo-Zip above)...

Here a slightly changed version, which covers that case (by hosting the Thread-Forms, as well as the Private UCs in a separate AX-Dll-Project):
AxExeThreading2.zip


Have fun,

Olaf

----------


## Krool

I think the overcomplicating comes, because of the following disadvantages that comes from using the simple plain VB6 approach:
- AxExe can't be RegFree
- The OCX that is used within the AxExe must be pre-compiled. Thus not very flexible when changing logic quite often in the threaded process.

That's why people like generic solutions. However, this is just a note of the disadvantages with this approach for general info and not meant to hijack the thread.

----------


## Schmidt

> I think the overcomplicating comes, because of the following disadvantages that comes from using the simple plain VB6 approach:
> - AxExe can't be RegFree


That's true - but on the other hand, only a single start with "run as Admin" would be required, to make things work.

And as said in the other thread... 
This AxExe-approach is a good (API-free) way to learn how to handle the STA-based VB6-threading.
 It is also quite easy, to "move stuff from the AxExe" over into an AX-Dll (without larger code-changes),
and then do the threading via a normal StdExe + Ax-Dll-based STA-thread(instances).




> - The OCX that is used within the AxExe must be pre-compiled. Thus not very flexible when changing logic quite often in the threaded process.


This can be easily covered, by developing/debugging these things in a VB6-Project*Group*.
In the concrete example at hand, you will only have to add the existing "ExternalControl.vbp" to the opened AxThreading.vbp
(and save this Group as e.g. "_Debug.vbg").

Olaf

----------


## The trick

Thanks for example.




> This AxExe-approach is a good (API-free) way to learn how to handle the STA-based VB6-threading.


It seems the other way around is hiding the whole "kitchen". What can i know if i work with an object in other STA? VB6 hides all the things and seems we can't learn how to handle the STA-based VB6-threading because it isn't differ from usual object.

----------


## Schmidt

> What can i know if i work with an object in other STA? VB6 hides all the things 
> ...seems we can't learn how to handle the STA-based VB6-threading because it isn't differ from usual object.


But exactly that (the fact that the ThreadObject is "just another Object") is the beauty of the VB6-STA-based threading-approach.
Why give it away?

These STA-threads are basically like "a process" (with regards to "isolated memory-management" via TLS).

And no, there's still something to learn, when working with these "STA-Objects".

These Objects already cover "synchronous communication" out of the box automatically (after you instantiated them on their own STA) - 
so there's no need, to write any "special sync-code" via Mutexes or CriticalSections (as needed in FreeThreading-approaches).

What these Thread-Objects don't cover though (the part which needs to be "learned"), is - 
how to trigger actions on them in an asynchronous fashion (whilst providing the Thread with the PayLoad-data it has to process).

Once that "async-part" was learned (not contained in the demo-example here), the shown approach 
(no matter if handled within an Ax-Exe or via Ax-Dlls) is a very robust way to cover nearly all threading-scenarios in VB6.

Olaf

----------


## The trick

> But exactly that (the fact that the ThreadObject is "just another Object") is the beauty of the VB6-STA-based threading-approach.
> Why give it away?


The threading in COM is very big area. You told about learning threading-handle. What's the threading handle differs from usual object? I understand the VB6 hides many things like message-pumping, marhsling, etc. Maybe it's useful for "just use" but i think this isn't useful for learning.




> These STA-threads are basically like "a process" (with regards to "isolated memory-management" via TLS).


This is many pitfails like if you use thread-pool then you can got the same STA and your global variables become shared with others.




> These Objects already cover "synchronous communication" out of the box automatically (after you instantiated them on their own STA) -
> so there's no need, to write any "special sync-code" via Mutexes or CriticalSections (as needed in FreeThreading-approaches).


This is the basic COM rule. The COM ensures that - not VB6. If you use for example C++ you also don't need "special sync-code" if you use a marshaled COM object. The potential "learner" don't know about that things at all in VB6 - the what i tell about.




> What these Thread-Objects don't cover though (the part which needs to be "learned"), is -
> how to trigger actions on them in an asynchronous fashion (whilst providing the Thread with the PayLoad-data it has to process).


Generally this task isn't related to multithreading and especially COM/STA/etc. You can use that scenario in a single threaded application. The typical task - accept windows messages which are placed asynchronously.

----------


## Schmidt

> The threading in COM is very big area.


Yes, threading in COM is a wide field - but that's not what I thought we were talking about (in the context of this Demo-post here).
I'm talking specifically about "*VB6*-STA-based threading, the easy way" (as demonstrated in the AxExe-based example in Post #1 here).




> You told about learning 
> Maybe it's useful for "just use" but i think this isn't useful for learning.


Exactly! ... "just use" these (Thread-)Objects like any other (normal) Object.
A VB6 beginner (or hobbyist) does not need to know (in detail) what an STA is, or how TLS works (underneath).

All he needs to know is, that CreateObject(AxExe.ProgID) will create an Object on its own Thread-Apartment,
present in the calling main-thread via a returned Object-Ref, which represents already a "communication-interface to the real Object in said thread".

Threading can really be that easy (when one uses VB6 AxExes).

And a StdExe+AXDll approach can be made to behave in the very same way (when we talk about User-Code).
One just needs to provide a Helper-routine, which will return a Proxy-instance in the same way as the CreateObject-call does in an AXExe
(which would make a switch to a real regfree working version easier - because the UserCode from the AxExe can mostly remain the same).

BTW - I don't know what got you so "riled up" here - but I assume it was my mentioning of "ASM-hacks".
And yes, I don't like them - and don't use them personally for several (good) reasons.

They are mostly used, to "avoid additional Dll-dependencies" (e.g. written in a more suitable language, without any need for ASM-usage there).
And this stance of "avoiding Dll dependencies like the plague" is (IMO) one of the main-reasons, 
why the VB6-community remains stuck in that "Single-Exes are all I know and want"-universe.

Olaf

----------


## The trick

> Yes, threading in COM is a wide field - but that's not what I thought we were talking about (in the context of this Demo-post here).
> I'm talking specifically about "VB6-STA-based threading, the easy way" (as demonstrated in the AxExe-based example in Post #1 here).


Notice what's i quoted in the first post. It was about learning how to handle the STA-based VB6-threading.




> Exactly! ... "just use" these (Thread-)Objects like any other (normal) Object.
> A VB6 beginner (or hobbyist) does not need to know (in detail) what an STA is, or how TLS works (underneath).


Exactly what i told about in the first post. One doesn't not need to know about STA, etc. What he/she can learn regarding threading? The question i asked in the first post.




> And a StdExe+AXDll approach can be made to behave in the very same way (when we talk about User-Code).
> One just needs to provide a Helper-routine, which will return a Proxy-instance in the same way as the CreateObject-call does in an AXExe
> (which would make a switch to a real regfree working version easier - because the UserCode from the AxExe can mostly remain the same).


Yes, you're right. I didn't argue with that.




> BTW - I don't know what got you so "riled up" here - but I assume it was my mentioning of "ASM-hacks".


No. It was when you said the all other methods are "inherently instable and not generically usable":



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


Although you told about "by doing the usual thing, handling the incoming thread-call via a TypeLib-defined PostMessage-delegation". Isn't that the VB6-Runtime-Hack?




> They are mostly used, to "avoid additional Dll-dependencies" (e.g. written in a more suitable language, without any need for ASM-usage there).


Does it bother you that VB6/MSVBVM60 itself uses this? I mean ASM-thunks etc. 




> And this stance of "avoiding Dll dependencies like the plague" is (IMO) one of the main-reasons,
> why the VB6-community remains stuck in that "Single-Exes are all I know and want"-universe.


It doesn't matter for our discussion. I didn't argue with that and moreover i have other opinion about that.

----------


## Schmidt

> Notice what's i quoted in the first post. It was about learning how to handle the STA-based VB6-threading.
> What he/she can learn regarding threading? The question i asked in the first post.


Ok, then again (slowly - your english-comprehension is certainly not the best)...
What I wrote - and what you quoted was:
_"This AxExe-approach is a good (API-free) way to learn how to handle the STA-based VB6-threading."_

So I was apparently talking about the AX-Exe-based threading-approach (in the context of the posted Demo-Code).
 (which hides "all the low level STA- and TLS-stuff" from the User of this approach - but "STA-stuff it is, all the same").

So, the only thing a User of that approach has to learn is:
- how to deal with the "Object-representation" (of the Main-Object which was created on such an STA)
(which is "simple enough" with regards to the learning-efforts - but "time to learn it" has to be invested all the same)

What a potential User of this AxExe-approach has *not* to learn is:
- how STAs work in detail
- how TLS works in detail
- how to hack the vb6Runtime
- how to maintain ASM-code-snippets (when certain things change, or are buggy)
- or how to avoid "certain things" which might be instable with runtime-hacked threading

I mean, all those "jsvenu-threads" in the main-forum were going on, for nearly a hundred postings - 
and still he does not have managed his threading-scenario (using an "advanced approach").

And I'm pretty sure, he's not the only one - my guess is, that 95% of the Users "just want to use threads".
And I'm also quite sure, that most of these Users would be able, to get an adaption of the simple approch (as shown here) - 
to work for them in their own threading-scenarios (without crashes or instabilities).

So, "learning they have to do" - but far less than with the "other alternatives, which overcomplicate things" (IMO).
I hope you understood it now more clearly (having these 95% of "normal VB6-Users in mind").




> It was when you said the all other methods are "inherently instable and not generically usable":


Ok - so you're sure, that your modMultiThreading2.bas-module is now entirely bug-free?
No matter "which Project-Type it is used from" (Ax-Exe, Ax-Dll, Std-Exe)?





> ...you told about "by doing the usual thing, handling the incoming thread-call via a TypeLib-defined PostMessage-delegation". 
> Isn't that the VB6-Runtime-Hack?
> 
> Does it bother you that VB6/MSVBVM60 itself uses this? I mean ASM-thunks etc.


It is not my intention, to "rile you up" - I'm arguing from a point of "reliable usage" (for those afore-mentioned "95% of VB6-Users").

And no, TypeLib-calls are definitely not "hacks" - 
they work entirely within the "normal VB6- and COM-realm and ABI" (no matter if those calls are defined with "avoid Err-checking", or not).

It is "usage of predefined functionality".
Hacks are, when you "change predefined functionality" (which you do..., e.g. when you manipulate around at the TLS-slots and whatnot in the vbRuntime itself).

Note that msvbvm60.dll is still maintained (it is part of system-updates) - and so it might change its internal behaviours, structures and offsets...

So, no - I'm still not a fan of such hacks - especially in cases, where they don't offer much over other, simpler alternatives which "play by the rules".

Olaf

----------


## xiaoyao

new method download (can you test?):
createthread for load new form with ocx or usercontrol demo:

*use hook api for fix eip
(EbLoadRunTime
EbCreateContext
EbSetContextWorkerThread)
*i don't khnow your project can support usercontrol?

http://www.vbforums.com/showthread.p...ol)&highlight=

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

----------


## xiaoyao

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


```
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
```

download【http://www.vbforums.com/attachment.p...059696】

----------


## The trick

> So, the only thing a User of that approach has to learn is:
> - how to deal with the "Object-representation" (of the Main-Object which was created on such an STA)
> (which is "simple enough" with regards to the learning-efforts - but "time to learn it" has to be invested all the same)


Please read my post carefully. I won't touch your "english-comprehension" but i didn't get the answer to my question yet. What's the difference if an user works with usual object between your "ThreadObject"? You wrote - there is no difference and it's good. I know about this but i answered then what the user learn about? There is NO difference if the user works for example with Word.Application. Do you think the user thought whenever he work with threading in this case?




> What a potential User of this AxExe-approach has *not* to learn is:
> - how STAs work in detail
> - how TLS works in detail
> - how to hack the vb6Runtime
> - how to maintain ASM-code-snippets (when certain things change, or are buggy)
> - or how to avoid "certain things" which might be instable with runtime-hacked threading


An user shouldn't know how the runtime works and your last 4 points isn't related to VB6-STA-COM-threading.
From your example an user won't know:
- What's the Apartment model;
- What models exist;
- What's the difference between VB6-threading models;
- What's the marshaling, why we need to use that;
- What's the message pumping. Why when i use a long-running cycle/sleeping my threads are freezes?
- Why my global variables are always "zeroed"? Why sometimes they are "not-zeroed"?
- Why can't i accept some callbacks and my application crashes?
- . . .
- . . .




> I mean, all those "jsvenu-threads" in the main-forum were going on, for nearly a hundred postings -
> and still he does not have managed his threading-scenario (using an "advanced approach").


Please study his thread if you want to talk about "jsvenu-threads". He want to use "free-threading" which isn't acceptable in VB6 (only in the restricted scenarios).




> And I'm pretty sure, he's not the only one - my guess is, that 95% of the Users "just want to use threads".
> And I'm also quite sure, that most of these Users would be able, to get an adaption of the simple approch (as shown here) -
> to work for them in their own threading-scenarios (without crashes or instabilities).


I already wrote your example is perfect to "just use". You can write me statements twice but i understand the first time.




> So, "learning they have to do" - but far less than with the "other alternatives, which overcomplicate things" (IMO).
> I hope you understood it now more clearly (having these 95% of "normal VB6-Users in mind").


I already wrote they learn nothing regarding to threading from your example.




> Ok - so you're sure, that your modMultiThreading2.bas-module is now entirely bug-free?
> No matter "which Project-Type it is used from" (Ax-Exe, Ax-Dll, Std-Exe)?


No. I'm not sure and i already wrote you. I won't duplicate the posts. Do you think your solution is bug-free?




> It is not my intention, to "rile you up" - I'm arguing from a point of "reliable usage" (for those afore-mentioned "95% of VB6-Users").


Okay but please see to thread you wrote. The thread about creation Native DLL and avoiding TypeLibs/etc.




> And no, TypeLib-calls are definitely not "hacks" -
> they work entirely within the "normal VB6- and COM-realm and ABI" (no matter if those calls are defined with "avoid Err-checking", or not).


Hmmm. I don't tell about TypeLib usage in common sense rather threading one. Why then you use it at free-threading manner? Isn't a hack?




> It is "usage of predefined functionality".
> Hacks are, when you "change predefined functionality" (which you do..., e.g. when you manipulate around at the TLS-slots and whatnot in the vbRuntime itself).


Who manipulate TLS?




> Note that msvbvm60.dll is still maintained (it is part of system-updates) - and so it might change its internal behaviours, structures and offsets...


Yes. Even moreover they do it but they never broke the backward-compatibility. But what's changed if for example the some structure is changed?




> So, no - I'm still not a fan of such hacks - especially in cases, where they don't offer much over other, simpler alternatives which "play by the rules".


The solution is selected from task and preferences. Your solution isn't cover all the tasks and preferences and isn't alternative. I understand it and use the solution which is suitable for the current task. You can use what you want.

----------


## xiaoyao

I think there will always be a way ,no need tls?
a simple method for support show forms with vb6 usercontrol or ocx  in new thread.

----------


## Schmidt

> Please read my post carefully. I won't touch your "english-comprehension" ...


But it *is* your bad english-comprehension which caused all this "off-topic talking in circles".

You seemed to think, I wanted to explain and teach about "STAs".

But that's not what I said... here again what I wrote:
_"This AxExe-approach is a good (API-free) way to learn how to handle the STA-based VB6-threading."_

Note the parts I've marked blue, because they take "precedence" (grammar-wise).

It's basically the same as the following sentence:
_"This is a good way to learn how to handle combustion-engine-based cars."_

It's all about "car-handling" - not about "the inner workings of a combustion-engine".

HTH

Olaf

----------


## jsvenu

> As the title says, just a simple Demo which might help to "find ground again", when it comes to VB6-threading.
> (there's certainly a whole lot of overcomplicating, unnecessary stuff out there).
> 
> The Demo shows real InProcess-Multithreading via a simple VB6-AX-Exe-Project.
> 
> There are no API-calls necessary, nor are there any Assembly-hacks - it's just plain VB6 - running threaded Forms in STAs.
> 
> Within the threaded Form, an external (OCX-based) Control gets loaded (since that recently came up).
> To get things running:
> ...


Dear olaf,

 I changed the AxExeThreading2 activex exe project in AxExeThreading2.zip to *standard exe* .
When I run this standard exe  application I am able to display two forms in different threads which  can be observed on the caption of the form which displays thread ID and the two forms are showing different thread IDs in their caption.But when we try to switch between forms by clicking on them we get the following error (in msgbox):

*the action cannot be completed because other application is busy  ...*

What are the *changes*  to be made *in code* for this *standard exe* to *create new form with user control in new thread without using vbheader* in a simple way *without displaying the following  error(in msgbox)* when we click on different forms displayed in different threads for switching between them.



regards,
JSVenu

----------


## jsvenu

> As the title says, just a simple Demo which might help to "find ground again", when it comes to VB6-threading.
> (there's certainly a whole lot of overcomplicating, unnecessary stuff out there).
> 
> The Demo shows real InProcess-Multithreading via a simple VB6-AX-Exe-Project.
> 
> There are no API-calls necessary, nor are there any Assembly-hacks - it's just plain VB6 - running threaded Forms in STAs.
> 
> Within the threaded Form, an external (OCX-based) Control gets loaded (since that recently came up).
> To get things running:
> ...


Dear Olaf,

Finally I solved the  problem of using usercontrol in the same activex exe as follows without having to create new activex dll as follows using Trick's help(code attached):

Add the following method to cThread



```
Public Sub SetThisThreadToApartmentModel()
    hModule = App.hInstance
    modMultiThreading.GetVBHeader
End Sub
```

Call the above method in cmdCreateThreadedForm_Click as follows:



```
Private Sub cmdCreateThreadedForm_Click()
    Dim Thread As cThread
    Set Thread = CreateObject(GetProjectLibName & ".cThread")                   'create a new Thread on its own STA
    Thread.SetThisThreadToApartmentModel                                        'set this thread to Apartment model
    Thread.CreateAndShowThreadedForm                                            'create a new Form within that STA by calling a Public Method of the ThreadInstance
    ThreadInstances.Add Thread                                                  'store this ThreadInstance within a Thread-Collection (for later re-usage)
End Sub
```

Here 



```
Public 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
    GetMem1 ByVal tempheader + &H3C, bThreading
    bThreading = bThreading Or 1
    VirtualProtect ByVal tempheader, 4, PAGE_READWRITE, lOldProtect
    GetMem1 bThreading, ByVal tempheader + &H3C                     'set to apartment model as per Trick's help
    VirtualProtect ByVal ptr, 4, lOldProtect, 0
    GetVBHeader = tempheader
End Function
```

Thankyou Olaf one again for the example.


Dear Trick,


 You have provided multithreading examples in which each thread has a copy of the main vbheader modified using CreateVBHeaderCopy module function which takes care of avoiding global variables from being cleared.

 Can you clarify me  since we are using vbheader only in the new thread without  using CreateVBHeaderCopy module function  how  we can avoid clearing of global variables here in this attached activex exe application.





regards.

JSVenu

----------


## The trick

> But that's not what I said... here again what I wrote:
> "This AxExe-approach is a good (API-free) way to learn how to handle the STA-based VB6-threading."


Olaf,
I understood what you said. I just asked you - "what's exactly"? The working with "your" threading isn't differs from an usual object or any other AxExe. It's the same if i'd make like CreateObject("Word.Application"). Do you think someone think about threading when works with Word.Application? It's the same. There is NO difference between usual object and your threading object. And i asked what's the exactly a potential user can learn if it's the same. As i wrote before - "just for use". Threading for threading.




> "This is a good way to learn how to handle combustion-engine-based cars."
> 
> It's all about "car-handling" - not about "the inner workings of a combustion-engine".


But your example looks like learning toy-car-handling. We don't have the steering wheel, the brakes, etc. We just "handle" the cars by pushing - they are going.

----------


## xiaoyao

CreateThread by Activex Exe In vb6(Support Shared variables)-VBForums
http://www.vbforums.com/showthread.p...69#post5451469



```
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
```

----------


## jsvenu

> CreateThread by Activex Exe In vb6(Support Shared variables)-VBForums
> http://www.vbforums.com/showthread.p...69#post5451469
> 
> 
> 
> ```
> ActivexExeThread.bas
> Public Src As String '主变量
> Public MEM(0) As String
> ...


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

----------


## jsvenu

Dear Trick,

In the activex exe application supporting usercontrol once we display user control in additional thread forms when we close the application it is still running.Can you provide code for properly closing the application so that it closes properly.

Link: http://www.vbforums.com/attachment.p...1&d=1581403109

regards,
JSVenu

----------


## xiaoyao

> Dear Trick,
> 
> In the activex exe application supporting usercontrol once we display user control in additional thread forms when we close the application it is still running.Can you provide code for properly closing the application so that it closes properly.
> 
> Link: http://www.vbforums.com/attachment.p...1&d=1581403109
> 
> regards,
> JSVenu


createthread(activex.exe),why threadid is the same?

----------


## jsvenu

> Dear Trick,
> 
> In the activex exe application supporting usercontrol once we display user control in additional thread forms when we close the application it is still running.Can you provide code for properly closing the application so that it closes properly.
> 
> Link: http://www.vbforums.com/attachment.p...1&d=1581403109
> 
> regards,
> JSVenu


Dear Trick,

  When I tried to use the following code for properly unloading * still the application  is running* when I close the application after displaying a usercontrol by setting the form thread to apartment model.I had to use task manager to kill the running activex exe application.Please clarify.



```
Public Declare Function VBDllCanUnloadNow Lib "MSVBVM60.DLL" (ByVal pVbHeader As Long) As Long

Public Sub UnloadThreadedForm()
    If Not ThreadForm Is Nothing Then Unload ThreadForm: Set ThreadForm = Nothing
    ' // The runtime will call CThreadPool::CheckForProjectUnload and CVBThreadAction::CleanupProjData
    If VBDllCanUnloadNow(modMultiThreading.GetVBHeader) Then
        Exit Sub
    End If
End Sub
```

regards,
JSVenu

----------


## jsvenu

> createthread(activex.exe),why threadid is the same?


dear Xiaoyao,
you might have changed project setting to thread pool.Change to thread per object and try.
regards,
JSVenu

----------


## xiaoyao

can you testing?I try ,it's error happened same threadid

----------


## xiaoyao

> Olaf,
> I understood what you said. I just asked you - "what's exactly"? The working with "your" threading isn't differs from an usual object or any other AxExe. It's the same if i'd make like CreateObject("Word.Application"). Do you think someone think about threading when works with Word.Application? It's the same. There is NO difference between usual object and your threading object. And i asked what's the exactly a potential user can learn if it's the same. As i wrote before - "just for use". Threading for threading.
> 
> 
> But your example looks like learning toy-car-handling. We don't have the steering wheel, the brakes, etc. We just "handle" the cars by pushing - they are going.


I don't known how to run sta threads in standard exe?For example, some multithreading needs to have access to global variables, and some are completely isolated from each other. (比如有的多线程需要可以访问全局变量，有的彼此完全隔离。)

----------


## jsvenu

> can you testing?I try ,it's error happened same threadid


Dear xiaoyao,

Have you tried the same attachment. http://www.vbforums.com/attachment.p...1&d=1581403109
Just make exe and run the compiled exe.

regards,
JSVenu

----------


## jsvenu

> Dear Trick,
> 
>   When I tried to use the following code for properly unloading * still the application  is running* when I close the application after displaying a usercontrol by setting the form thread to apartment model.I had to use task manager to kill the running activex exe application.Please clarify.
> 
> 
> 
> ```
> Public Declare Function VBDllCanUnloadNow Lib "MSVBVM60.DLL" (ByVal pVbHeader As Long) As Long
> 
> ...


Dear Trick,

Can we not close the application properly in which we have threads with vbheader set to apartment model like above by unloading all the resources like above and see that the application closes completely without running as we see it running in task manager.

regards,
JSVenu

----------


## xiaoyao

I like postmessage wm_quit to main form,
but your project not support,so can't end process

----------


## jsvenu

> I like postmessage wm_quit to main form,
> but your project not support,so can't end process


Dear Xiaoyao,

I think we can use *ExitThread* API with 0 as the parameter after calling VBDllCanUnloadNow in post *#22*  for closing the additional threads which are set to apartment model (to support user control) for which *Trick* may suggest any changes.

regards,
JSVenu

----------


## jsvenu

Dear Trick,

In the link below 
http://www.vbforums.com/attachment.p...3&d=1581316682

When we run the activex exe application with thread per object setting and try to display a new form in new thread by clicking on *Create and show an additional threaded Form*  button of *main form* we get a new form displayed in *new thread* and it supports loading user control at runtime which can be verified by clicking on LoadUsercontrol in the new displayed form.*Upto here everything is fine*.

But from *now onwards when we again* try to display a new form in new thread by clicking on

*Create and show an additional threaded Form*  button of *main form* we get a new form displayed in *same main thread* but not new thread.Ofcourse it supports user control loading at runtime.

*Before displaying I was setting the thread to apartment model by modifying vbheader*.

Even though the above activex exe supports loading user control of the same project at runtime *why*  I get new form in new thread only first time and after that the new form is always created in the main thread only.*This can be checked by seeing  new  form's thread id in its Caption*.

*Here I think as you associate new vbheader copy with each thread I have to use new vbheader copy for each thread as you already showed in standard exe.*

*Can you show me how to code such that we have new vbheader copy for each thread in activex exe application so that the activex exe application creates new form in new thread whenever I click on "Create and show an additional threaded Form"  button in main thread default form so that the new form displayed in new thread supports runtime user control loading for which I shall be thankful to you*.

regards,
JSVenu

----------


## Schmidt

> Dear Trick,
> 
> ...
> *Can you show me how to code such that we have new vbheader copy for each thread in activex exe application...
> *


I can only repeat what I've posted already several times...

Please don't use these "vbHeader-hacks", because:
- they introduce instabilities 
- they crash outright (e.g. when you compile to PCode)
- and are not needed at all, for proper threading in VB6

The whole CodeBank-entry here was provided, to show a truly safe way with VB6-threading (avoiding the hacks).

I've even addressed your "Thread-Form-issues" (with Project-Private UserControls) appropriately,
by uploading a second example with support for that scenario (by encapsulating these thread-forms in their own Ax-Dll).

ActiveX-Dlls are (generally) the proper place for "VB6-WorkerThread-code" (including Form-Definitions, when they shall run on their own thread)-
 And such AX-Dll-hosted WorkerThread-code will run properly, even when you use VB6-STA-based threading without AxExes.

Ax-Dlls can easily be shipped in a \Bin\-folder beside your "normal StdExe-binary" in a true portable fashion (no registering needed).
I have no clue, why so many in the VB-community are trying to avoid writing their own Ax-Dlls - there's absolutely no reason for that.

HTH

Olaf

----------


## Schmidt

> ...your example looks like learning toy-car-handling.


Nope, cannot agree with that.

The built-in threading-support (via COM-STAs and their automatic synchronizing) 
is (for me) comparable with a "Luxus-Van" ...

It has a whole lot of comfort- and safety-features built-in (comparable to "ESP and ABS" + "airbags in every corner") -
and it will cover 99% of all threading-scenarios (on normal roads).

Whereas "the hacks" look like some "go-cart, kept together with a lot of duct-tape, always hoping the wheels don't fly off".

If you really have to go "offroad" with your threading, then there's better suited (language-)vehicles out there.

Olaf

----------


## jsvenu

> I can only repeat what I've posted already several times...
> 
> Please don't use these "vbHeader-hacks", because:
> - they introduce instabilities 
> - they crash outright (e.g. when you compile to PCode)
> - and are not needed at all, for proper threading in VB6
> 
> The whole CodeBank-entry here was provided, to show a truly safe way with VB6-threading (avoiding the hacks).
> 
> ...


Dear Olaf,

Thankyou  for the reply.

regards,
JSVenu

----------


## The trick

> Before displaying I was setting the thread to apartment model by modifying vbheader.


I don't know how to explain you shouldn't use that module for ActiveX EXE. This module is intended for Standard EXE usage. ActiveX EXE supports threading out-of-box.




> The built-in threading-support (via COM-STAs and their automatic synchronizing)
> is (for me) comparable with a "Luxus-Van" ...
> 
> It has a whole lot of comfort- and safety-features built-in (comparable to "ESP and ABS" + "airbags in every corner") -
> and it will cover 99% of all threading-scenarios (on normal roads).
> 
> Whereas "the hacks" look like some "go-cart, kept together with a lot of duct-tape, always hoping the wheels don't fly off".
> 
> If you really have to go "offroad" with your threading, then there's better suited (language-)vehicles out there.


"The hack" is to use an external ActiveX OCX in order to use an internal user control i think. So i can use an external exe (or even the same exe) and use it for my threading scenarios by communication over a moniker. Moreover i don't need the registration in this case. You didn't show a real threading scenario as i wrote "threading-for-threading".

----------


## jsvenu

> I don't know how to explain you shouldn't use that module for ActiveX EXE. This module is intended for Standard EXE usage. ActiveX EXE supports threading out-of-box.
> 
> 
> "The hack" is to use an external ActiveX OCX in order to use an internal user control i think. So i can use an external exe (or even the same exe) and use it for my threading scenarios by communication over a moniker. Moreover i don't need the registration in this case. You didn't show a real threading scenario as i wrote "threading-for-threading".


Dear Trick,

 I was using vbheader only to set the thread created using CreateObject to apartment model only in order to load user control to the form in new thread since as you said it is the only way I know to access user control.Moreover  once we set to apartment model by starting new thread we are unable to terminate  the application properly since it is running in taskmanager even after closing the application.

regards,
JSVenu

----------


## The trick

> I was using vbheader only to set the thread created using CreateObject to apartment model only in order to load user control to the form in new thread since as you said it is the only way I know to access user control.Moreover once we set to apartment model by starting new thread we are unable to terminate the application properly since it is running in taskmanager even after closing the application.


You shouldn't use the module for ActiveX EXE projects. ActiveX EXE already have the ability to create the threads. If you have a question about the usage/issues of the VbTrickThreading module in the Standard EXE then you can create a new thread. Please STOP hijack this thread and others! I won't answer you about VbTrickThreading in this thread anymore.

----------


## jsvenu

> You shouldn't use the module for ActiveX EXE projects. ActiveX EXE already have the ability to create the threads. If you have a question about the usage/issues of the VbTrickThreading module in the Standard EXE then you can create a new thread. Please STOP hijack this thread and others! I won't answer you about VbTrickThreading in this thread anymore.


Dear Trick,

Ok Sorry for using vbheader in act exe for loading the same project user control in new thread form  at runtime.

Dear Olaf,

Can you suggest any other way to load the same project existing user control in form of new thread  at runtime without using vbheader for a activex exe application.

regards,
JSVenu

----------


## Schmidt

> You didn't show a real threading scenario ...


Maybe the new tutorial I've just posted into the codebank, is more helpful in understanding 
"how to handle ThreadObjects on COM-STAs" (without going too deep into what happens "underneath those STAs")...

It's doing that in several steps (starting in Step0 with a "monolitic, nonthreaded Mandelbrot-rendering") - 
and evolves from there - over AxExe-based Steps - to AxDll-based STA-threading in the last Steps - 
ending with an "automatically filled Bin-Folder" for a regfree deployable, portable StdExe...

http://www.vbforums.com/showthread.p...rot-Rendering)

Olaf

----------


## The trick

> Maybe the new tutorial I've just posted into the codebank, is more helpful in understanding 
> "how to handle ThreadObjects on COM-STAs" (without going too deep into what happens "underneath those STAs")...
> 
> It's doing that in several steps (starting in Step0 with a "monolitic, nonthreaded Mandelbrot-rendering") - 
> and evolves from there - over AxExe-based Steps - to AxDll-based STA-threading in the last Steps - 
> ending with an "automatically filled Bin-Folder" for a regfree deployable, portable StdExe...
> 
> http://www.vbforums.com/showthread.p...rot-Rendering)
> 
> Olaf


Thank you for the example. I'll study your example.




> (without going too deep into what happens "underneath those STAs").


I see you use *CoMarshalInterThreadInterfaceInStream* and *CoGetInterfaceAndReleaseStream* in the code to marshal an object reference between threads. You also use the message pumping to process the inter-threads communication. You use CreateThread API (free-threading) to create STA, manual synchronization (why cycle with sleep?), no marshaling for parameters. Literally the same things i used in my module.
Note, I am not against this, but you just said the opposite.

----------


## Schmidt

> I see you use *CoMarshalInterThreadInterfaceInStream* and *CoGetInterfaceAndReleaseStream* in the code to marshal an object reference between threads.


Yes, of course - that's the usual (STA-conform) way for Proxy/Stub based Object-communication.




> You also use the message pumping to process the inter-threads communication.


Yes, because every proper STA should have such a loop.




> You use CreateThread API (free-threading) to create STA,


I have to, because this API is the only official way on a Win-OS, to create - well - a thread.
Any language who wants to establish an STA (on a new thread), has to call this API at some point.

And no - FreeThreading (in the real sense) implies also cross-thread-sharing of (global) variables - 
which I did not try to attempt (by hacking VB6 with regards to avoiding the built-in ThreadLocalStorage).




> ... (why cycle with sleep?)


You mean implicitely - via DoEvents (and its "hidden Sleep(0) call")?

Well, I've found - when developing/testing the (threaded) RPCserver-support in RC5 (decades ago) - 
 that a final Sleep(0) call in the "pump-loop" was increasing performance under heavy concurrent load.

So please read, what the MSDN has to say (about Sleep with Param 0) - 
or alternatively this discussion: https://stackoverflow.com/questions/...ance-of-sleep0




> Literally the same things i used in my module.


I've never said, that *everything* in your threading-modules was "a hack".

And in case you meant, I've "stolen something" -  well, I didn't.
In the new Demo, I've restricted myself to "only the things which are known to be safe to use" 
(especially when the whole STA-establishing stuff is encapsulated in a Dll, as in my example).




> Note, I am not against this, but you just said the opposite.


I said, that the "interna of STAs" are not, what a User (of an STA-threaded solution) should "bother with".

They are (black-box-like) hidden when the builtin AxExe-threading is used - 
and they are "hidden in a Dll-binary", when StdExes want to use the same STA-based threading 
(as shown in my example, or when using DirectCOM.dll from the RC5-package).

Much more important (and time-consuming) with regards to threading, 
is it (for newcomers) to learn what I described in the Readme of the new Tutorial for Step1:
- how to split-up (the load of) a monolithic routine into "concurrently runnable sub-parts".

As soon as this Step1 is done, the rest is relatively easy (in most threading-scenarios) - 
aside from synchronization-efforts (which with that normal STA-based VB6-threading, are basically non-existent).

Olaf

----------


## The trick

> I have to, because this API is the only official way on a Win-OS, to create - well - a thread.
> Any language who wants to establish an STA (on a new thread), has to call this API at some point.


Not only CreateThread.




> And no - FreeThreading (in the real sense) implies also cross-thread-sharing of (global) variables -
> which I did not try to attempt (by hacking VB6 with regards to avoiding the built-in ThreadLocalStorage).


In your opinion, if we do not use global variables, then this turns out to be a non-free-threading? But free-threading means you can safe call the methods without marshaling across threads. The second question - who hacking TLS?




> You mean implicitely - via DoEvents (and its "hidden Sleep(0) call")?
> 
> Well, I've found - when developing/testing the (threaded) RPCserver-support in RC5 (decades ago) -
> that a final Sleep(0) call in the "pump-loop" was increasing performance under heavy concurrent load.
> 
> So please read, what the MSDN has to say (about Sleep with Param 0) -
> or alternatively this discussion: https://stackoverflow.com/questions/...ance-of-sleep0


I mean there is much suitable way by using special Wait functions (like WaitForSingleObject).




> I've never said, that *everything* in your threading-modules was "a hack".
> 
> And in case you meant, I've "stolen something" - well, I didn't.
> In the new Demo, I've restricted myself to "only the things which are known to be safe to use"
> (especially when the whole STA-establishing stuff is encapsulated in a Dll, as in my example).


No-no. You (and others) can use my codes even don't mention me (i always told it). This isn't problem. I meant other. When i told you - "an user should know it" you answered "A VB6 beginner (or hobbyist) does not need to know (in detail) what an STA is, or how TLS works (underneath)."
If you would demonstrate the Mandelbrot example at the first post i didn't begin this discussion about learning because it would have no sense. Just what i told about you've implemented in the new example which is very good and useful. Just imagine if your first post was with this example do my following post have sense?




> I said, that the "interna of STAs" are not, what a User (of an STA-threaded solution) should "bother with".


What's the internal? I already wrote about that (MessagePump, Marshaling, etc.). This sounds very strange you made the example with that internal things and you told user shouldn't bother about that. How should an user go steps to StdEXE+DLL from AxEXE without that things?




> They are (black-box-like) hidden when the builtin AxExe-threading is used -
> and they are "hidden in a Dll-binary", when StdExes want to use the same STA-based threading
> (as shown in my example, or when using DirectCOM.dll from the RC5-package).


Again  :Roll Eyes (Sarcastic): . They aren't hidden in your example. When you need to perform an async task (the multithreadin is used for) you use this things and you need to know that things to avoid the typical threading errors like deadlocks, etc.

----------


## Schmidt

> Again . They aren't hidden in your example. When you need to perform an async task (the multithreadin is used for) you use this things and you need to know that things to avoid the typical threading errors like deadlocks, etc.


I seriously don't know, why you're deliberately "playing dumb" in this thread (across so many postings).

Instead of "Step6" of this tutorial, I'd have personally preferred to just include a  C-compiled "CreateWorkerObjectOnSTA_Helper.dll" 
(or one, developed in FreeBasic, or C++, or FreePascal... meaning that those other languages would have offered an easier implementation-path, because they support FreeThreading out-of-the-box)

Such a "BlackBox"-binary (similar to DirectCOM.dll - which was implemented using PowerBasic - and does that kind of thing for nearly two decades now),
is the only thing needed, to "close the gap, to what MS already did implement - hidden - but sadly only for VB6-Apps in ActiveX-Exe-mode).

In both cases: 
- AxExe-based new STA-Object-creation 
- and "BlackBox-dll-based creation" of such STA-WorkerObjects,
the "bothersome details of STAs" will remain hidden from the User 
(which is a good thing, because they only distract from what's really needed, to develop stable, STA-based VB6-threading-solutions).

I've included this Step6-Project as VB6-source only, because the Forum-rules forbid the upload of Dll-binaries.

The real thread-implementations in TutorialFolders Step-7 and 8 - are both using only the compiled version of Step6 (via a reference to ThreadedSTAs.dll).
And only in those 2 Projects (*not in Step6*) is, where "the real VB6-threading-stuff happens" (which plays by the rules of such WorkerObjects on STAs).

Olaf

----------


## The trick

> Instead of "Step6" of this tutorial, I'd have personally preferred to just include a C-compiled "CreateWorkerObjectOnSTA_Helper.dll"
> (or one, developed in FreeBasic, or C++, or FreePascal... meaning that those other languages would have offered an easier implementation-path, because they support FreeThreading out-of-the-box)


Why should i use another language/additional dependency if i may use the same language? You've demonstrated it yourself. Why should i use "a black box" to achieve the task i can implement without it?




> In both cases:
> - AxExe-based new STA-Object-creation
> - and "BlackBox-dll-based creation" of such STA-WorkerObjects,
> the "bothersome details of STAs" will remain hidden from the User
> (which is a good thing, because they only distract from what's really needed, to develop stable, STA-based VB6-threading-solutions).


This isn't good thing. I've already wrote about that.




> I've included this Step6-Project as VB6-source only, because the Forum-rules forbid the upload of Dll-binaries.


You could refer to a dll/source code (developed in FreeBasic, or C++, or FreePascal). Hundreds of examples in codebank which refer to RC5. Does it bother you? Who are "playing dumb"? Let's tell about what you done not what you wanted.

I don't understand your position. You've created the example which teaches nothing. Just threading for threading. I've written about that and asked what could an user learn. You wrote the user couldn't know many things about STA-COM-threading. You called all other threading except your - bad/unstable/hacks/etc. But when you need the real threading things you use this things/unstable hacks. You tell me i'm "playing dumb" but I think it's not me.
Of course then you've written the real good threading example which is good for learning. If it would be originally I wouldn't start this discussion.

----------


## Schmidt

> I don't understand your position.


Deliberately so, I guess.




> You've created the example which teaches nothing.


Wrong, the two examples here (in this thread, are about AxExe-related threading), 
and they do the same thing (showing Forms on their own threads), as discussed in other threads (using your helper-modules).

But with a significant difference:
- they use less code
- they are much more stable
And if that's not an advantage which should be "made known" (or "taught"), then I don't know what else this forum is for...




> I've written about that and asked what could an user learn.


And I've explained that to you already several times.

If a threading-solution is more stable - and uses less code than an alternative - 
then it is clearly better - and the user should learn how to write threading-code for that better approach.




> You wrote the user couldn't know many things about STA-COM-threading.


No, I wrote that the user "doesn't need to know" (why STA-based threading works, as it works).
That's totally different from your "couldn't know" above.




> You called all other threading except your - bad/unstable/hacks/etc.


Well, that's because they clearly *are* (unstable hacks).

The main-point being:
- as soon as you "drag" thread-callback-code into a VB6-StdExe-Binary, your whole App.exe becomes "risky to run",
- especially when you enabled "global Variable sharing" along with these hacks

The only safe way to do threading in VB6 is:
1) to encapsulate your WorkerObject in a *Public* Class (and *Public* Classes are only possible in VB6's AxExe-, Dll-, and OCX-Projects, not in StdExes)
2) to place your Worker-Object on an STA (either via AxExes CreateObject-call, or via a *compiled* Helper-Dll when you do that from StdExe-code)

And whilst Krools threading-helper is breaking only rule #1) (halfways, via his Interface-callbacks),
your "threading-module helper" is breaking both of the above two rules...

In short - never, ever place VB6-based ThreadCallback-code directly within the Code-Modules of a StdExe.




> But when you need the real threading things you use this things/unstable hacks.


Nooo, I'm not doing that - I just wrote above, that the code which establishes a Worker-Object on an STA,
should be "hidden away in a binary" (in a compiled Dll).

In case of AxExes this "precompiled STA establishing code" is sitting in the vbRuntime-dll.
In case of StdExes this "precompiled STA establishing code" is sitting in ThreadedSTAs.dll.

The user should never include "editable (VB6)-Thread-Callback code" within his Executable-Project.
Only then will things run stable.

With your threading-helper *.bas module, you're outright encouraging Users,
to include this stuff into a StdExe-Project directly as Code.
Please don't do that - it's not stable - never was - and never will be (until a new compiler comes out).




> You tell me i'm "playing dumb" but I think it's not me.


Well, in the meantime I'm starting to think, that you "really don't get it".




> Of course then you've written the real good threading example which is good for learning.


If you're referring to the Mandelbrot-Tutorial, then this outright proves, that you understood nothing so far.

Because what I'm showing there, is exactly the same thing as I was showing here.

I'm establishing WorkerObjects on their own STAs (and show, how to work with them, "once they are there").

The only difference in the Mandelbrot-Tutorial is, that I made things more generic,
by introducing a cThreadHost- (+ cThreadPool) abstraction, which allows to instantiate these STA-WorkerObjects
(from *any* Ax-Dll) in a "non-specific, generic way" (via a ProgID to such an "outsourced" Ax-Dll-Class).

But the principle is the same - and very, very different from what you promote by: "just include my threadhelper.bas into your StdExes".

Olaf

----------


## xiaoyao

can use vbheader for activex.exe threading bu api createthread。
if only use createobject("activexExe.class1"),You cannot use global variables, and they run very slowly.

----------


## Schmidt

> can use vbheader for activex.exe threading bu api createthread。


As already stated several times, this is not recommended - and also not needed (in most VB6-threading-scenarios).




> if only use createobject("activexExe.class1"),You cannot use global variables, and they run very slowly.


There's dozens of different ways to communicate in a faster manner among thread-STAs:
- shared Memory-areas via SafeArrays only being one of them
- there's also Pipes, Sockets, FileMapping, shared SQLite-InMem-DBs
- or just plain "*pre-*transfer of Input-Parameters via normal COM-marshaling into thread-locally reachable Variables"

Neither of the above methods will require you, to incorporate vbHeader-hacking into your "threaded VB6-solution".

Olaf

----------


## The trick

> Wrong, the two examples here (in this thread, are about AxExe-related threading),
> and they do the same thing (showing Forms on their own threads), as discussed in other threads (using your helper-modules).


No. Those threads wasn't about just-create-a-form in thread the threads where you posted your example was about NativeDLL-callbacks.




> But with a significant difference:
> - they use less code
> - they are much more stable
> And if that's not an advantage which should be "made known" (or "taught"), then I don't know what else this forum is for...


We saw how can it be more stable with the private user controls. The user code amount is the same, you don't need touch the helper module.




> If a threading-solution is more stable - and uses less code than an alternative -
> then it is clearly better - and the user should learn how to write threading-code for that better approach.


I'm disagree. Decomposition to several projects isn't less code. The code wasn't about threading - you just created the objects in threads. No communication/no-async/no-advantages from threading.




> No, I wrote that the user "doesn't need to know" (why STA-based threading works, as it works).
> That's totally different from your "couldn't know" above.


And i wrote that's bad and explained. The bunch of jsvenu's questions about free-threading/why we need to marshal/free-marshaller etc. about STA/MTA.




> Well, that's because they clearly are (unstable hacks).


http://www.vbforums.com/showthread.p...=1#post5447257




> The main-point being:
> - as soon as you "drag" thread-callback-code into a VB6-StdExe-Binary, your whole App.exe becomes "risky to run",
> - especially when you enabled "global Variable sharing" along with these hacks


I don't know your knowledge about vb-internals but i know something about it. The runtime manages all the projects and each project is associated with the special structure(structures). There is no significant difference between project types so if you have a properly initialized project you can use it.
You can use the shared-variables - this isn't violates COM rules.




> The only safe way to do threading in VB6 is:
> 1) to encapsulate your WorkerObject in a Public Class (and Public Classes are only possible in VB6's AxExe-, Dll-, and OCX-Projects, not in StdExes)


You can use also StdEXE classes.




> 2) to place your Worker-Object on an STA (either via AxExes CreateObject-call, or via a compiled Helper-Dll when you do that from StdExe-code)


That's the base rule of COM regarding the single-threading apartments but you can't "place" and object to STA you can only create the object in an apartment.




> And whilst Krools threading-helper is breaking only rule #1) (halfways, via his Interface-callbacks),
> your "threading-module helper" is breaking both of the above two rules...


No. My module doesn't break rule. It initializes STA apartment but it doesn't matter. Even if you initialize STA apartment you couldn't use a VB-class because you missed the other point related to runtime-initialization.




> In short - never, ever place VB6-based ThreadCallback-code directly within the Code-Modules of a StdExe.


Why? If need someone can use even non-initialized apartment/runtime. VB6 allows to mix procedural and OOP so we can use C-style coding. I already showed several *stable* examples of such behavior (like DirectShow, JuliaSet, EXE-Loader and Kernel-Driver (which don't use runtime at all)).




> Nooo, I'm not doing that - I just wrote above, that the code which establishes a Worker-Object on an STA,
> should be "hidden away in a binary" (in a compiled Dll).
> 
> In case of AxExes this "precompiled STA establishing code" is sitting in the vbRuntime-dll.
> In case of StdExes this "precompiled STA establishing code" is sitting in ThreadedSTAs.dll.
> 
> The user should never include "editable (VB6)-Thread-Callback code" within his Executable-Project.
> Only then will things run stable.


No. It uses this "hacks". Your ThreadedSTAs.dll is written on VB6 which accepts the callbacks to ThreadProc. There isn't difference if you accept a callback to AxDll compiled function or StdExe compiled function.




> With your threading-helper *.bas module, you're outright encouraging Users,
> to include this stuff into a StdExe-Project directly as Code.
> Please don't do that - it's not stable - never was - and never will be (until a new compiler comes out).


Seems you don't know how does my module work. It works like AxEXE (it runs the code in the main thread when you debug like in AxEXE). It uses threading only when it's compiled.




> Well, in the meantime I'm starting to think, that you "really don't get it".


That's really. Just that's strange when you told - we shouldn't use free threading but you use that. When i told you about that you begin to tell me about "forum-rules" but hunderts-RC5-examples don't bother you. That's really strange. The my point is - yes we can use that (and your example shows that) and it works stable. Your point is incomprehensible.




> Because what I'm showing there, is exactly the same thing as I was showing here.


No. I already wrote if you would place Mandelbrod-example initially i didn't answer my question.




> I'm establishing WorkerObjects on their own STAs (and show, how to work with them, "once they are there").
> 
> The only difference in the Mandelbrot-Tutorial is, that I made things more generic,
> by introducing a cThreadHost- (+ cThreadPool) abstraction, which allows to instantiate these STA-WorkerObjects
> (from any Ax-Dll) in a "non-specific, generic way" (via a ProgID to such an "outsourced" Ax-Dll-Class).


You became to use the things which i told you about STA (message pumping/marshaling/etc), async calls, etc. That's the differences. Initially you told an user shouldn't know that.




> what you promote by: "just include my threadhelper.bas into your StdExes".


But this is real. Using my module you don't need to rewrite your application to decompose parts. It doesn't require SxS manifests (in the most cases). It doesn't require admin rights etc. Moreover the user can learn how does it work, how to do communication, how STA works, how Marshaling works, etc.

----------


## Schmidt

> You became to use the things which i told you about STA (message pumping/marshaling/etc), async calls, etc.


That's where our opinions differ (and we really should end this "talking in circles, repeating ourselves" soon).

Hopefully for the last time:
- You are of the opinion, that STA-establishing code should be used in a Std-Exe via a *.bas-module
- Whereas I advise strongly against that ... such STA-establishing code should be hidden-away in a separate "Black-Box-Dll"
- ... in the same way as it is "hidden from the User", when we use the "officially supported AxExe-based threading" 
- ... so the User does not need to know exactly "why it works, as it works" (because *it distracts* from concrete Threading-implementations)

And no, it wasn't you who told me about "those things" 
(I've written my first STA-establishing code as a VB6-helper-Dll already two decades ago).




> Initially you told an user shouldn't know that.


And I'm still of the same opinion - and have no clue, why you are incapable to understand this.
(despite the existence of my ThreadedSTAs-HelperDll, which should be considered a "don't look, don't touch it"-BlackBox, 
which is only there - along with a few "extras" - to mimick the "official, built-in approach, as available in AxExes").

Proper VB6-supported Threading only requires you to know, how to "instantiate a Worker-(COM-)Object on a new thread".
That can be done in only one line of code (in both, the AxExe-based threading, and via the ThreadedSTAs-HelperDll).

Once you have this "ThreadObject", you "talk with the Worker-thread" *through* that (Worker-)Object.
(and that "marshaled" communication is automatically synchronized and "safe to use").

The only (slight) difficulty with that "official VB6-approach to threading" is -
hot to trigger *a*synchronous calls.

But this "async-problem" can be circumvented by dozens of different methods -
(the easiest one, is to use a simple, call-decoupling VB.Timer in your Worker-Object).

But that's basically all there is with regards to VB-Threading-basics "a User needs to know".




> Using my module you don't need to rewrite your application to decompose parts.


At the risk of making your whole StdExe-code instable, yes.

I also don't get, why "decomposing" (isolating) a "Thread-entity" in its own Dll(-Class) is a bad thing
(when it's just good engineering-practise).

E.g. Javascript follows a quite similar pattern (comparable to "VB6 STA-based threading") with its "WebWorkers":
- You can only establish such a WebWorker(Object) from a separate *.js-File
- You don't "share Variables" among Main- and Worker-Thread (the threads are memory-wise isolated from each other)
- You can only communicate with a WebWorker(Object) via Methods and Events, and Params are passed "as copies"
So this is quite comparable to the "isolation-level" and principles we have in VB6-STA-based threading.

And both threading-patterns have the same goal: 
- to "make threading robust and more safe to use"
- for the "overwhelming majority of relatively inexperienced users of a higher-level language"!

I really don't get, why the VB6-community seems so obsessed with "Free Threading" - and "Variable-sharing", 
when the designers were going out of their way, to offer a much safer to use threading-model in VB6.

Why not just use what's already there - why use instable hacks, when 99% of all threading-scenarios 
can be covered with the "STA-based standard-approach to VB6-based threading"? 
(and the 1% rest - by using Dlls, produced with "more suitable languages or -compilers").

Olaf

----------


## The trick

> And no, it wasn't you who told me about "those things"
> (I've written my first STA-establishing code as a VB6-helper-Dll already two decades ago).


I meant you became use the things when you move from AxExe to StdEXE/AxDll decomposition. You claimed this is quite simple to move from AxExe to StdEXE/AxDll decomposition and an user shouldn't know about STA-internals at all. I claimed the user should know because it can face to some threading pitfails etc. like deadlocks, why use marshaling etc. These things are well documented and anyone who uses threading should know them. You told it's not needed. What i meant is you became use the things which you told aren't needed. So a potential user need to use those things if he doesn't want to use any "magic" dlls etc.




> Hopefully for the last time:
> - You are of the opinion, that STA-establishing code should be used in a Std-Exe via a *.bas-module
> - Whereas I advise strongly against that ... such STA-establishing code should be hidden-away in a separate "Black-Box-Dll"
> - ... in the same way as it is "hidden from the User", when we use the "officially supported AxExe-based threading"
> - ... so the User does not need to know exactly "why it works, as it works" (because it distracts from concrete Threading-implementations)


You could use a *.bas-module like a "Black-Box-Dll". You could use it "just-for-threading" as you advertised here. The advantage you can see and learn how does it work, change something, you don't depend on "Black-Box-Dll" developer, etc. Anyway after compilation there isn't difference if you call the compiled code either from a dll or compiled code from the exe.




> Proper VB6-supported Threading only requires you to know, how to "instantiate a Worker-(COM-)Object on a new thread".
> That can be done in only one line of code (in both, the AxExe-based threading, and via the ThreadedSTAs-HelperDll).


You don't consider ThreadedSTAs-HelperDll code. You avoid this rule in that dll. Following your claims it can't be stable then, so all your ThreadedSTAs-HelperDll is unstable. Okay? But i don't think so. It's just your claims. Moreover by addition code from ThreadedSTAs-HelperDll and "instantiate a Worker-(COM-)Object on a new thread" we get more than one line of code.
By the way my module also supports this scenario (this is most part of code) (CreateActiveXObjectInNewThread/CreateActiveXObjectInNewThread2/CreatePrivateObjectByNameInNewThread). These functions also make marshaling things automatically the user shouldn't do it. In a word, the code does the same 




> Once you have this "ThreadObject", you "talk with the Worker-thread" through that (Worker-)Object.
> (and that "marshaled" communication is automatically synchronized and "safe to use").


The marshaling synchronization are implemented by COM (in our scenarios). This isn't difficult to talk about that.




> The only (slight) difficulty with that "official VB6-approach to threading" is -
> hot to trigger asynchronous calls.
> 
> But this "async-problem" can be circumvented by dozens of different methods -
> (the easiest one, is to use a simple, call-decoupling VB.Timer in your Worker-Object).
> 
> But that's basically all there is with regards to VB-Threading-basics "a User needs to know".


That's the main task in the 99% threading scenarios you didn't show in the initial example.




> At the risk of making your whole StdExe-code instable, yes.
> 
> I also don't get, why "decomposing" (isolating) a "Thread-entity" in its own Dll(-Class) is a bad thing
> (when it's just good engineering-practise).


Because KISS principle. Why should i decompose if i don't need that? Just to use threading? No, thanks. 




> E.g. Javascript follows a quite similar pattern (comparable to "VB6 STA-based threading") with its "WebWorkers":
> - You can only establish such a WebWorker(Object) from a separate *.js-File
> - You don't "share Variables" among Main- and Worker-Thread (the threads are memory-wise isolated from each other)
> - You can only communicate with a WebWorker(Object) via Methods and Events, and Params are passed "as copies"
> So this is quite comparable to the "isolation-level" and principles we have in VB6-STA-based threading.


Why did you give JS? I can give an example in C++ where you can use std::thread or _beginthread without any decomposition.




> I really don't get, why the VB6-community seems so obsessed with "Free Threading" - and "Variable-sharing",
> when the designers were going out of their way, to offer a much safer to use threading-model in VB6.


The problem is you think the community shouldn't use those things but they actually need. You even suggested to use shared memory areas/file-mapping etc couple posts ago though now you tell we shouldn't use that shared things.

----------


## VanGoghGaming

Yeah, as amusing as it was reading this debate over the pros and cons of each threading model, I do have a question pertaining to the initial post. Having to click a button to create new threads is obviously not very practical.

I discovered the hard way that moving the thread creation code in the "Form_Load" event doesn't work! It doesn't produce any errors but all the new forms display the same ThreadID, meaning they are not running in separate threads. A temporary fix was using the "Form_Activate" event instead but I was wondering if there's a better way?

----------


## Schmidt

> ...moving the thread creation code in the "Form_Load" event doesn't work!


That's a "known problem" with this "CreateObject(AxExe.Class)"-based STA-establishing...

At the point of Form_Load (of the first loaded VB-Form on the MainThread), 
the vbRuntime is not yet in "the right state", that new threads(ThreadObjects) can be started this way.

A "OneShotTimer"(Event) would be a robust way to decouple the thread-startups from inside a Form_Load-Handler.
(though PostMessage-based decoupling should work also).

But yes, the Form_Activate Event is a "sufficiently late Event" to do so as well - 
and matches with the "Keep it simple and API-free" intent of this CodeBank-entry the best.

 I've uploaded this, so that others can make their first experiments with STA-based Thread(Objects).

For those who "want to get more serious" with STAs, I've uploaded a more advanced tutorial here:
https://www.vbforums.com/showthread....rot-Rendering)

Olaf

----------


## VanGoghGaming

Yep, I've gathered as much. The good old "Timer" control gets the job done in a pinch. The "Form_Activate" event does exactly the same thing with the only caveat that it needs a "static" boolean variable to make sure it doesn't fire more than one time.

Also I'm a little bummed out that you have to use "CreateObject" versus the "New" keyword when creating new threads. All I could find online was that the former method is called "late bound" while the latter is called "early bound" and that doesn't explain much... I've always used "New" when creating objects so far.

I'll make sure to go through your Mandelbrot tutorial as well since it looks like a fun and enlightening experience.

----------


## Schmidt

> I'm a little bummed out that you have to use "CreateObject" versus the "New" keyword when creating new threads.


This kind of thing plays into the realm of "regfree Object-instantiation"...

The CreateObject-call is a "VB-simplified outer wrapper" around the CoCreateInstance-API 
(which itself is a wrapper around registry-lookups, typelib-interaction and COM-Threading-inits before the real instancing takes place),
whereas the New Keyword is more "near to the metal" in, that it bypasses all the registry-, typelib- and thread-init parts
(to jump "more straight" into the final phases of Instance-creation via a precompiled ClassFactory...)

That's the reason why CreateObject has to be used instead of New in this case of "InProcess Ax-Exe-Threading".
In the tutorial you're about to read, "all the stuff which CreateObject does under the covers" (in case of Ax-Exe-based STAs), will be done "manually" when you come to the parts which cover the "Dll-based STA-creation from within Std-Exes".

Olaf

----------


## georgekar

I would like to have a New method written in VB6, to have it in M2000 Interpreter, which finally use CoCreateInstance-API. I think about to have the same list as the reference list in Vb6 and simply get a name from there. Also I would like to have the properties and methods and the events in a list, with parameter types.

----------


## VanGoghGaming

> In the tutorial you're about to read...
> Olaf


Finally got the time to go through your tutorial, it's very well put together and quite accessible to everyone. I especially like the way you send the WM_MOUSEMOVE message to a hidden form in order to get asynchronous execution, it's brilliant in its simplicity! I'm definitely gonna steal that, so much more elegant than using a Timer control!  :Big Grin: 

Around step 5, where you introduce the "cThreadPool" class, I was mislead by its name. Initially I though you changed the "Threading Model" to "Thread Pool" in project properties and I was curious how you got that working because it was always crashing horribly when I tried it. But I see that was not the case, the "Threading model" is still set to "Thread per Object", haha!

This "cThreadPool" class is just a collection of thread objects used to gather them all under a single event. It's an interesting approach for sure. Instead of a collection, I've always used an array of thread objects for the same purpose in my projects and used "Implements" with a callback interface to gather them all by their "array index" under a single event.

I've got a question about the events raised by the worker threads. Assuming that all threads finish their calculations roughly at the same time, one would assume the threads would fire their "AsyncJobFinished" events roughly at the same time. So how is the main form (where you perform the actual drawing of the fractal) dealing with these events? Are they "queued" somehow in an invisible "event queue" and the main form is executing them sequentially as soon as they arrive?

----------


## Schmidt

> Assuming that all threads finish their calculations roughly at the same time, 
> one would assume the threads would fire their "AsyncJobFinished" events roughly at the same time. 
> 
> So how is the main form (where you perform the actual drawing of the fractal) dealing with these events? 
> Are they "queued" somehow in an invisible "event queue" ...


Yes, all Method- (and RaiseEvent-)calls which go to a COM-Class-instance on a different STA,
are automatically "serialized in a Msg-Queue" under the covers (so that these cross-thread calls are "synchronous").

That's the beauty of the COM-STA-based threading-model, since the User can rest assured, to "automatically work collision-free" -
without having to manage "synchronization himself" (via other means, like CriticalSections or Mutexes).

The disadvantage of this is (when all cross-thread calls are synchronous), 
that one has to "trigger desired *a*synchronous behaviour" from within the "Callee-Class-Method" itself 
(which in the examples is done via that PostMessage-decoupling you mentioned).

Olaf

----------


## VanGoghGaming

Yep, all makes sense now. I see that in the last two steps of the tutorial you jump through a lot of hoops to make threading work for a "Standard EXE" instead of ActiveX. I was thinking it would be a lot easier that instead of using an "ActiveX DLL" for the "cMandelbrot" class, you could move it in an "ActiveX EXE" along with the "fAsyncHelper" form.

Then a "Standard EXE" would have no problem instantiating any number of objects from a single "ActiveX EXE" and managing their events out-of-the-box, no tricks required. One difference is that the worker objects would exist in their own process (visible in Task Manager) instead of their own thread, other than that the existing tutorial should work exactly the same without any modifications. I remember reading somewhere that "cross-thread" marshalling and "cross-process" marshalling have roughly the same cost (performance-wise).

The only other difference I can think of would be that "ActiveX EXE" objects can't be used "RegFree" like an "ActiveX DLL" but they do register themselves on the first execution...

----------


## Schmidt

> I see that in the last two steps of the tutorial you jump through a lot of hoops -
>  to make threading work for a "Standard EXE" instead of ActiveX.


Might look this way, but once the "ThreadedSTAs.dll" was compiled, 
you won't have to bother with that step ever again, and "just use it" 
(it was written generically, and can instantiate *any* COM-Dll-Object on an STA as an "async Worker")

For example, if you want to perform a larger Folder-Backup "threaded, in the BackGround" -
the following Code could be used (when ThreadedSTAs was included as a Reference):


```
Option Explicit
 
Private WithEvents ThFSO As cThreadHost

Private Sub Form_Load()
  'Set ThFSO = RegFree("ThreadedSTAs.cFactory").ThreadHost 'alternatively, if modManifest.bas is in the Project
  Set ThFSO = CreateObject("ThreadedSTAs.cFactory").ThreadHost
      ThFSO.CreateWorkerObject "Scripting.FileSystemObject"
End Sub

Private Sub Form_Click() 'async, threaded call into the FSO.CopyFolder-method
  ThFSO.CallAsync "CopyFolder", "C:\Code", "D:\Backups\Code"
End Sub

Private Sub ThFSO_AsyncJobFinished(ByVal JobKeyCounter As String, ByVal Result As Variant, ByVal ErrString As String)
  If Len(ErrString) Then MsgBox ErrString: Exit Sub
  Debug.Print "Backup Finished", JobKeyCounter, Result
End Sub

Private Sub Form_Unload(Cancel As Integer)
  ThFSO.Cleanup
End Sub
```




> The only other difference I can think of would be that "ActiveX EXE" objects can't be used "RegFree"...


Yep, they don't work "Regfree, out of the box" ...
(since for proper registering, one would need to start them at least once, in "Run As Admin"-mode)

Olaf

----------

