Calling COM Components

I l @ ve RuBoard

Calling COM Components

COM has been around for a number of years , and a large base of useful COM components is now available. COM components execute in unmanaged space, but you can integrate these components into your .NET applications using COM Interop.

You can create, manage, and communicate with a COM component from managed code by using a Runtime Callable Wrapper (RCW). The RCW acts as a proxy to the COM object. It hides the differences between the COM and the .NET component models, how objects are activated, and how object lifetimes are managed. The overall aim of the RCW is to make the COM object appear just like any other ordinary .NET object (if there is such a thing!).

Note

Behind the scenes, COM Interop uses P/Invoke to execute unmanaged code, and COM Interop is subject to the same security constraints.


Creating and Using an RCW

If you're using Visual Studio.NET, you can create an RCW automatically by adding a reference to a COM component by using the COM tab of the Add Reference dialog box (shown in Figure 13-4).

Figure 13-4. The COM tab of the Add Reference dialog box

The COM page lists the COM components and type libraries that are recorded in the Windows Registry of the local machine. You can select any of these items or click Browse to hunt for a component on the hard disk. If you select a component (such as the Microsoft TAPI 3.0 Type Library ”the telephony API ”as shown in the figure) and click OK, an RCW will be created for this component and added to the list of references in your project. The RCW will be implemented in an assembly and namespace whose name is based on that of the DLL implementing the original COM component, with the suffix Lib . In the case of the Microsoft TAPI 3.0 Type Library (which is implemented in Tapi3.dll), the namespace is called TAPI3Lib and is implemented by the assembly Interop.TAPI3Lib.dll . You can bring the contents of this assembly into scope in the normal way, using an import statement:

 importTAPI3Lib.*; 

The contents of the TAPI3Lib namespace will be made available through Intellisense in the usual manner. You can create and manipulate TAPI3 objects just as you would ordinary .NET objects, and you can invoke methods on them in the same way as you would invoke methods on .NET components. The RCW converts and marshals the .NET types and method calls into their COM equivalents.

The RCW Implementation

The RCW performs some small naming transformations when it provides access to COM. Each coclass defined in the original type library is made available as a .NET class and an interface. The name of the class is the same as the original coclass, with the suffix Class appended to it, while the name of the interface is the same as the original coclass without the Class suffix.

For example, the TAPI coclass, which is the main entry point into the TAPI object model, is made available in the RCW as the TAPIClass type and the TAPI interface. The TAPIClass actually implements the TAPI interface in the RCW. The rationale behind this approach is that you should access the TAPI coclass through the TAPI interface in your .NET code. However, you cannot instantiate an interface, so the TAPI object must be created using TAPIClass , as shown here:

 TAPItapiObject=newTAPIClass(); 

If you have the Platform SDK available, you can examine the original definition of the TAPI coclass by looking at the file Tapi3.idl (supplied in the Include folder of the Platform SDK). The definition is reproduced here:

 [ uuid(21D6D48E-A88B-11D0-83DD-00AA003CCABD), helpstring("TAPI3.0TAPIObject") ] coclassTAPI { [default]interfaceITTAPI; [default,source]dispinterfaceITTAPIDispatchEventNotification; [defaultvtable,source]interfaceITTAPIEventNotification; interfaceITTAPICallCenter; }; 

The TAPI coclass implements several interfaces, and all of these are also available through the RCW. Figure 13-5 shows part of the TAPIClass definition in the RCW, Interop.TAPI3Lib.dll, using the Visual Studio .NET Object Browser.

Figure 13-5. The definition of the TAPIClass type in the RCW

You can access all of these interfaces through the TAPIClass type, in much the same way as you would the TAPI interface. For example, to create an ITTAPICallCenter object, you would use the following statement:

 ITTAPICallCentercallCenterObject=newTAPIClass(); 
Handling HRESULT Values

Like native code executed using P/Invoke, methods of COM components can report error conditions. The usual way for a COM method to do this is through the value returned by the method, which is invariably an HRESULT . Traditional C++ code checks this value to ensure that it is S_OK (which means the method call was successful). Any other value indicates an error, and the value itself specifies the reason for the error. The common language runtime does not pass HRESULT values back to managed clients as return values. Instead, managed COM method calls return either a void or the value of any parameter marked as [retval] in the original COM method. ( [retval] is a COM attribute used by the MIDL compiler to indicate that a parameter should be marshaled as the return value.) If a COM method call attempts to pass back an HRESULT other than S_OK , the common language runtime will trap it and raise an exception.

