Interop: Common Issues and Debugging Techniques

This post will describe some of the types of problems that can arise when interoperating between managed and native code using the .Net Compact Framework Version 2. An overview of how the runtime works under the scenes is also given, which may help promote better quality and more efficient code as well as give insight into the cause of problems.

Marshaling

When interoperating with native code, a conversion process is necessary to translate argument data between managed and native code. The .Net Compact Framework supports marshaling many different managed types, both to and from native code. Each managed type that is supported has default marshaling behavior, which may be different depending on the type of call being made (COM interface function call, delegate callback, or PInvoke). You can also override the default marshaling behavior by using the MarshalAs attribute. The runtime will marshal (or convert) data automatically when an interop function call is made, as specified in the managed PInvoke signature, delegate callback signature, or COM interface signature. You can also use the functions in the InteropServices.Marshal class to manually convert data between managed objects and an IntPtr representing the native object. For some examples, see GetIUnknownForObject, GetObjectForIUnknown, PtrToStringBSTR, StringToBSTR, StructureToPtr, or PtrToStructure.

There are a few types of issues that come up when interoperating with native code that involve the marshaling process:

1. A managed NotSupportedException may be thrown, due to an unsupported type being specified as an argument or return value in the managed interop signature.
Although almost all parameter types the desktop framework supports are now supported in the .Net Compact Framework, there are a few situations where there is limited support. These exceptions include fields in structures, elements in arrays, and return types. The workaround for these limitations is to use the functions in the InteropServices.Marshal class to manually do the conversion process, and specify an IntPtr as the type in the signature or structure.
Interoperating using methods, types, or COM interface classes that are generic is also not supported.
The interop log file will specify the particular method causing the issue, which can be especially useful when using COM interfaces. The best way to diagnose this is to isolate the unsupported type by systematically changing parameter types to IntPtr or a known supported type. Once the unsupported type is identified, consider using another similar type or doing the conversion work manually. 
If the unsupported type is identified to be a structure, removing the MarshalAs specifier for structure fields may solve the issue (assuming you can specify a field type that has the appropriate default marshaling behavior).  .NetCF will allow only a few limited cases of using the MarshalAs attribute to change the default marshaling behavior of a field in a structure.  You can also consider using IntPtr marshaling using the various functions in the InteropServices.Marshal class.
See the resources section for more information about how to determine what the default marshaling behavior is for a particular type.

2. The native function call may not produce the expected results or causes a native exception, due to types being marshaled incorrectly and not matching the specifications of the native function signature.
The best way to diagnose these types of issues is to use the signature output in the interop log file and compare how the runtime is marshaling parameters to what is expected by the native function.
You can then change the types used in the managed signature or use the MarshalAs attribute to ensure data is marshaled correctly. Keep in mind the default marshaling behavior of a particular type will sometimes be different depending on the type of interop call (PInvoke, Com, Delegate callback).  The ref keyword must also be applied correctly to the parameters in the managed interop signature. In the event the signature takes a structure parameter, you should also make sure the structure alignment is specified correctly using the StructLayout attribute.
Other common marshaling errors include using a managed 'bool' instead of an 'int' to represent a native Win32 BOOL type.  Likewise, an managed 'long' is actually 64 bits and is not equalivant to a Win32 LONG, which is 32 bits.

The .Net Compact Framework has two differences from the desktop framework with regard to default marshaling of types for a PInvoke call, for backward compat reasons.

  • An 'object' in the managed signature will not marshal into a by value variant, but instead will be 'NULL'.  You can override this behavior by using MarshalAs(UnmanagedType.Struct) in your PInvoke signature, which will cause the object to be marshaled as a variant under both .NetCF and the desktop.
  • A class in the managed signature will not marshal into a CCW, but instead will result in a pointer to a structure in native code.  You can override this behavior by using MarshalAs(UnmanagedType.Interface) in your PInvoke signature, which will cause the object to be marshaled as a CCW under both .NetCF and the desktop.

