Re-creating COM Interfaces Using Managed Code

team lib

Earlier in the chapter, you saw a re-created interface in Listing 14-1. This is the unmanaged form of the IComponent interface. Figure 14-8 (on the next page) shows how the interface appears without all the managed information added in Listing 14-1.

Looking at Figure 14-8 should tell you a few things about the code in Listing 14-1. First, the code uses the [ComImport] attribute to show that this interface is in a previously defined type library. You don't want the managed version of the interface to appear as an original (new) interface. Notice the [Guid] attribute has the same GUID as the unmanaged version shown in Figure 14-8. This is a requirement if you want other applications to react to the managed interface as if it were the unmanaged version. Finally, the [InterfaceType] attribute shows that the managed interface derives from IUnknown , just as the unmanaged version does.

click to expand
Figure 14-8: The unmanaged form of the IComponent interface

This particular interface has some interesting marshaling requirements you need to consider when you create interfaces of your own. Look at the method used to marshal lpConsole . In the unmanaged version, this variable holds a pointer to the IConsole interface. This interface provides access to the MMC console. The MMC snap-in doesn't implement this interface, so storing it in an object makes sense at the outset of the initialization process. However, the component still has to marshal the object as an UnmanagedType.Interface .

The next declaration method is preceded by the [PreserveSig()] attribute. If you look at the unmanaged versions of the Notify() and Initialize() methods , you'll see that they both return HRESULT values. In reality, however, only the Notify() method actually returns an HRESULT . The common language runtime normally performs a transformation as it creates output for the unmanaged environment. When you return a value from a function, the value actually appears as an [out, retval] entry in the function argument list. Using the [PreserveSig()] attribute suppresses this behavior so that the returned value appears as an HRESULT instead of the [out, retval] entry. The only time you should use this technique is when the code must return an HRESULT , and not a value, to the caller.

The Notify() method has another interesting difference from the Initialize() method. The lpDataObject argument is a pointer to an IDataObject interface. In both cases, the caller passes the interface to the method, so there's no discernable difference in the unmanaged declaration for the interface. The difference, in this case, is in the implementation. The Initialize() method uses the object passed by the caller to query interfaces the console supports. The Notify() method, on the other hand, has to determine what type of information the interface will provide, and then it uses the Marshal.GetObjectForIUnknown() method to convert the IntPtr to a local object using the technique shown here:


In short, the use of an argument determines the interface's declaration in the managed interface as much as the original data type in the unmanaged version of the interface. In some cases, this means you'll have to redefine the managed version of the interface as your component develops.

Both the managed and unmanaged versions of the Notify() method contain an MMC_NOTIFY_TYPE enumeration. In many cases, you can simply copy the enumeration from the Visual C++ header and convert it to a managed equivalent. However, you must ensure that the managed version values match the unmanaged form precisely, as shown in Listing 14-2.

Listing 14-2: The managed form of the MMC_NOTIFY_TYPE enumeration
start example
end example

As you can see, each enumerated member has a value associated with it. Using this technique reduces the probability that an individual member will have the wrong value, even if it appears in a different order than the original unmanaged version.

Some data conversions are relatively easy once you figure out what the value is going to do in your component. For example, the unmanaged version of the Notify() method contains two LPARAM values. These values easily convert to Int32 values. Make sure you use caution when converting the values. In some cases, you have to use a UInt32 value to make the parameter work as anticipated. Otherwise, a value will appear negative when it's actually just large.

Look at the definition for the GetResultViewType() method. This method has two out values. In contrast, the GetDisplayInfo() method uses a ref value. As a rule of thumb, if the unmanaged code contains just an [out] attribute, you need to use the out keyword in the managed description. On the other hand, if the unmanaged code has both an [in] and an [out] attribute, use the ref keyword in the managed code. Obviously, this rule isn't cast in concrete, but it does work most of the time. If you run into problems with the interaction between a managed component and the container in which it resides, try using the other keyword ( out in place of ref or ref in place of out ).

At this point, you've seen how the OLE/COM Object Viewer can help you determine which interfaces you must implement and how careful analysis of the C header files can provide clues about interface implementation. Remember that you need to test every interface thoroughly and be ready to make changes as needed. Some decisions you must make to create the interface are less than straightforward because they depend on implementation details within the component or data expectations from the caller.

team lib

COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140 © 2008-2017.
If you may any questions please contact us: