Object Life-Cycle Implications

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Eight.  Advanced COM to .NET Interop

Object Life-Cycle Implications

When you are using a managed code object from a COM/Win32 client, you face a mismatch between the life-cycle scheme that the client uses and expects and the life cycle that the underlying object uses. As you already learned in Chapter 6, the CCW exposes a managed object to unmanaged consumers so that it appears for all intents and purposes to be like any other COM object. The CCW synthesizes an implementation of system-level interfaces like IUnknown, IDispatch, and ISupportsErrorInfo. The CCW exposes COM equivalents of any managed interfaces that the managed class supports, and it may also expose a class interface that exposes all of the public methods implemented by the class. All of these interfaces exposed by the CCW support a reference counting, life-cycle management scheme through the AddRef and Release methods in the IUnknown interface. Under the covers though, the CCW is holding an internal reference to the underlying COM object as shown in Figure 8-4.

Figure 8-4. The CCW holding a reference to a managed object.

graphics/08fig04.gif

When you release all the COM interfaces that you are holding on the object, the CCW will remove its reference on the managed object. The managed object then becomes eligible for garbage collection, but it is not immediately destroyed. In fact, it won't be destroyed until the garbage collector runs, which, in many cases, won't be until the client application shuts down. This is the expected behavior for a managed class, but it's not the behavior that a COM/Win32 client is expecting. A COM/Win32 client will expect the object to destroy itself immediately after it makes its final Release call on the object.

Using the IDisposable Interface from an Unmanaged Client

