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 LogThe 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.
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.
Diagnosing a P/Invoke call issueOne 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,-- DKEdit: Fix error in registry steps.Edit: Fix UseApp steps
int SampleFunction(long lData){ // int == long on Win32 systems return lData;}
[DllImport("InteropLogExampleNative.dll", SetLastError=true)]public static extern int SampleFunction(long lData);
int result = SampleFunction(long.MaxValue);
[pinvokeimpl][preservesig]int InteropLogPInvokeExample.Program::SampleFunction(long );int (I4_VAL) SampleFunction(INT64 (I8_VAL) );
// 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);
Disclaimer(s):This posting is provided "AS IS" with no warranties, and confers no rights.