P/Invoke

The task of calling unmanaged code begins with declaring a static external method in a class with parameters that match the calling parameters of the unmanaged function. The method is then marked with a special attribute to indicate its purpose to the compiler. At run time, the method is simply called like any other method in the class. The call to the method results in the Compact Framework loading the DLL containing the function to be called, marshaling the calling parameters onto the stack, and calling the function.

The following is a simple but contrived example of calling outside the framework. Before I dive into the discussion of this example, be aware that there are managed ways of computing elapsed time without calling into unmanaged code. I'm simply using GetTickCount since it's a simple function with no parameters.

public class SomeClass
{
    private uint OldCnt;
    public void SomeClass()
    {
        OldCnt = 0;
    }
    [DllImport ("coredll.dll")]
    private static extern uint GetTickCount ();

    public uint TicksSinceLast (){
        uint ticks = GetTickCount();
        uint diff = ticks – OldCnt;
        OldCnt = ticks;
        return diff;
    }
}

This code shows a class that contains two methods, GetTickCount and TicksSinceLast. The GetTickCount method is marked as static, meaning that it's defined by the class, not an instance of the class; and extern, meaning that the method body is defined outside the class. In this case, the method body is actually implemented in unmanaged code.

Just above the definition of GetTickCount is the DllImport attribute enclosed in the square brackets. The DllImport attribute marks the method as being a P/Invoke call to unmanaged code. The single parameter for DllImport, in this case, is the name of the unmanaged DLL that implements the function GetTickCount. The DLL, Coredll.dll, is the standard API DLL for Windows CE and exposes most of the functions supported by the Windows CE operating system, including GetTickCount.

The TicksSinceLast method calls GetTickCount just as it would any other managed method in the class. No special syntax is necessary for the method when the class calls it. There is also no need for the P/Invoke method to be marked private, although it typically is good practice since the programmer who wrote the class knows the method is a P/Invoke call and can provide proper precautions such as couching the call in a try, catch block to catch exceptions specific to the P/Invoke call.

The DllImport attribute class can specify more than simply the name of the DLL to call. Although the Compact Framework supports other fields in DllImport, only two are useful to the application: EntryPoint and SetLastError.

The EntryPoint field of DllImport allows the application to specify a name for the unmanaged entry point in the DLL that's different from the name of the managed method. The EntryPoint field is handy when calling a Windows CE API that has a string as a parameter. Win32 API convention specifies that the real name of a function with a string parameter have a suffix of either W or A, depending on whether the function expects Unicode- or ANSI-formatted strings. Even though Windows CE supports only Unicode entry points, the names of the functions exported by Coredll.dll have the W suffix. C and C++ applications don't normally see the W suffix because the .h files in the SDK redefine the generic function names used in the application without the W or A to the specific API name that's used when the application is compiled. The following code fragment shows an example of specifying the function name when calling the unmanaged API SetWindowText:

[DllImport ("coredll.dll", EntryPoint="SetWindowTextW")]
private static extern void SetWindowText (IntPtr h, string s);

The name traditionally used for the entry point is SetWindowText. However, Coredll.dll exports the function as SetWindowTextW. The use of the EntryPoint field in the DllImport attribute specifies the correct entry point name while retaining the traditional name for references within the managed code.

The other useful field in the DllImport attribute class is SetLastError. This field is defined as a bool. Setting this field to true tells the runtime to save the last error value set by the call to the unmanaged code. This allows the managed code to later call the GetLastWin32Error method of the Marshal class to retrieve the last error value. If the SetLastError field is not set to true, the default is not to save the last error value of the P/Invoke call.

None of the other fields in the DllImport attribute class that are supported by the .NET Compact Framework have much use. The CharSet field allows the application to specify whether the strings being passed to the unmanaged code should be converted to ANSI or remain Unicode. On the Compact Framework, the only values supported for the CharSet field are Auto and Unicode. Since Auto defaults to Unicode on the Compact Framework, these two values mean the same thing. The CallingConvention field can also be set, but here again, the single value supported by the Compact Framework has no real effect on the processing of the P/Invoke call.

This topic is from Programming Microsoft Windows CE, Third Edition, by Douglas Boling, published by Microsoft Press. © 2003 by Douglas McConnaughey Boling. Reprinted here by permission of the author.