打印本文 打印本文 关闭窗口 关闭窗口
A Thread to Visual Basic
作者:武汉SEO闵涛  文章来源:敏韬网  点击数5508  更新时间:2009/4/23 16:39:28  文章录入:mintao  责任编辑:mintao
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]  下一页

    打印本文 打印本文 关闭窗口 关闭窗口