OK, so this first example is going to show how to call an unmanaged dll from .NET (C#). There's no better way to explain how it all fits together than by example, so first off we're going to create an unmanaged dll in C++. The function we're exporting from the dll would obviously be of vital importance to your business in the real world and contain a wealth of logic, but for the sake of simplicity let's have a void function that takes a basic struct as an argument and does nothing more than alter the fields within it.
The header file in your project should contain the following definitions:
OK, I'm hoping the struct decleration doesn't need any explanation, we're simply defining a structure that contains two fields, one of type int and one of type double.
Our function definition is a little more complicated however, so let's start from left to right and work our way through it.
In C++, because functions can be overloaded (differing not by name but by signature [mixture of name and parameters]) the compiler goes through a process of 'decorating' the names internally so it can uniquely identify them when they're called. To simplify this example, we want to use the function name as we've written it from within our C# code and not a mangled representation. Using extern "C" forces the compiler to use the actual function name (as it would in C). This prevents us from overloading this function but we're not bothered about that in this example.
On a related note, if you want to examine a dll to find out, amongst other things, exported function names, you can use the dumpbin command from the Visual Studio command prompt. Typing dumpin /exports filename will list the exported function names from the dll. Try it on our simple dll with and without the extern "C" keywords to see the decoration in action.
__declspec(dllexport) puts the plumbing in place that's actually going to allow our function to be exported from our dll. It adds the export directive to the object file so we don't need to bother around with a .def file.
void PassStructIn(MyStruct* myStruct); OK, so our function is void (doesn't return anything), is named PassStructIn and takes a single argument of type pointer-to MyStruct.
The actual function definition in the source file should look something like this:
This is basic indeed. All it does is check that the pointer to our struct isn't NULL and then attempts to alter the two fields within it.
OK, that's the unmanaged code out the way, let's move on to the managed code now and utilise our 'feature rich' dll... ; )
I started off by creating a C# console application, and then adding a class within it named NativeMethods. This class is going to neatly wrap all of our native calls and such like. Because our unmanaged function requires a structure as a parameter, the structure needs to be defined in the managed code as well as in the unmanaged code. Following is our NativeMethods class definition:
Notice that the fields within the structure definition are defined in the same order as in the unmanaged C++ structure and are of the same type. If they weren't, we would have to decorate the structure with the [StructLayout] attribute, passing in a value from the LayoutKind enumeration. If it's not provided (as in our example), it defaults to:
This tells the marshaller that the fields within our structure should be laid out in the same sequence as they're defined. The other two permissable values are Auto and Explicit. Auto instructs the runtime to lay the fields out how it sees fit, and Explicit gives you the ability to define precisely how each field is to be laid out.
Next up is our DllImport attribute, where we specify the full name of the unmanaged DLL that our function is contained within. There are some optional parameters we can provide this attribute with, which I'll cover in later posts. The only one I need to mention now is the EntryPoint parameter, which we haven't specified (and for good reason). This allows us to specify the name of the function within the dll if we want the name of our managed wrapper function to be different. In our case, PassStructIn is the name of our unmanaged function, as well as our managed function and so EntryPoint can be ommitted. If our unmanaged function name was decorated and rather unwieldy, we might be tempted to specify this in the EntryPoint parameter and keep our managed function name neat and tidy.
All that remains is for us to utilise our code like so and hey presto!
We define our managed struct and set it's fields to two arbitrary values, before calling our managed wrapper and passing the struct in. Notice we pass it in by reference, as our unmanaged function expects a pointer.
Our output shows that the two fields were then changed within the unmanaged C++ code, simple eh?