C Sharp

Platform Invocation Services

The .NET Platform Invocation Services-sometimes referred to as PInvoke-allows managed code to work with functions and structures that have been exported from DLLs. In this section, we'll look at how to call DLL functions and at the attributes that are used to marshal data between a .NET application and a DLL.

Because you're not providing the source code for the DLL function to the C# compiler, you must specify to the compiler the signature of the native method as well as information about any return values and how to marshal the parameters to the DLL.

NOTE
As you know, you can create DLLs with C# and other .NET compilers. In this section, I've avoided using the term "unmanaged Win32 DLL." You should assume that any time I refer to a DLL, I'm referring to the unmanaged variety.

Declaring the Exported DLL Function

The first issue we'll look at is how to declare a simple DLL function in C#. We'll use what's fast becoming the canonical .NET PInvoke example, the Win32 "MessageBox" example, to get things started. Then we'll move into the more advanced areas of parameter marshalling.

As you learned in Chapter 8, "Attributes," attributes are used to provide design-time information for a C# type. Through reflection, this information can later be queried at run time. C# makes use of an attribute to allow you to describe the DLL function that the application will call to the compiler. This attribute is called the DllImport attribute, and its syntax is shown here: -

[DllImport(dllName)]
accessModifier static extern retValue dllFunction ( param1, param2,...);

As you can see, importing a DLL function is as easy as attaching the DllImport attribute (passing the DLL name to its constructor) to the DLL function you want to call. Pay particular attention to the fact that the static and extern modifiers must also be used on the function that you're defining. Here's the MessageBox example showing how easy PInvoke is to use: -

using System;
using System.Runtime.InteropServices;
class PInvoke1App
{
    [DllImport("user32.dll")]
    static extern int MessageBoxA(int hWnd,
                                 string msg,
                                 string caption,
                                 int type);
    public static void Main()
    {
        MessageBoxA(0,
                    "Hello, World!",
                    "This is called from a C# app!",
                    0);
    }
}

Running this application results in the expected message box popping up with the "Hello, World!" statement.

Notice the using statement that refers to the System.Runtime.InteropServices namespace. This is the namespace that defines the DllImport attribute. After that, note that I've defined a MessageBoxA method that's called in the Main method. But what if you want to call your internal C# method something other than the name of the DLL function? You can accomplish this by using one of the DllImport attribute's named parameters.

What's happening in the following code is that I'm telling the compiler that I want mymethod to be called MessageBoxA. Because I didn't specify the name of the DLL function in the DllImport attribute, the compiler assumes that both names are the same.

    [DllImport("user32.dll")]
    static extern int MessageBoxA(int hWnd,
                                  string msg,
                                  string caption,
                                  int type);

To see how to change this default behavior, let's look at another example-this time using an internal name of MsgBox while still calling the DLL's MessageBoxA function.

using System;
using System.Runtime.InteropServices;
class PInvoke2App
{
    [DllImport("user32.dll", EntryPoint="MessageBoxA")]
    static extern int MsgBox(int hWnd, 
                             string msg,
                             string caption,
                             int type);
    public static void Main()
    {
        MsgBox(0, 
               "Hello, World!",
               "This is called from a C# app!",
               0);
    }
}

As you can see, I need only specify the DllImport attribute's EntryPoint named parameter to be able to name my internal equivalent of the external DLL function anything I like.

The last thing we'll look at before moving on to parameter marshalling is the CharSet parameter. This parameter lets you specify the character set used by the DLL file. Typically, when writing C++ applications, you don't explicitly specify MessageBoxA or MessageBoxW-a pragma has already let the compiler know whether you're using the ANSI or Unicode character set. That's why in C++ you call MessageBox and the compiler determines which character set version to call. Likewise, in C# you can specify the target character set to the DllImport attribute and have the target set indicate which version of the MessageBox function to call. In the following example, the MessageBoxA function will still be called as a result of the value I've passed to the DllImport attribute through its CharSet named parameter.

using System;
using System.Runtime.InteropServices;
class PInvoke3App
{
    // CharSet.Ansi will result in a call to MessageBoxA.
    // CharSet.Unicode will result in a call to MessageBoxW.
    [DllImport("user32.dll", CharSet=CharSet.Ansi)]
    static extern int MessageBox(int hWnd,
                                 string msg,
                                 string caption,
                                 int type);
    public static void Main()
    {
        MessageBox(0,
                   "Hello, World!",
                   "This is called from a C# app!",
                   0);
    }
}

The advantage of using the CharSet parameter is that I can set a variable in my application and have it control which version (ANSI or Unicode) of the functions are called; I don't have to change all my code if I change from one version to the other.

by BrainBellupdated
Advertisement: