COM Interoperability

Snoops

   

 
Migrating to .NET: A Pragmatic Path to Visual Basic .NET, Visual C++ .NET, and ASP.NET
By Dhananjay  Katre, Prashant  Halari, Narayana  Rao  Surapaneni, Manu  Gupta, Meghana  Deshpande

Table of Contents
Chapter 9.   Migrating to Visual C++ .NET


Managed and unmanaged object models differ with respect to the data types, signatures of methods , and the mechanism by which the error is handled. Interoperability and migration are made easier in .NET as CLR hides the complexity associated with calls between managed and unmanaged code. The code required to translate the calls between the two environments is generated by the CLR. The following are the aspects that are managed by .NET as far as interoperability between managed and unmanaged code is concerned :

  • Both late and early binding of interfaces.

  • Data marshalling between managed and unmanaged code.

  • Management of object references to ensure that objects are released or marked for garbage collection.

  • COM HRESULT is translated to .NET exceptions and vice versa.

In this section we will briefly deal with the various types of interoperability supported by .NET.

Exposing COM Components to the .NET Client

There may be many instances when you want to build a COM component and expose it to managed applications as a COM component in the .NET environment. Here we will cover how to use COM component in a managed environment.

TLBIMP.EXE UTILITY

On execution of the tlbimp.exe , the tlbimp.exe utility will generate the assembly after reading the type library. In the assembly those types will be created by the tool that will be used by the RCW to translate calls between the .NET type in the managed environment and the COM type in the unmanaged environment. The library name becomes the namespace, a coclass becomes a .NET class of the same name , a COM interface becomes a .NET interface, and methods and properties of the interfaces get mapped over their respective interfaces. To avoid cluttering of interface definitions the IUnknown and IDispatch interfaces are stripped out.

A number of things have to be done to get everything translated properly. First, all COM types and declarations must be converted to .NET-compatible types. For example BSTR will get mapped to System.String . The tool also has to convert the method parameters to return values of appropriate types. At runtime HRESULTs failure must be mapped to .NET exceptions. Any naming collisions must also be handled by the CLR or the tlbimp.exe .

CLR SUPPORT FOR COM OBJECTS

The RCW acts as a bridge between COM components and the .NET world. It is generated by the runtime whenever a COM object is called from the .NET environment. In other words, the RCW acts as a proxy for the unmanaged COM object. The CLR uses metadata to expose the COM object. The RCW handles all interactions between the .NET client and the COM component. This wrapper manages COM component instantiation and reference counts on the COM object. It plays an important role in marshalling the parameters and return value of the methods that are called by the client. It also handles an error that may occur and finally releases the object once the client is through. Regardless of the number of references, one RCW is created for each COM object. Because the RCW is a managed object, it is allocated on the heap managed by the CLR and is subjected to garbage collection.

Nothing special has to be done to instantiate and call the methods of the COM component from your .NET client because the CLR handles all the COM specifics through the RCW . All that needs to be done is to create the .NET assembly using the type library import tool ( tlbimp.exe ), which will represent the types in the COM library. Both early and late binding of COM objects are supported by the CLR. Early binding requires the importing of type information from the type library so that the compiler knows all the interface methods, events, and properties. Late binding is supported for those COM objects that implement IDispatch-derived interfaces. In this case there is no need to import type information at build time. See Figure 9-1.

Figure 9-1. Functioning of the RCW .

graphics/09fig01.gif

EARLY BINDING TO COMCOMPONENTS

In early binding, add a reference to the COM type library for the component by selecting AddReference . On doing this the IDE again runs the tlbimp.exe to generate an assembly for the type library. The component can then be used as if it were a .NET component.

LATE BINDING TO COM COMPONENTS

Late binding allows the use of an object at runtime without any compile time information about its properties and methods. A late-bound COM object is created by using the Type class and the methods GetTypeFromProgID() and GetTypeFromCLSID() . The type is then mapped to an object using an Activator. This object is then used to invoke the methods.

RELEASE OF COM OBJECTS

Because it is a managed type, an RCW will not be garbage collected until all references to it are released. Until garbage collected, the RCW will continue to hold references on the COM object it manages. When the RCW is garbage collected, it releases all references of the COM object it wraps. This allows the runtime to destroy the COM object, which will also release any resources that the COM object was holding. Use of COM objects in .NET relieves you from reference counting and releasing your objects. However, it has the disadvantage that it is possible that the COM object and the resources it holds only get released after a long time because you have to wait for the runtime to perform garbage collection. This can be avoided by forcing the runtime to release the COM references with the Marshal.ReleaseComObject() static method.

Code Sample

We will create a COM component called the ATLCOMServer. This COM component has a single method ValidateEmployee() that takes the employee name. It will connect to the database and will try to validate the employee name and depending on success or failure it will return a bool value.

The declaration of the CEmployeeInfo class is in the header file. The code for it is, which is in the ComComent_NetClient , follows :

 graphics/icon01.gif #import "c:\Program Files\Common Files  \System\ADO\msado15.dll   " no_namespace rename("EOF",  "EndOfFile") \  inline void TESTHR(HRESULT x) {if FAILED(x)  _com_issue_error(x);};  class ATL_NO_VTABLE CEmployeeInfo :      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CEmployeeInfo, &CLSID_EmployeeInfo>,      public IDispatchImpl<IEmployeeInfo, &IID_IEmployeeInfo,  &LIBID_ATLCOMSERVERLib>{  public:      CEmployeeInfo(){      }  DECLARE_REGISTRY_RESOURCEID(IDR_EMPLOYEEINFO)  DECLARE_PROTECT_FINAL_CONSTRUCT()  BEGIN_COM_MAP(CEmployeeInfo)      COM_INTERFACE_ENTRY(IEmployeeInfo)      COM_INTERFACE_ENTRY(IDispatch)  END_COM_MAP()  // IEmployeeInfo  public:  STDMETHOD(ValidateEmployee)(BSTR EmployeeName, /*[out]*/ BOOL  * bValue);  }; 

The implementation of the ValidateEmployee is in the cpp file. The implementation code follows:

 graphics/icon01.gif STDMETHODIMP CEmployeeInfo::ValidateEmployee(BSTR  EmployeeName, BOOL *bValue){      AFX_MANAGE_STATE(AfxGetStaticModuleState());       HRESULT    hr = S_OK;      _ConnectionPtr    pConnection = NULL;      _RecordsetPtr   pRstSession  = NULL;      _bstr_t     bstrQuery;      CString     sEmployee = EmployeeName;      CString sServerName = "pc-p3793";      CString sDatabaseName = "pubs";      CString sUserName = "sa";      CString sPassword = "";  _bstr_t strCnn("Provider=sqloledb;server=" + sServerName +  ";Database=" + sDatabaseName + ";UID="+ sUserName + ";PWD=" +  sPassword + ";");      try{            // Open connection from the database       TESTHR(pConnection.CreateInstance(__uuidof  (Connection)));            pConnection->Open(strCnn,"","",NULL);             TESTHR(pRstSession.CreateInstance  (__uuidof(Recordset)));            pRstSession->CursorType = adOpenStatic;            pRstSession->CursorLocation = adUseClient;  // Make a Query for getting Access Mode and Pass  //word for passed User ID  bstrQuery = "Select * from Employee Where emp_id = '" +  sEm- ployee + "'";            pRstSession->Open(bstrQuery, _variant_t  ((IDispatch *)pConnection,true),  adOpenStatic,adLockBatchOptimistic, adCmdText);          _variant_t vPasswd;          // If no user exists then return with Error          if(pRstSession->RecordCount==0){                  *bValue = false;                  return S_FALSE;         }        else{            *bValue = true;            return S_OK;       }  }  catch(_com_error &e){      _bstr_t bstrDescription(e.Description());      *bValue = false;      return E_FAIL;  }  return S_OK;  } 

Now it's time to import the tlb and generate the assembly using the following command:

 tlbimp ATLCOMServer.dll /keyfile: ATLCOMServer.snk /out:  RCWATLCOMServer.dll 

Now we will create the .NET client that will call the ATLCOMServer we created. In .NET create a new solution and in it, call this COM component. The code sample for this follows:

 graphics/icon01.gif #using <RCWATLCOMServer.dll>  using namespace RCWATLCOMServer;  int _tmain(void){      Console::WriteLine(S"Hello World");      IEmployeeInfo * pEmployeeInfo = new EmployeeInfoClass;      int * pBool = new int;      pEmployeeInfo->ValidateEmployee("Alice", pBool);      if (*pBool)            Console::WriteLine("Employee name validated");      else  Console::WriteLine("Employee name not validated");      return 0;  } 

Compile this code. In the workspace of this client, copy the RCWATLCOMServer.dll . Also import the namespace by using the two #using directives as shown in the preceding code sample. The output is as follows:

 Hello World  Employee name not validated 

Exposing .NET Components to the COM Client

Component-based development is widely recognized as one of the best ways to develop reusable software. COM provides specification about the layout of interfaces and their methods at the binary level that makes COM compatible across languages. Specifying things at the binary level allows code from other languages to access and use the COM's interfaces and methods. Alternately this information can also be obtained from type libraries and Interface Definition Language (IDL) specifications that are used by COM. The type library and the IDL specification help applications written in various languages to find out the interfaces and methods supported by COM, so that they can be used by them. After creation of the component, it must be registered with the System Registry so that it can be located at runtime to be instantiated properly.

Shared Name Key Files

Embedding public keys in the metadata for an assembly will identify the creator of the component in a secure fashion, improving support for identity and versioning. The shared name (sn.exe ) utility is used to generate the shared name keys. This utility generates a file that contains a public and private key pair, which is used to authenticate the identity of the component. When you are developing .NET components, these keys are used by the CLR as part of the component identity. Hence it is essential that these shared names be included in the assemblies targeted for the COM client. This can be done by either using the /a.keyfile: compiler switch to identify the shared name key file or by using the AssemblyKeyFileAttribute on the assembly. This is done as follows:

 [assembly:AssemblyKeyFileAttribute("Test.snk")]; 

Now compile the assembly again.


Snoops

   
Top


Migrating to. NET. A Pragmatic Path to Visual Basic. NET, Visual C++. NET, and ASP. NET
Migrating to. NET. A Pragmatic Path to Visual Basic. NET, Visual C++. NET, and ASP. NET
ISBN: 131009621
EAN: N/A
Year: 2001
Pages: 149

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