Sometimes, determining why an unmanaged function call (P/Invoke) has failed can be challenging.  Fortunately, version 2 of the .NET Compact Framework has a new diagnostic log for this data -- the Interop Log.  In June 2005, Steven Pratschner wrote a great post detailing the Inteop Log and it's contents: Diagnosing Marshalling Errors using Interop Logging.

I would like to take some time to explore a specific example where the Interop Log can save time and make P/Invoke call debugging quick and easy.

Enabling the Interop Log
The Interop Log is enabled via the device's registry. In my steps, below, I will be using the Remote Registry Editor that is included with Visual Studio 2005.

WARNING: Using Remote Registry Editor incorrectly can cause serious problems that may require you to hard reset your device. Microsoft cannot guarantee that problems resulting from the incorrect use of Remote Registry Editor can be solved. Use Remote Registry Editor at your own risk.

  1. Start the Remote Registry Editor
    • Open the Start Menu
    • Select All Programs
    • Select Microsoft Visual Studio 2005
    • Select Visual Studio Remote Tools
    • Select Remote Registry Editor
  2. Connect to your device
    • On the Target menu, select Connect
    • Select your device and click OK
  3. Enable diagnostic logging
    • In the left hand pane, expand the device
    • Expand HKEY_LOCAL_MACHINE
    • Expand SOFTWARE
    • Expand Microsoft
    • Expand .NETCompactFramework
    • Expand Diagnostics
      If this key does not exist, you will need to create it
      • Right click on .NETCompactFramework
      • Select New > Key
      • Type "Diagnostics" (without quotes)
      • Click OK
    • Expand Logging
      If this key does not exist, you will need to create it
      • Right click on Diagnostics
      • Select New > Key
      • Type "Logging" (without quotes)
      • Click OK
    • Set the value of Enabled to 1
      If this value does not exist, you will need to create it
      • Right click on Logging
      • Select New > DWORD Value
      • Set the Name to "Enabled" (without quotes)
      • Set the value to 1
      • Click OK
  4. Enable interop logging
    • In the Logging key (from step 3) expand Interop
      If this key does not exist, you will need to create it
      • Right click on Logging
      • Select New > Key
      • Type "Interop" (without quotes)
      • Click OK
    • Set the value of Enabled to 1
      If this value does not exist, you will need to create it
      • Right click on Interop
      • Select New > DWORD Value
      • Set the Name to "Enabled" (without quotes)
      • Set the value to 1
      • Click OK

To disable the Interop Log, follow the above steps and set the value of Enabled to 0.
To disable all logging, follow only steps 1-3, and set the value of Enabled to 0.

There are a few optional settings that can be set to configure how the Interop Log is created. For details, please refer to Steven's post.

One optional setting I highly recommend is to create application specific log files. This causes the .NET Compact Framework to name the log file based on your application name. For example, if I enable logging with the UseApp option, the log file created for an application called DkTest.exe will be netcf_DkTest_interop.log.

To enable application specific log files please make the following addition to the above steps.

  1. Enable application specific log files
    • In the Logging key set the value of UseApp to 1
      If this value does not exist, you will need to create it
      • Right click on Logging
      • Select New > DWORD Value
      • Set the Name to "UseApp" (without quotes)
      • Set the value to 1
      • Click OK

Diagnosing a P/Invoke call issue
One of the things that I sometimes forget is that .NET types are not necessarily the same as Win32 types.  One type, in particular, where this is true is long.  On Win32, a long (or LONG) takes up 4 bytes (32 bits).  In the .NET world, a long is 8 bytes (64 bits) in size.

Let's look at an example where this type size difference can cause application issues.

First, a simple function written in C/C++
int SampleFunction(long lData)
{
    // int == long on Win32 systems
    return lData;
}

To be able to call this function, our managed application must define the signature for the P/Invoke.
[DllImport("InteropLogExampleNative.dll", SetLastError=true)]
public static extern int SampleFunction(long lData);

Now we can call our function.
int result = SampleFunction(long.MaxValue);

If you add the above code to a simple project and attempt to call the P/Invoke method, depending on the data passed to the function, you may encounter "calculation errors" related to the wrong data type being sent to the unmanaged function.

If the Interop logging is enabled, we can see that the application tried to call the method with an 8 byte value when the method was expecting only 4 bytes. 

[pinvokeimpl][preservesig]
int InteropLogPInvokeExample.Program::SampleFunction(long );
int (I4_VAL) SampleFunction(INT64 (I8_VAL) );

To quote Steven: "The Compact Framework does not have intrinsic knowledge of the native function you are calling – it simply takes the managed definition you’ve provided and creates an equivalent native signature. If this signature doesn’t match the actual signature of the targeted native function, a marshaling error will occur."

By changing the P/Invoke signature to specify a 4 byte integer, the call succeeds.

// managed code
// p/invoke method signature. 
//
// note: a Win32 long is the same size as a .NET int
[DllImport("InteropLogExampleNative.dll", SetLastError=true)]
public static extern int SampleFunction(int lData);


These types of issues can sometimes be difficult to notice.  For example, in our simple unmanaged (native) function, we are simply returning the value passed as the argument.  If the value happens to be one that can fit within a 32 bit integer, the failure may go unnoticed.  If the value exceeds that which will fit in a 32 bit integer and the code does not validate the data returned from the call, the failure may also go unnoticed.

The moral of this story is that it is very important to validate the behavior of all P/Invoke calls by examining the returned data or any other means appropriate.  I also highly recommend enabling Interop logging and examining the P/Invoke signature data in the log file as a standard part pre-checkin unit testing and release criteria if your application makes calls to unmanaged functions.

Take care,
-- DK

Edit: Fix error in registry steps.
Edit: Fix UseApp steps

Disclaimer(s):
This posting is provided "AS IS" with no warranties, and confers no rights.