3. A native exception occurs after the native function has returned or at inconsistent times, due to improper use of runtime memory by the native code.
During the marshaling process, the runtime will sometimes place the parameter data inside a temporary buffer or temporarily on the stack. For this reason, it is not always safe for native code to hold a reference to the memory containing marshaled data. Although, in some cases this will work with our current implementation depending on the particular types marshaled, it is recommended that native code always copy data into its own buffer when marshaling any complex types.
If you must avoid copying data, use an IntPtr in your managed interop signature. In the event you are passing a pointer associated with managed object data to native code that will live beyond the return of the native function call, be aware that the GC can cause objects to move around. It is recommended that you maintain a pinned reference to the object, by using a GCHandle.
When returning from a native function call, the marshaler will free native memory associated with arguments after the back propagation into the managed object has completed. This includes cases where only out-marshaling has occurred as well as cases where the content pointed to has changed while in the native function. The runtime will use CoTaskMemFree (or SysFreeString in the case of BSTRs), and expects any non-null memory returned to have been allocated with CoTaskMemAlloc. When necessary, the runtime will free both the original data allocated during the in-marshaling process, and the new data allocated inside the native function call. To avoid this behavior, you can use IntPtr in the managed interop signature, in which case you are then responsible for cleaning up the memory yourself. The types that are affected by this include, but are not limited to, Formatted Classes, Strings, StringBuilders, and Arrays.
You must also use IntPtr marshaling when interacting with native APIs that will return data from the middle of an allocated buffer, such as VerQueryValue. You can see more information about cases where IntPtr marshaling may be necessary for certain APIs here:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconmemorymanagement.asp

4. There appears to be a memory leak when using BSTR marshaling, due to a native cache in the Windows CE OS.
The memory used for BSTR allocations is cached, so a BSTR is not always freed completely upon a call to SysFreeString. There is currently no way to turn this cache off under Windows CE, and given the low memory conditions of embedded devices this may present itself as an unacceptable memory leak. To work around this issue, consider using CoTaskMemFree instead of SysFreeString, which prevents any free space in the cache from accumulating.

The runtime will use SysFreeString under two conditions:

  • During the marshaling process when you specify MarshalAs(UnmanagedType.BSTR) for a parameter, array element, or structure field. You can prevent this from happening by using IntPtr marshaling, and manually converting strings to BSTRs before calling the native function using InteropServices.Marshal.StringToBSTR. 
  • The Marshal.FreeBSTR managed API maps directly to SysFreeString.

The native COM component being used will also need to be screened for SysFreeString use.
You can see some more information about the BSTR cache here:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/automat/htm/chap7_2xgz.asp

Making Native function calls from Managed code

There are 2 main types of managed to native function calls:

  • PInvoke - Using the DLLImport attribute, a signature is defined in managed code that corresponds to an exported function in a native .dll. 
  • COM interface method call - a Runtime Callable Wrapper (RCW) object is instantiated in managed code that wraps a native COM object and then cast to a managed interface describing the native COM interface.
    • COM VTable call - For interfaces that are declared as Dual or IUnknown, the runtime will call the method directly through the VTable.
    • COM Dispatch call - For interfaces that are declared as pure IDispatch, the runtime will call the method through the use of IDispatch->Invoke.

In all three of these cases, there is a stub generated that converts between the managed calling convention and the native calling convention. In order to create this stub, the signature of the PInvoke or COM interface method is processed and any error checking that only requires knowledge of the parameter types and not the actual data occurs.
During runtime when the function is called and this stub is executed, the arguments are in-marshaled as specified, then the native function is called, and finally the out-marshaling occurs along with some runtime post-processing, cleanup, and error handling. If an exception occurs during the in-marshaling process, the native function will not be called.
If a native function call is considered PreserveSig(false), the runtime will assume the native function returns a HResult. In the event that a failure HResult is returned, the runtime will translate this into a managed exception, and managed objects used as parameters will not necessarily be modified or initialized during the out-marshaling process as normal. In this case, you must also keep in mind that the managed signature will not look exactly like the native signature. You can use interop logging to determine the exact native signature the runtime will be expecting. For COM calls PreserveSig defaults to false, where as for PInvoke calls it defaults to true, if not explicitly specified.