The exception object created will be of a type that maps to the value of the HRESULT . For example, the COM HRESULT value E_NOTIMPL (the method is not implemented) will be signaled with a System.NotImplementedException object. You can of course trap all exceptions as a System.Exception object. Note that not all COM exceptions have corresponding managed exceptions. (This observation is especially true if you're calling a COM component that defines its own set of exception codes.) Unmapped exceptions are translated into System.Runtime.InteropServices.COMException objects, and you can obtain the HRESULT value that generated the exception by examining the ErrorCode ( get_ErrorCode in J#) property. In addition, if the COM object supports the IErrorInfo interface, the Message ( get_Message ) property of the exception will be filled in with the string returned by IErrorInfo.GetDescription , the Source ( get_Source ) property will be populated with the return value from IErrorInfo.GetSource , and the HelpLink ( get_HelpLink ) property will contain information returned by the IErrorInfo.GetHelpFile and IErrorInfo.GetHelpContext methods.

Marshaling and Apartments

The RCW uses the .NET interop marshaler to marshal data between the managed and unmanaged spaces, including COM. Although apartments are not an issue when you use P/Invoke, they can become important when you consider COM Interop.

COM objects execute in apartments. When the common language runtime instantiates a COM object, it creates an apartment for it, which by default is multithreaded. You might recall the use of the STAThread attribute, which is often inserted automatically by Visual Studio into your code, especially when you create console and Windows Forms applications. This setting fixes the value of the Thread.ApartmentState property, causing the current thread to run using the specified apartment state. (See Chapter 8.) The STAThread attribute directs COM Interop to create a single-threaded apartment. (Threads that handle the user interface must reside in a single-threaded apartment.) If the thread affinity of a COM server (specified by the ThreadingModel setting in the Windows Registry) that's dishing out components to a .NET client matches that of the .NET client, the common language runtime will create the COM component in the same apartment as the .NET client code and the .NET interop marshaler will perform all the necessary marshaling. If the ThreadingModel setting does not match that of the .NET client, the COM component will be created in a separate apartment. The .NET interop marshaler will marshal data from managed space to unmanaged space and then use COM to perform cross-apartment marshaling.

Cross-apartment marshaling can be expensive and time-consuming , so you should try to ensure that calls to COM components made by managed code are performed in the same apartment. However, this might not always be feasible . The Thread.ApartmentState property can be set only once for a thread ”thereafter, it cannot be changed. If the same managed thread makes calls to single-threaded apartment COM components and multithreaded apartment COM components, some cross-apartment COM marshaling will be unavoidable. As a workaround, you have at least two choices: You can create a new thread for each COM component you want to use and set its ApartmentState property appropriately (this might prove to be more expensive than performing cross-apartment marshaling if you use it to excess!). Or you can try to ensure that the COM components you use support the both-threading model (so that they will execute in both single and multithreaded apartments).

Creating an RCW from the Command Line

If you're not using Visual Studio .NET as your programming environment, you can still create an RCW for a COM component, but you must do it manually. The main requirement is that you have access to the type library of the COM component, which might be held in a TLB file or it could be packaged with the component in its DLL file.

The Type Library Importer (Tlbimp.exe) will create an assembly containing the RCW, from the type library for a COM component. The type library does not have to be registered in the Windows Registry for Tlbimp to work; it is sometimes used in conjunction with the MIDL compiler, which can generate a type library from an IDL definition of a COM component.

Tip

MIDL and Tlbimp are especially useful if you want to access some of the very recent functionality of Windows XP from managed code. An example is the Background Intelligent Transfer Service (BITS). You can use BITS to upload and download files to or from a file server over the Web. It operates as a service, executing in the background, and is accessed using DCOM. The BITS API is available to C++ programmers using the Microsoft Platform SDK. BITS does not provide a type library, but the Platform SDK does include an IDL file (called Bits.idl), which defines the interfaces and methods of BITS. Using MIDL, you can create the type library and then use Tlbimp to generate the RCW.


You execute Tlbimp from the command line. When you create the RCW in this way, you can sign it to generate an assembly with a strong name. The RCW assembly can then be copied to the GAC and be made globally available.

Note

The ability to sign RCWs is one compelling reason for using Tlbimp rather than Visual Studio .NET to create them. RCWs generated by Visual Studio .NET are inherently private assemblies. RCWs generated by Tlbimp can be placed in the Global Assembly Cache (GAC) and shared by multiple applications if they are signed.


For example, the following commands create a public/private key pair in a file called Bits.snk (using the Sn.exe utility described in Chapter 2), generate an RCW in an assembly called Bits.dll from the type library called Bits.tlb, sign it with the key pair found in Bits.snk, and deploy the assembly to the GAC:

 snkBits.snk tlbimpBits.tlb/out:Bits.dll/keyfile:Bits.snk gacutil/iBits.dll 

Tip

If the type library is embedded in a DLL, you must specify the name of the DLL as the source to the Tlbimp command. If the DLL contains several type libraries, you can specify which one you want to import into an RCW by appending a resource identifier to the source. For example, to create an RCW based on the first type library in the component file MyComponent.dll, you would use this command:

 tlbimpMyComponent.dll/out:MyComponentLib.dll 

Sinking COM Events

COM components can publish events. Managed components can subscribe to these events, registering their own methods as event handlers. When such an event is raised, COM is effectively calling back into managed space from the unmanaged environment. Event handler parameters must be marshaled from the unmanaged space by the .NET interop marshaler. When an RCW is generated, it provides .NET delegates that you can specify as callbacks into your code from the COM component, for subscribing to COM events. (COM+-savvy readers: The RCW generates interfaces compatible with COM connection points.) The names generated for these delegates are combinations of the COM sink event interface and the name of the event, and they have the word EventHandler appended to the end.

For example, consider the ExplorerDemo class, available in the ExplorerDemo.jsl sample file in the ExplorerDemo project, which demonstrates how to create and manage a Microsoft Internet Explorer window from J#. To access Internet Explorer, you can add a reference to the Microsoft Internet Controls library (in \WINDOWS\System32\shdocvw.dll), as shown in Figure 13-6.

Figure 13-6. Adding a reference to the Microsoft Internet Controls library

This action will create an RCW called SHDocVw in the assembly Interop.SHDocVw.dll. The SHDocVw namespace contains a selection of objects, interfaces, delegates, and constants. The InternetExplorerClass exposes the InternetExplorer interface. The doWork method shown below creates an instance of InternetExplorerClass and references it using the InternetExplorer variable, ie :

 privatevoiddoWork() { InternetExplorerie=newInternetExplorerClass(); 

The InternetExplorer interface supplies methods that allow you to subscribe to the various events that can occur, using the add_<XXX> methods (where < XXX> is the name of the event). The corresponding remove_<XXX> methods allow you to unsubscribe from an event. To subscribe to an event, you must first create a delegate that refers to the method to be invoked when the event is raised. As described earlier, each event has its own delegate class defined by the RCW for this purpose. For instance, the OnVisible event expects a DWebBrowserEvents2_OnVisibleEventHandler delegate. ( DWebBrowserEvents2 is the name of the event interface in the Internet Explorer COM library.) This delegate must refer to a method that takes a single boolean parameter indicating whether the Internet Explorer window is visible and returns a void. The ExplorerDemo class provides just such a method, called ieVisible :

 ie.add_OnVisible(newDWebBrowserEvents2_OnVisibleEventHandler(ieVisible)); 

In much the same way, the TitleChange event requires a DWebBrowserEvents2_TitleChangeEventHandler delegate, which refers to a method that returns a void and takes a single String parameter containing the new title displayed when a page is loaded into Internet Explorer. The ExplorerDemo class contains the ieTitleChange method for this purpose:

 ie.add_TitleChange(new DWebBrowserEvents2_TitleChangeEventHandler(ieTitleChange)); 

To test these event handlers, the doWork method makes the Internet Explorer window visible and navigates to the user's selected home page. At this point, Internet Explorer should burst into life on the user's desktop. If the user navigates to other URLs, the TitleChange event will fire and you'll see the text output by the ieTitleChange method in the console window. (The doWork method waits for the user to press the Enter key before terminating because when this method finishes so does the program, and all the event handlers will be lost even though the Internet Explorer window will remain ). Figure 13-7 shows the console window after navigating to the author's home page (the MSN UK Homepage) and then moving to the Microsoft Press Web site.

Figure 13-7. The console window showing the messages output by the event handlers

Using COM Objects Without Type Libraries

Not all COM objects provide nicely wrapped type libraries. In these cases, you cannot create a static RCW using Tlbimp or the Add Reference dialog box in Visual Studio .NET. Instead, you have at least two choices: late binding or manual marshaling.

Using Late Binding

Late binding is commonly used when you access COM objects that implement IDispatch . The COM IDispatch interface defines methods that allow a client to query the interfaces an object implements at run time, and discover its methods and properties. Late binding offers an advantage over using a static RCW because the client is not tied to any particular implementation or version of a COM server. (Versioning still is an issue in COM.) The disadvantages are the additional overhead needed to make method calls and the extra complexity required in your J# code.

Late binding is based on reflection and the type system employed by the common language runtime. When used in conjunction with COM Interop, the common language runtime interacts with a dynamically created RCW, which in turn uses the methods of the IDispatch interface to dynamically invoke methods and access properties of the underlying COM object.

To see how late binding works, consider the following example. The Fourth Coffee company has an existing COM component that exposes two classes called CakeFactory and Cake . This component was developed using Visual Basic 6, but the developers at Fourth Coffee want to access it from J#. For reasons best known to them, the developers wish to use late binding. (This component will support early binding as well, but we're looking for a demonstration of late binding here). The Visual Basic code for the Cake class is shown here (it is available in the FourthCoffee Visual Basic 6.0 project):

Cake.cls
 OptionExplicit PrivatesizeAsInteger PrivatefillingAsInteger PrivateshapeAsInteger PublicPropertyLetCakeFilling(ByValfillAsInteger) filling=fill EndProperty PublicPropertyGetCakeFilling()AsInteger CakeFilling=filling EndProperty PublicPropertyLetCakeShape(ByValshpAsInteger) shape=shp EndProperty PublicPropertyGetCakeShape()AsInteger CakeShape=shape EndProperty PublicPropertyLetCakeSize(ByValszAsInteger) size=sz EndProperty PublicPropertyGetCakeSize()AsInteger CakeSize=size EndProperty 

This class has been kept deliberately simple. All it does is expose the CakeFilling , CakeShape , and CakeSize properties, each of which is implemented as a Visual Basic 6.0 Integer . The CakeFactory class is also straightforward. It provides two methods: CreateCake , which takes parameters describing the properties of a cake and creates a new Cake object using them, and Feeds ­HowMany , which is another implementation of the method we've seen so often in this book!

CakeFactory.cls
 OptionExplicit 'CakeFillings ConstCF_FRUIT=0 ConstCF_SPONGE=1 'CakeShapes ConstCS_SQUARE=0 ConstCS_ROUND=1 ConstCS_HEXAGONAL=2 'Makeacakeusingthesupplieddetails PublicFunctionCreateCake(ByValsizeAsInteger,ByValshapeAsInteger, ByValfillingAsInteger)AsCake DimtheCakeAsCake SettheCake=NewCake theCake.CakeSize=size theCake.CakeShape=shape theCake.CakeFilling=filling SetCreateCake=theCake EndFunction 'Howmanypeopledoesacakeofagivensize,shape,andfillingfeed PublicFunctionFeedsHowMany(ByValdiameterAsInteger, ByValshapeAsInteger,ByValfillingAsInteger)AsInteger DimmunchSizeFactorAsDouble DimdeadSpaceFactorAsDouble DimnumConsumersAsInteger munchSizeFactor=IIf(filling=CF_FRUIT,2.5,1) SelectCaseshape CaseCS_SQUARE deadSpaceFactor=0 CaseCS_HEXAGONAL deadSpaceFactor=0.1 CaseCS_ROUND deadSpaceFactor=0.15 CaseElse deadSpaceFactor=0.2 EndSelect numConsumers=diameter*munchSizeFactor*(1-deadSpaceFactor) FeedsHowMany=numConsumers EndFunction 

The component is deployed as FourthCoffee.dll (an unmanaged DLL). The J# CakeClient class, available in the COMCakeClient project, shows how to access the CakeFactory and Cake classes through late binding. If you examine the project, you'll notice that no explicit RCW has been created or imported; everything is done through reflection. The key line of code is actually the first statement in the main method:

 TypecakeFactoryType=Type.GetTypeFromProgID("FourthCoffee.CakeFactory"); 

This piece of magic creates a System.Type object based on information found in the Windows Registry, which was located through the specified ProgID using the static method GetTypeFromProgID of the System.Type class. (There's also a static GetTypeFromCLSID method that takes a CLSID of a component, which is specified as a System.Guid and creates a System.Type object in the same manner.) In this case, the ProgID used is that of the CakeFactory class. (ProgIDs generated by Visual Basic 6.0 are usually of the form project.class , although you can use the OLE/COM Object Viewer available from the Tools menu of Visual Studio.NET, and the Platform SDK, to verify the ProgID of any registered component.) Assuming that the statement is successful (the value returned will be null if it wasn't), the cakeFactoryType variable will be populated with type information about the COM object. (In case you're interested, the actual value returned is System.__ComObject , which is an opaque representation of a COM object.)

The next task is to create the RCW itself. You can do this using the static CreateInstance method of the System.Activator class. (You saw this method before, when we used Remoting.)

 ObjectcakeFactory=Activator.CreateInstance(cakeFactoryType); 

The CreateInstance method dynamically creates an instance of the object indicated by its System.Type parameter ”in this case, a System.__ComObject . Information about exactly which COM class the cakeFactory object represents is retained deep within the RCW and is not easily accessible. ( System.__ComObject is opaque, remember.) You can assume that because you instantiated this object using the ProgID FourthCoffee.CakeFactory , you can treat it as a COM CakeFactory object.

Remember that you created this object using late binding, so the only way you can access its methods is through IDispatch . The System.Type class defines the InvokeMember method, which is useful on such occasions as this. The InvokeMember method is used to access a member of a type. A member can be a method, a property, or a field. The arguments to InvokeMember are the name of the member, a System.Reflection.BindingFlags value indicating what sort of member it is, binding information that can be used to choose between overloaded members that have the same name, the object over which the member should be invoked, and an array of parameters taken by the member. The value returned by the InvokeMember method is the value resulting from accessing the member. An exception will be thrown if the member does not exist or the parameters are wrong or the return value is miscast.

The following statements create an array of three System.Int16 values. (Visual Basic Integers are 16-bit values, and the data must be convertible to Object s, so you cannot use Java language primitive types such as short. ) The values denote a 12-inch hexagonal sponge cake and then call the FeedsHowMany method of the cakeFactory object. In this example, the method call will be intercepted by the RCW (the cakeFactory object) and executed as a COM method call. The return value will be marshaled from COM back to your managed code. The result will be the number of people this cake will serve.

 Object[]params=newObject[]{(Int16)12,(Int16)2,(Int16)1}; Int16numEaters=(Int16)cakeFactoryType.InvokeMember("FeedsHowMany", BindingFlags.InvokeMethod,null,cakeFactory,params); Console.WriteLine("Thiscakewillfeed " +numEaters); 

You can use the same technique to execute the CreateCake method and return a COM Cake object. This time, the value returned is another System.__ComObject :

 Objectcake=cakeFactoryType.InvokeMember("CreateCake", BindingFlags.InvokeMethod,null,cakeFactory,params); 

Once the Cake object has been created, you can verify that its properties are set correctly by querying the CakeSize , CakeShape , and CakeFilling properties ”you set the BindingFlags parameter to GetProperty . (You can also use SetProperty to change a property value as long as the property is not read-only.)

 Int16size=(Int16)cakeFactoryType.InvokeMember("CakeSize", BindingFlags.GetProperty,null,cake,null); Int16shape=(Int16)cakeFactoryType.InvokeMember("CakeShape", BindingFlags.GetProperty,null,cake,null); Int16filling=(Int16)cakeFactoryType.InvokeMember("CakeFilling", BindingFlags.GetProperty,null,cake,null); Console.WriteLine("Sizeis " +size+ ",Shapeis " +shape+  ",andFillingis " +filling); 

The CakeClient.jsl sample file in the COMCakeClient project contains the complete code.

Note

You might be wondering how (or maybe even why) we're using the cakeFactoryType object to invoke Cake properties. After all, the cakeFactoryType variable was created using the ProgID of the CakeFactory class. You're welcome to create a Type variable using the ProgID FourthCoffee.Cake and execute InvokeMember using this variable instead, but this would be a waste of effort. The fourth parameter of InvokeMember (the cakeFactory and cake objects in the examples presented) is the key ”it specifies exactly which COM object should be used. You can actually use any Type variable you like for this purpose, as long as it represents a System.__ComObject . We even tried using the FlexGrid control (with a ProgID of MSFlexGridLib.MSFlexGrid ), and it all worked beautifully.


Manual Binding and Marshaling

An alternative to using late binding in the absence of a type library is for you to define the metadata required by the RCW yourself. The System.Run ­time.InteropServices namespace contains a number of attributes that you can use to define classes and interfaces and specify that they're implemented by COM. The most important attributes are ComImportAttribute , which marks an interface or class as being externally implemented through COM, and GuidAttribute , which specifies the GUID of the interface or class. In the case of an IDispatch interface, the individual members should be tagged with their DispIds using DispIdAttribute . You can specify the directionality of individual method parameters using InAttribute and OutAttribute . If any of the parameter types requires additional marshaling (strings, for example), you can apply MarshalAsAttribute , as shown earlier when we discussed P/Invoke.

Using the CakeFactory and Cake classes from the previous example, here are .NET definitions of interfaces for these classes. We deliberately used the same naming scheme that the RCW uses ”the interfaces are named after the original implementations , and the coclass has the suffix Class appended to the end. The code for this example is available in the file CakeClient.jsl in the ManualCOMCakeClient project.

 /**@attributeComImportAttribute()*/ /**@attributeGuidAttribute("2F2A5381-B1DB-4828-9982-A77DA2470237")*/ publicinterfaceCake { /**@attributeDispIdAttribute(0x68030002)*/ voidCakeFilling(/**@attributeInAttribute()*/shortfilling); /**@attributeDispIdAttribute(0x68030002)*/shortCakeFilling(); /**@attributeDispIdAttribute(0x68030001)*/ voidCakeSize(/**@attributeInAttribute()*/shortfilling); /**@attributeDispIdAttribute(0x68030001)*/ shortCakeSize(); /**@attributeDispIdAttribute(0x68030000)*/ voidCakeShape(/**@attributeInAttribute()*/shortshape); /**@attributeDispIdAttribute(0x68030000)*/ shortCakeShape(); } /**@attributeComImportAttribute()*/ /**@attributeGuidAttribute("977BAB34-FB20-4884-B73D-8A63E73DA8F4")*/ publicinterfaceCakeFactory { /**@attributeDispIdAttribute(0x60030000)*/ CakeCreateCake(/**@attributeInAttribute()*/shortsize, /**@attributeInAttribute()*/shortshape, /**@attributeInAttribute()*/shortfilling); /**@attributeDispIdAttribute(0x60030001)*/ shortFeedsHowMany(/**@attributeInAttribute()*/shortdiameter, /**@attributeInAttribute()*/shortshape, /**@attributeInAttribute()*/shortfilling); } /** *.NETdefinitionoftheCakeFactoryclass */ /**@attributeComImportAttribute()*/ /**@attributeGuidAttribute("28915B3A-DC8A-4780-AFF5-0C6136AF4496")*/ publicclassCakeFactoryClass{} 

Note

The GUIDs for the interfaces and the class were generated by Visual Basic. If you rebuild the Visual Basic component, the GUIDs might change.


Common Managed COM Interfaces

COM objects that do not have type libraries almost inevitably implement horrendously complex interfaces, usually incorporating methods that return other interface pointers. You can end up having to work out how to map a number of COM interfaces to their managed equivalents. To save you a bit of time, the .NET Framework Class Library provides managed definitions of some of the standard COM interfaces in the System.Runtime.InteropServices namespace. Their names all take the form UCOM<XXX> , where < XXX> is the name of the underlying COM interface.

For example, UCOMIPersistFile is the managed version of the COM IPersistFile interface. The other interfaces available are UCOMIBindCtx , UCOMI ­BindConnectionPoint , UCOMIBindConnectionPointContainer , UCOMIEnumConnectionPoints , UCOMIEnumConnections , UCOMIEnumMoniker , UCOMIEnumString , UCOMIEnumVARIANT , UCOMIMoniker , UCOMIRunningObjectTable , UCOMIStream , UCOMITypeComp , and UCOMITypeLib . To obtain a reference to a specific interface exposed by an RCW, you just cast to the appropriate interface. The following statements create an InternetExplorer object (see the ExplorerDemo class shown earlier) and retrieve a reference to its IConnectionPointContainer interface:

 InternetExplorerie=newInternetExplorerClass(); UCOMIConnectionPointContainericpc= (UCOMIConnectionPointContainer)ie; 

The definition of CakeFactoryClass , for accessing the CakeFactory coclass that implements the CakeFactory interface, is worthy of further mention. The class does not actually contain any methods because the methods are implemented by the underlying COM class. Additionally, the class does not explicitly implement the CakeFactory interface. If you try to attach an implements CakeFactory clause, your code will not compile because you'll also have to provide implementations of the CreateCake and FeedsHowMany methods. (These are the general rules of interface implementation.)

To create a CakeFactory object, you use this statement:

 CakeFactorycakeFactory=(CakeFactory)newCakeFactoryClass(); 

This looks mighty suspect! Under normal circumstances, you'd expect the J# compiler to reject any attempt to cast an object as an interface that it does not explicitly implement. However, in the case of classes tagged with ComImportAttribute , the compile-time checking of such casts is temporarily suspended . It will be performed instead at run time, when the RCW generated for the CakeFactoryClass attempts to obtain a pointer to the specified interface ( CakeFactory ) through QueryInterface , in the classic COM manner. If QueryInterface fails, the RCW will throw an exception.

Having obtained an interface pointer to a CakeFactory object, you can execute the FeedsHowMany and CreateCake methods as before. This time, however, you do not have to use InvokeMember :

 //Howmanypeoplewilla12" hexagonal(2)sponge(1)cakefeed? shortnumEaters=cakeFactory.FeedsHowMany((short)12,(short)2,(short)1); Console.WriteLine("Thiscakewillfeed " +numEaters); //CalltheCreateCakemethodtocreateanewCakeobject Cakecake=cakeFactory.CreateCake((short)12,(short)2,(short)1); Console.WriteLine("Cakecreated"); //Verifythepropertiesofthenewcake shortsize=cake.CakeSize(); shortshape=cake.CakeShape(); shortfilling=cake.CakeFilling(); Console.WriteLine("Sizeis " +size+ ",Shapeis " +shape+  ",andFillingis " +filling); 

The Marshal Class

The System.Runtime.InteropServices.Marshal class is a utility class containing a range of static methods for managing the transition from managed to unmanaged space and vice-versa. Methods are available for copying blocks of memory between the two spaces and interacting with the COM infrastructure, including managed implementations of the IUnknown methods ( QueryInterface , AddRef , and Release ). Furthermore, methods are available that allow you to obtain pointers to the IDispatch ( GetIDispatchForObject ) and IUnknown ( GetIUnknownForObject ) interfaces of a COM object accessed through an RCW. With one exception, you're unlikely to use many of the methods of the Marshal class on a day-to-day basis, mainly because much of the functionality is automatically available through an RCW anyway. But on occasion you might need to delve more deeply than the RCW will allow, so it's nice to know that the Marshal class is there.

The method that you might find useful is ReleaseComObject , which implements the Release method of the COM IUnknown interface. You can use this method if you want to release a reference to a COM object earlier than the common language runtime would have (and maybe save some resources). If you try to access an object after having released it, you'll cause a NullReferenceException to be thrown.

Many of the methods in the Marshal class make heavy use of the System.IntPtr type, which is a managed implementation of a pointer to unmanaged memory. Unmanaged pointers are marshaled as IntPtr values by many of the Marshal methods.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

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