If the managed object holds time-critical resources and implements the IDisposable interface, the COM/Win32 client can get the IDisposable interface and call the Dispose method explicitly, just as a managed client would. How the client will call the Dispose method depends on whether the client is implemented in Visual Basic or Visual C++; it also depends on the class interface options you chose for your managed class. The following code shows how you would call the Dispose method from an unmanaged Visual C++ client:

 1.  void CVcppclientDlg::OnCalculatePaymentButton() 2.  { 3.      HRESULT hRes; 4.      IDispatch *pDisp; 5.      IDisposable *pDisposable; 6.      CLSID clsID; 7.      hRes=CLSIDFromProgID(L"LifeCycleDemo.TestClass", 8.        &clsID); 9.      hRes=CoCreateInstance(clsID,NULL,CLSCTX_ALL, 10.       IID_IDispatch,(void**)&pDisp); 11.  // Use the object here... 12.     hRes=pDisp->QueryInterface(13.       IID_IDisposable,(void **)&pDisposable); 14.     if (SUCCEEDED(hRes)) 15.     { 16.       pDisposable->Dispose(); 17.       pDisposable->Release(); 18.     } 19.     pDisp->Release(); 20. } 

Lines 3 through 6 contain variable declarations. Line 7 calls the CLSIDFromProgID method to get the CLSID for the managed class. Lines 9 and 10 call the CoCreateInstance method and request the IDispatch interface. (You could also have requested IUnknown here.) Lines 12 and 13 run a QueryInterface for the IDisposable interface. Assuming the call to QueryInterface succeeds, lines 16 and 17 call the Dispose method on the IDisposable interface pointer and then release the interface. Line 19 releases the interface that you acquired on lines 9 and 10. As with all of my COM- related examples, I use less-than -perfect error handling for the sake of brevity.

In order to compile this code, you must reference the type library for the .NET Framework class library (mscorlib.tlb) using Visual C++'s native COM support as follows in stdafx.h :

 #import "mscorlib.tlb" no_namespace named_guids raw_interfaces_only exclude("IObjectHandle") 

You will need to do this regardless of whether you are using Visual C++ 6 or Visual Studio .NET. This statement will include the definition of the IDisposable interface in the Visual C++ project as well as GUIDs, such as IID_IDisposable. Keep in mind that, by default, the CCW exposes an IDispatch interface that contains all of the methods and properties supported by the managed object, including the Dispose method if the object implements the IDisposable interface. Therefore, if you would prefer instead to take a late bound approach, you can also call the Dispose method using the following code:

 1.  void CVcppclientDlg::OnCalculatePaymentButton() 2.  { 3.      HRESULT hRes; 4.      DISPID dispid; 5.      IDispatch *pDisp; 6.      OLECHAR *methodName; 7.      CLSID clsID; 8.      EXCEPINFO errorInfo; 9.      UINT intArg; 10.     DISPPARAMS param; 11.     param.cArgs=0; 12.     param.rgvarg=NULL; 13.     param.cNamedArgs=0; 14.     param.rgdispidNamedArgs=NULL; 15. 16.     hRes=CLSIDFromProgID(L"LifeCycleDemo.TestClass", 17.       &clsID); 18.     hRes=CoCreateInstance(clsID,NULL,CLSCTX_ALL, 19.       IID_IDispatch,(void**)&pDisp); 20. // Use the object here... 21.     methodName=L"Dispose"; 22.     hRes=pDisp->GetIDsOfNames(IID_NULL, 23.          &methodName,1,GetUserDefaultLCID(),&dispid); 24.     hRes=pDisp->Invoke(dispid,IID_NULL, 25.       GetUserDefaultLCID(), 26.       DISPATCH_METHOD,&param,NULL, 27.       &errorInfo,&intArg); 28. 29.     pDisp->Release(); 30. } 

Lines 3 through 10 contain variable declarations. Lines 11 through 14 initialize the DISPPARAMs structure that will contain the parameters that you will pass to the Invoke method. Lines 16 through 19 call the CLSIDFromProgID method to get the CLSID of the managed class, and then they use the CoCreateInstance method to create an instance of the managed class. Lines 21 through 23 call the GetIDsOfNames method to get the ProgID of the Dispose method, and then, on lines 24 through 27, I call the Dispose method via the Invoke method on IDispatch. Finally, I call the Release method on IDispatch on line 29.

Here is early-bound code showing you how to call the IDisposable.Dispose method from a Visual Basic client:

 1.  Private Sub Command1_Click() 2.       Dim objLifeCycleDemo As Object 3.       Dim intfDisposable As IDisposable 4.       Set objLifeCycleDemo = CreateObject(5.        "LifeCycleDemo.TestClass") 6.  ' Use the object here... 7. 8.       Set intfDisposable = objLifeCycleDemo 9.       intfDisposable.Dispose 10. End Sub 

The code here is very simple. Lines 2 and 3 contain variable declarations for a late-bound object (objLifeCycleDemo) and an IDisposable interface pointer (intfDisposable). Lines 4 and 5 call the CreateObject method to create an instance of the managed class and then assign the object reference to the objLifeCycleDemo object variable. I then use the object and, on lines 8 and 9, get an IDisposable interface from the object reference and call the Dispose method. In order to compile this code, you must reference the type library called CLR Library using the Project References command in Visual Basic. Here is the late-bound equivalent of the Visual Basic code:

 1.  Private Sub Command1_Click() 2.      Dim objLifeCycleDemo As Object 3.      Set objLifeCycleDemo = CreateObject(4.      "LifeCycleDemo.TestClass") 5.  ' Use the object here... 6. 7.      objLifeCycleDemo.Dispose 8.  End Sub 

Line 2 declares a late-bound object reference. Lines 3 and 4 instantiate an instance of the managed class. I can then use the object, and, finally, on line 7, I call Dispose through the late-bound class interface that the CCW exposes. With this code, you do not have to reference the CLR library.

Using CorExitProcess

In many cases, the garbage collector will not run until an application shuts down. If you create managed objects and use them from a managed client, the CLR will run the garbage collector and will faithfully call the Finalization method on every object that you instantiated before the application shuts down. Unfortunately, things don't work as well when you instantiate a managed object from an unmanaged client. The process termination steps are different for a managed executable as opposed to an unmanaged executable, and any managed objects that are still alive when you shut down an unmanaged application will not have their Finalization methods called. Fortunately, the CLR's runtime execution engine ( mscoree.dll ) exports a method called CorExitProcess that you may call from an unmanaged client that will force a Finalization method call on every managed object that the unmanaged process currently contains. You should place the call to CorExitProcess as near as possible to the end of your application. The CorExitProcess method is declared as follows in mscoree.h , where exitCode is just the integer value that the process will return when it shuts down.

 void STDMETHODCALLTYPE CorExitProcess(int exitCode) 

The bolded code that follows shows a call to CorExitProcess in the InitInstance method of an MFC dialog-based application:

 BOOL CVcppclientApp::InitInstance() {     AfxEnableControlContainer();     CVcppclientDlg dlg;     m_pMainWnd = &dlg;     int nResponse = dlg.DoModal();     if (nResponse == IDOK)     {     // TODO: Place code here to handle when the dialog is     //  dismissed with OK     }     else if (nResponse == IDCANCEL)     {     // TODO: Place code here to handle when the dialog is     //  dismissed with Cancel     }     ::CorExitProcess(0);     return FALSE; } 

In order to use this code, you need to include mscoree.h at the top of the file and link with mscoree.lib . Remember that, if you are using Visual Studio 6, you will need to add the path to the .NET Framework SDK include and library files on the Directories tab of the Tools Options dialog. If you are using Visual Studio .NET, no additional project configuration is necessary.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net