Knowledge of this process can help you diagnose and handle a few common issues:

5. A MissingMethodException occurs when trying to call a PInvoke function, due to the .dll not being found by the runtime or because the function requested is not exported as specified in the native .dll.
In this case, the exception occurs at stub generation time rather then at runtime. Normally a PInvoke function is jitted upon its first call, however you can use the Marshal.PreLink function to force the runtime to jit the stub, allowing you to handle these cases smoothly in managed code and present the user with an appropriate message.
If the runtime is failing to find the native .dll, ensure it is present and consider writing a simple native application that calls LoadLibraryW to load the .dll. In some cases under generic windows CE, a native .dll may have a dependency on the components of the image, and may fail to load if the dependencies are not present in the image.
If the runtime is failing to find the particular exported function in the native .dll, ensure the function is present and properly exported using the dumpbin utility as described here: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/netcfdumpbinpinvoke.asp. Keep in mind, you can reference the exported functions either by ordinal or name.

6. A managed exception occurs when calling a native function from managed code, due to a runtime marshaling issue.
In the case a PInvoke function call is being made, it is worthwhile to use Marshal.PreLink and ensure no exceptions are thrown. This will rule out jit-time errors that occur when the stub is generated, such as those resulting from unsupported marshaling types. Some marshaling issues will not occur until the function is called and the actual conversion work happens. These typically happen when trying to convert a native object into a managed object, either during the out-marshaling process of a managed to native function call, or during the in-marshaling process of a native to managed call. Some types have specific ranges in which the data is valid, such as the case with the DateTime type. In the case of VARIANTs, the runtime many not be able to coerce the type specified inside the native variant to the managed type specified in the function signature.
The best practice to diagnose these issues is to use a process of elimination to determine the particular parameter causing the issue. You can use the [in] and [out] attributes to prevent marshaling in a particular direction for each parameter.

7. A native COM function results in an unexpected managed exception or the native function behaves incorrectly, due to the managed COM interface signatures being incorrect with regard to the PreserveSig attribute.
If the PreserveSig attribute is used incorrectly, the runtime will assume an incorrect native signature and may call the function with un-initialized parameters, or interpret the return valid as a failure HResult when the native function does not return a HResult.
The interop log file will reveal the expected native signature, and this should be compared to the documentation associated with the COM interface.

Making Managed function calls from Native code

There are 2 main types of native to managed function calls:

  • Delegate Callbacks - You can marshal a delegate down to native code as a function pointer
  • COM interface method call - a Com Callable Wrapper (CCW) is associated with a managed object and used to expose interfaces that object implements to native code.
    • COM VTable call - For interfaces that are declared as Dual or IUnknown, native code can directly call managed methods
    • COM Dispatch call - For interfaces that are declared as pure IDispatch and Dual, native code can call managed methods through the use of IDispatch->Invoke.

The process for making callbacks from native code into managed code is similar to the other direction. Both stub generation errors and runtime errors are possible. Likewise, based on the PreserveSig attribute, the runtime may translate managed exceptions to HResults. If the managed function call is considered PreserveSig(false) and an uncaught exception occurs while in managed code, the runtime will return a HResult associated with the exception to the native caller. For COM calls PreserveSig defaults to false, where as for delegate callbacks it results to true, if not explicitly specified. If a COM call is marked as PreserveSig(true) and the managed signature returns a 4-byte value such as an int, the runtime will still translate any uncaught exceptions to a HResult.
The .Net Compact Framework does not support hosting the runtime from native code. This means that you will not be able to call CoCreateInstace to instantiate a managed object, or register managed objects as COM objects on the system. You will also not be able to call CorBindToRuntime or ClrCreateManagedInstance. In order to call managed functions from native code, you must first use the runtime to marshal a managed interface or a delegate down to native code. This means you must always start out in managed code (with a .net executable) in order to expose .net components to native code.

8. A native exception occurs when attempting to call a Delegate callbacks from native code, due to the managed delegate being finalized.
When using delegate callbacks, you often first make a PInvoke call to register the callback function pointer which will be used after the original PInvoke call returns. Because the generated stub associated with the callback stub is cleaned up when a delegate is finalized, you must ensure the delegate object stays alive until it will no longer be used by native code. Simply keeping a reference to the delegate in managed code will be enough to keep it alive (there is no need to pin the delegate object).

9. Native code that uses native to managed function calls is behaving unexpectedly because of a managed exception firing or runtime marshaling errors, due to interface signatures being incorrect with regard to the PreserveSig attribute.
When a managed exception occurs inside a managed function call that is not caught before the call returns, operating under PreserveSig(false) is the only way to ensure this exception will be translated to a HResult and properly returned from the managed call so native code can handle the error. If operating under preserveSig(true), it is recommended that you wrap a try/catch around your exposed managed function to ensure there are no uncaught exceptions and handle any errors in managed code since native code will not be able to know there was an error or handle it properly. Also keep in mind, that an exception can also occur due to a runtime marshaling error and cause the managed function to not be called.

10. A CCW function call works when called through the VTable, but not when called through IDispatch->Invoke despite the interface being marked as InterfaceIsDual, due to missing or improper use of attributes.
Individual methods, as well as interfaces, can be marked ComVisible. When a method is marked ComVisible(false), a VTable stub is still generated for it allowing it to be called directly through the VTable, but it is protected from being called through IDispatch->Invoke. In the case of properties, the individual get or set method is checked, as well as the property declaration itself.
If native code calls QueryInterface requesting a pure IDispatch pointer, the runtime will return the interface that it has determined is the 'default interface' that supports IDispatch.  Typically, under the desktop framework this resolves to the class interface and exposes all methods to native code.  Under .NetCF, since class interfaces are not automatically generated, this will resolve to the first interface present in metadata that the class implements which supports IDispatch.  It's recommended that you explicitly declare a default interface using the ComDefaultInterfaceAttribute. 
For methods to be exposed through IDispatch, they must be explicitly marked with the DispIdAttribute.
If native code is relying on IDispatch->GetIDsOfNames to resolve methods, there are a couple of limitations.  The interop log file will contain more detailed output if unsupported functionallity is being requested or an error is being returned to native code from GetIDsOfNames.

COM Interop

The .Net Compact Framework supports interoperating with native COM objects from managed code as well as allowing native code to interact with managed objects using COM. 

  • Runtime Callable Wrappers (RCWs) - these are used to allow managed code to call native COM object functions.
    • There are 3 ways to create an RCW:
      • You can use a tool called tlbimp to import a type library (shipped with the native COM object), and spit out a managed .dll. This managed .dll will contain a bunch of interfaces associated with the native COM interfaces supported by the object. It will also contain a managed class (with a guid and ComImport attribute specified) that is associated with the actual COM object. When this managed class is instantiated, the runtime will make a call to CoCreateInstance and instantiate the native COM object. Using this method requires that the native COM object has been properly registered with the system and the guid is present in the registry for lookup.
      • You can then use the following code to manually instantiate an object

        Type t = Type.GetTypeFromCLSID(new Guid("2CD19942-4103-4dcc-A75C-57DF5814C611") ); // This guid is associated with the COM object itself, not the native COM interfaces you'd like to use to interact with the object

        Object obj = Activator.CreateInstance(t); // This causes the runtime to call CoCreateInstance

        Alternatively, you can use Type.GetTypeFromProgID to use the registry to resolve a string into a GUID associated with a native COM object.
        Using this method requires that the native COM object has been properly registered with the system and the guid and\or progID is present in the registry for lookup.
      • You can also create an RCW object via marshaling when making a managed to native call that causes a COM object to be out-marshaled. For example, a native .dll can have a 'CreateComObject' exported function that returns a native COM object, and therefore doesn't require the native COM object to be registered by the system.
    • The runtime will hide many of the rules that are required when using native COM objects. For example, you don't need to manually call QueryInterface in managed code. Simply casting an RCW object to a particular interface causes the runtime to do a QueryInterface under the scenes, and a managed InvalidCast exception is thrown if the native COM object doesn't support the guid associated with the casted interface. The runtime also handles the reference counting details.
  • Com Callable Wrappers (CCWs) - these are used to allow native code to call managed functions exposed through COM interfaces.
    • CCWs are created when an object is marshaled down to native code as an interface. Once in native code, other interfaces can be retrieved using QueryInterface, including a number of runtime implemented interfaces such as IConnectionPointContainer. You can call managed functions directly through the VTable, or through IDispatch->Invoke depending on the type of COM interface as declared in managed code. The native code must abide by normal reference counting and COM rules when interacting with CCWs.

TlbImp is the only tool supported under the .Net Compact Framework. While the use of other tools, such as AxImp, and TlbExp may work in some cases, they are not tested and may not always produce code that will run without modification under the .Net Compact Framework.

11. There appears to be a memory leak when using COM interop features, due to Runtime Callable Wrappers not being freed immediately.
Normally, RCWs are freed upon finalization when the managed object associated with the native COM object is no longer in use and the GC starts collecting object memory. At this point, the original object returned from CoCreateInstance is Released, along with any interface pointers that the runtime called QueryInterface to retrieve.
Marshal.ReleaseComObject and Marshal.FinalReleaseComObject can be used to manage the lifetime of an RCW manually. Keep in mind that once these are used you will not be able to make native COM interface method calls using the object any longer and will receive an exception upon attempting to do so.
There is also a native bug in Windows CE TypeLib support that can cause a memory leak when a native COM object that uses LoadTypeLib or LoadRegTypeLib is freed from a thread other then the one it was created on, including the thread finalizers are run on. In this case, it is highly recommended you use the marshal class functions to manually release the native COM object.

12. A native control doesn't appear properly or doesn't work properly when using COM interop features to host an ActiveX control in a managed Form, due to a bad ActiveX container implementation.
Be sure to review the following references and understand how to write a container in native code.
http://msdn.microsoft.com/library/default.asp?url=/workshop/components/containers/overview/containers.asp
http://msdn.microsoft.com/library/default.asp?url=/workshop/components/activex/intro.asp
It is also recommended that you create a simple native ActiveX control to test your container with (possibly using the Visual Studio MFC wizard), which will allow you to debug into the native source of the control.
When debugging an ActiveX container implementation, it may helpful to examine the interop log file for cases where native code called QueryInterface and received an E_NOINTERFACE return value. This can help you diagnose when a required interface was not implemented by the container.
If the application is running, but the control doesn't appear in the form, use the Remote Spy tool to examine the window hierarchy and position/sizing of the native control and its parents.

13. A native exception or memory leak occurs when using Com Callable Wrappers to provide native code an interface mapping to managed functions, due to improper reference counting logic.
A CCW will be cleaned up upon the last reference count release from native code. Native code is expected to release any interfaces it acquired from a call to QueryInterface, as well as other functions that return an interface (such as IConnectionPointContainer::FindConnectionPoint for example).
If native code wishes to keep a reference to a CCW beyond the return from the PInvoke where a CCW was marshaled down, the particular interface desired should be retrieved through QueryInterface. When the PInvoke returns and the out-marshaling process completes, the CCW will be cleaned up if there are no outstanding references.
When using the runtime implemented connection point interfaces to allow native code to get notified when a managed event fires, the runtime will increment the CCW reference count upon a call to IConnectionPoint::Advise. Native code must call IConnectionPoint::Unadvise when it no longer wishes to be notified of managed events firing or the CCW will not be cleaned up.
You can use the Marshal Class functions to retrieve an IntPtr representing an interface from a managed object (See GetIUnknownForObject), You can also examine the current reference count for an IntPtr representing an interface (including a CCW) by using Marshal.AddRef and Marshal.Release (which both return the current reference count). 

