| that it is impossible to characterize the behavior of the application, or to predict whether it will work or should work in any given environment.
It means that it is impossible to predict whether the code will work on any given system, and that the behavior may vary depending on the operating system in use, the number of processors in use, and other system configuration issue.
You see, once you violate the COM contract, you are no longer protected by those features in COM that allow objects to successfully communicate with each other and with clients.
This approach is programming alchemy. It is irresponsible and no programmer should ever use it. Period.
The CreateThread API Revisited
Now that I''''ve shown you why the CreateThread API approach that has appeared in some articles is garbage, it''''s only fair that I make things right and show you how you can, in fact, use this API safely.
The trick is simple -- you must simply adhere to the COM threading contract. This takes a bit more work, but the results have proven so far to be reliable.
The MTDemo3 sample shows this in the frmMTDemo3 form with the following code that launches an apartment model background class as follows:
Private Sub cmdCreateApt_Click()
Set c = New clsBackground
StartBackgroundThreadApt c
End Sub
So far this looks very similar to the free threading approach. You create
an instance of the class and pass it to a function that starts the
background thread. The following code appears in the modMTBack module:
'''' Structure to hold IDispatch GUID
Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public IID_IDispatch As GUID
Declare Function CoMarshalInterThreadInterfaceInStream _
Lib "ole32.dll" (riid As GUID, ByVal pUnk As IUnknown, _
ppStm As Long) As Long
Declare Function CoGetInterfaceAndReleaseStream Lib _
"ole32.dll" (ByVal pStm As Long, riid As GUID, _
pUnk As IUnknown) As Long
Declare Function CoInitialize Lib "ole32.dll" _
(ByVal pvReserved As Long) As Long
Declare Sub CoUninitialize Lib "ole32.dll" ()
'''' Start the background thread for this object
'''' using the apartment model
'''' Returns zero on error
Public Function StartBackgroundThreadApt(ByVal qobj As _
clsBackground)
Dim threadid As Long
Dim hnd&, res&
Dim threadparam As Long
Dim tobj As Object
Set tobj = qobj
'''' Proper marshaled approach
InitializeIID
res = CoMarshalInterThreadInterfaceInStream _
(IID_IDispatch, qobj, threadparam)
If res <> 0 Then
StartBackgroundThreadApt = 0
Exit Function
End If
hnd = CreateThread(0, 2000, AddressOf _
BackgroundFuncApt, threadparam, 0, threadid)
If hnd = 0 Then
'''' Return with zero (error)
Exit Function
End If
'''' We don''''t need the thread handle
CloseHandle hnd
StartBackgroundThreadApt = threadid
End Function
The StartBackgroundThreadApt function is a bit more complex than the free
threading equivalent. The first new function is called InitializeIID. This function
deals with the following code:
'''' Initialize the GUID structure
Private Sub InitializeIID()
Static Initialized As Boolean
If Initialized Then Exit Sub
With IID_IDispatch
.Data1 = &H20400
.Data2 = 0
.Data3 = 0
.Data4(0) = &HC0
.Data4(7) = &H46
End With
Initialized = True
End Sub
You see, we''''re going to need an interface identifier -- a 16 byte structure that uniquely identifies and interface. In particular, we''''re going to need the interface identifier for the IDispatch interface (more information on IDispatch can be found in my Developing ActiveX Components book). The InitializeIID function simply initializes the IID_IDispatch structure to the correctly values for the IDispatch interface identifier. This value is obtained originally using a registry viewer utility.
Why do we need this identifier?
Because in order to adhere to the COM threading contract, we need to create a proxy object for the clsBackground object. The proxy object needs to be passed to the new thread instead of the original object. Calls by the new thread on the proxy object will be marshaled into the current thread.
The CoMarshalInterThreadInterfaceInStream performs an interesting task. It collects all of the information needed to create a proxy for a specified interface and loads it into a stream object. In this example we use the IDispatch interface because we know that every Visual Basic class supports IDispatch, and we know that IDispatch marshalling support is built into Windows -- so this code will always work. We then pass the stream object to the new thread. This object is designed by Windows to be transferable between threads in exactly this manner, so we can pass it safely to the CreateThread function. The rest of the StartBackgroundThreadApt function is identical to the StartBackgroundThreadFree function.
The BackgroundFuncApt function is also more complex than the free threaded equivalent as shown below:
'''' A correctly marshaled apartment model callback.
'''' This is the correct approach, though slower.
Public Function BackgroundFuncApt(ByVal param As Long) _
As Long
Dim qobj As Object
Dim qobj2 As clsBackground
Dim res&
'''' This new thread is a new apartment, we must
'''' initialize OLE for this apartment
'''' (VB doesn''''t seem to do it)
res = CoInitialize(0)
'''' Proper apartment modeled approach
res = CoGetInterfaceAndReleaseStream(param,
IID_IDispatch, qobj)
Set qobj2 = qobj
Do While Not qobj2.DoTheCount(10000)
Loop
qobj2.ShowAForm
'''' Alternatively, you can put a wait function here,
'''' then call the qobj function when the wait is satisfied
'''' All calls to CoInitialize must be balanced
CoUninitialize
End Function
The first step is to initialize the OLE subsystem for the new thread. This is necessary for the marshalling code to work correctly. The CoGetInterfaceAndReleaseStream creates the proxy object for the original clsBackground object and releases the stream object used to transfer the data from the other thread. The IDispatch interface for the new object is loaded into the qobj variable. It is now possible to obtain other interfaces -- the proxy object will correctly marshal data for every interface that it can support.
Now you can see why the loop is placed in this function instead of in the object itself. When you call the qobj2.DoTheCount function for the first time, you''''ll see that the code is running in the original thread! Every time you call a method on the object, you are actually calling the method on the proxy object. Your current thread is suspended, the method request is marshaled to the original thread, and the method called on the original object in the same thread that created the object. If the loop was in the object, you would be freezing up the original thread.
The nice thing about this approach is that everything works. The clsBackground object can show forms and raise events safely. Of course it can -- it''''s running in the same thread as the form and its client -- as it should be. The disadvantage of this approach is, of course, that it is slow. Thread switches and marshalling are relatively slow operations. You would never actually want to implement a background operation as shown here.
But this approach can work extremely well if you can place the background operation in the BackgroundFuncApt function itself! For example: you could have the background thread perform a background calculation or a system wait operation. When it is complete, 上一页 [1] [2] [3] [4] [5] [6] [7] 下一页 |