14. A managed exception occurs when instantiating or using a Runtime Callable Wrapper or a Com Callable Wrapper despite the same code working on the desktop framework, due to limitations in com interop support under the .Net Compact Framework.
The interop log file will often contain more detailed information about the cause of an exception that occurs due to differences in COM interop support between the desktop framework and the .Net Compact framework.

  • The .Net Compact Framework does not support class interfaces. Under the desktop, a class interface will be automatically generated for any managed objects exposed as CCWs to native code, allowing you to directly access any field members or functions present in the class. Under the .Net Compact Framework, all functionality you wish to expose must be wrapped in an interface. It is also recommended that you explicitly mark classes as ClassInterfaceType.None using the ClassInterfaceAttribute, when passing classes directly to native code.
  • The .Net Compact Framework will not automatically generate a guid for interfaces lacking an explicitly defined guid. If an interface is marked ComVisible(true) and is exposed to native code, it must also have a guid specified or an exception will occur.
  • The .Net Compact Framework does not allow you to inherit from an interop assembly class generated through the use of tlbimp.
  • The .Net Compact Framework does not support STAThread COM initialization, because Windows Mobile does not allow you to initialize COM in this way.
  • The .Net Compact Framework has some marshaling support differences from the desktop framework.
  • The .Net Compact Framework has marked all base class libraries as ComVisible(false) at the assembly level, which requires developers to wrap any BCL functionality they wish to expose to native code.
  • The .Net Compact Framework has some subtle differences in the logic to determine COM visibility for interfaces.
    • Any generic or open type interfaces are automatically not visible to COM.
    • Any classes marked as ComImport are visible to COM.
    • Any classes without the public access modifier are considered to be not visible to COM.
    • Finally the class is checked the ComVisible attribute. If it is not present, the assembly is checked for the ComVisible attribute. If the ComVisible attribute is not declared then the default is ComVisible(true).
    • This logic is shared between RCWs and CCWs.
      • For the RCW case, if an interface is not considered to be visible to COM, then you will not be able to cast to it. (The runtime will not attempt to call QueryInterface on the native COM object associated with the RCW object).
      • For the CCW case, if an interface is not considered to be visible to COM, then native code will not be able to successfully call QueryInterface requesting it. Developers will also not be able to marshal an interface not visible to COM down to native code.

15. A managed InvalidComObjectException occurs when using a Runtime Callable Wrapper to call a native COM function, due to the Runtime Callable Wrapper having been freed previously.
If the application has used Marshal.ReleaseComObject or Marshal.FinalReleaseComObject, you will not be able to make native COM interface method calls using the object any longer. If an attempt to do this has been made upon a previously valid RCW object, the interop log file will contain an error message.

16. A managed InvalidComObjectException occurs when attempting to instantiate a Com Callable Wrapper, due to the use of a type that requires an unsupported marshaling feature.
When a CCW is constructed, the object's entire interface hierarchy is walked by the runtime. All interface methods visible to COM that are implemented will have a stub generated for them to allow the managed function call from native code. If the runtime cannot marshal any parameter type for any method, the entire CCW will not be able to be constructed. The interop log file will contain more information specifying the exact method containing the issue.

Resources

With regard to COM interop support, it may be useful to understand both the rules around using and building native COM objects, as well as how COM is integrated and supported in the .Net runtime. I like the following sources:
1) Inside COM, by Dale Rogerson (red book)
2) .Net and COM, by Adam Nathan (blue book)

All applications should be tested with Interop Logging turned on, even if there are no known issues. This will produce a log file that should then be searched for 'ERROR' and 'WARNING'. The log file will contain more information about the cause of managed exceptions, subtle errors that do not manifest themselves as managed exceptions, as well as assist in debugging interop issues involving native code for which the source is unavailable.
http://blogs.msdn.com/netcfteam/archive/2005/07/24/442609.aspx

There are interop related guidelines and tips that can drastically improve your application's performance: http://blogs.msdn.com/netcfteam/archive/2005/05/04/414820.aspx

Default marshaling for various types:
Arrays: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforarrays.asp
Boolean: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforbooleans.asp
Classes: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforclasses.asp
Value Types / Structs: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforvaluetypes.asp
Strings: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforstrings.asp
Objects: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondefaultmarshalingforobjects.asp

 

[Author: Katie Blanch]

Disclaimers:
This posting is provided "AS IS" with no warranties, and confers no rights.
Some of the information contained within this post may be in relation to beta software. Any and all details are subject to change.