0

Using C/C++ within C#, Part 2! – Writing and using a DLL

Welcome back!

Last time we’ve learned the theory about including external libraries and modules into our programs. But the way we did it was very static (no pun intended). Luckily, kernel32.dll can help us out here. Let’s start by loading the “LoadLibrary”, the “FreeLibrary” and the “GetProcAddress” functions from the kernel32.dll into our program the way we learned to do it in the last part.

[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool FreeLibrary(IntPtr hModule);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

Invoking Native Library Functions in C#

Now we can begin loading methods from other dll’s.

We will begin by using the LoadLibrary method. LoadLibrary returns an IntPtr, which is the pointer to our newly loaded module. This one will be important for invoking methods from this module, so we will save this one in a field.

public class Program
{
    static IntPtr hCurModule;

    public static void MethodOne()
    {
        hCurModule = LoadLibrary("shell32.dll");
    }
}

Now that we have loaded the shell32.dll, we can access it’s methods. Last time I mentioned the method “PathYetAnotherMakeUniqueName”. Let’s call it!
From pInvoke.net we know, that this method requires four parameters. A StringBuilder and three strings. The problem is → We know, C# does not.
We can solve this problem by defining an appropriate delegate and tell C# to Marshal everything behind the functions process address as a function of the type of our delegate.

public class Program
{
     static IntPtr hCurModule;

     [UnmanagedFunctionPointer(CallingConvention.Winapi,CharSet=CharSet.Unicode)]
     internal delegate bool dPathYetAnotherMakeUniqueName(System.Text.StringBuilder pszUniqueName, 
                 string pszPath, string pszShort, string pszFileSpec);
     static dPathYetAnotherMakeUniqueName PathYetAnotherMakeUniqueName;

     public static void MethodOne()
     {

        hCurModule = LoadLibrary("shell32.dll");

        // Get the process address to the function we want to call
        IntPtr pFunction = GetProcAddress(hCurModule, "PathYetAnotherMakeUniqueName");
        // tell c# -> "yaw dog, gimme the function under the label of mah delegate!"
        PathYetAnotherMakeUniqueName = (dPathYetAnotherMakeUniqueName)Marshal.GetDelegateForFunctionPointer(pFunction, 
                typeof(dPathYetAnotherMakeUniqueName));
        // Declare and define a string builder to substitute for the the string*
        System.Text.StringBuilder pszUniqueName = new System.Text.StringBuilder ();
        // call the method
        PathYetAnotherMakeUniqueName(pszUniqueName, "Foo", "Bar", "Test");
     
        Console.WriteLine(pszUniqueName); // output: "Foo/Bar"

    }
}

Okay, so now that we know how to use native libraries in our C# code, let’s dive a bit deeper and write our own Library!

Writing a C/C++ DLL

For this part I will boldly assume that your IDE of choice is Visual Studio. So let’s fire up a new project and create a new Win32 Console Application.

In the following Application Wizard do not jump straight to the finish line! Instead press the „Next“ button.

In the following window, select the Application type „DLL“. Then click „Finish“.

Upon completing this first trial, your project should look a bit like this:

Let’s dive into the dllmain.cpp and write a function that we will then use in our C# application.

extern "C" _declspec(dllexport) LPWSTR GiveMeAString() {
	return TEXT("Yoho!"); 
};

So I declared and defined a function here, alright, but what’s up with that extern „C“? And what is _declspec(dllexport) !?

Extern „C“ is a prefix that makes a C++ function be linked in the old fashioned C style. C++ allows method overrides and such neat things that come with Object Oriented Programming, but because of that the name of a function is not a unique identifier anymore. Since OOP allows us to overload functions with different parameters, C++ linked functions are identified by a combination of name, parameters and return types. In C however, this is not possible. We prevent name mangling here, each function name can only be unique, and thus we will be able to access the function by its name in our managed code.

_declspec(dllimport) on the other hand, is a storage-class specifier. There are similar prefixes to functions, also known as calling conventions. If you dig around in C++ libraries you will notice that most of them have their functions prefixed with calling conventions. For example, winapi functions all share the calling convention „WINAPI“, which is a define for _stdcall. _stdcall tells the C++ compiler that the stack is to be cleared by the called function, not the calling function, as would be the case with the __cdecl calling convention, which is the standard calling convention. It also defines the order in which the arguments are passed. When DLLImporting our function from C++ to C#, we can and often must specify a calling convention for the method we try to marshall. Naturally, the calling convention in the native code and the one we specify when marshalling should be the same.

__declspec is not a calling convention in the strict sense, however, as it is a specification for our declaration. It’s an attribute that specifies the storage-class of our function. __declspec(dllexport) tells the computer: „hey man, store that in a place suitable for other programs to call it“. The actual calling convention would still be _cdecl, since we didn’t prefix the whole thing with (e.g.) _stdcall.
All this is… semi related and somewhat important. Since we used an attribute in C# that specified the calling convention of our library methods and I guess it’s good to have at least a vague idea about what we are doing.

Let’s get away from that topic for now though, it deserves a better explanation, one that I feel I am not quialified to give you guys. So for more information on storage-class specifiers calling conventions and the inner works of the compiler go ask your local C++ expert or read a good book (I’ve heard that the C++ inventor has written one! *cough*).

It’s time to set our project to release mode, right click it and press Build! Now navigate to the Path your dll was exported to (if you do not know where that is, check the output window.) and copy it into the project folder of our C# program. More precisely, if we run our C# app in Debug mode, put the dll into the Debug folder.

Calling our DLL, Converting Strings, COM-Memory and moar!

Back in our C# Application we can now call our new DLL using one of the methods above. Let’s go with the LoadLibrary approach.

[UnmanagedFunctionPointer(CallingConvention.Cdecl,CharSet=CharSet.Unicode)]
internal delegate IntPtr dGiveMeAString();
static dGiveMeAString GiveMeAString;

public static void MyDLLInvoke()
{
    IntPtr hMod = LoadLibrary("MyDLL.dll");
    IntPtr pFunction = GetProcAddress(hMod, "GiveMeAString");

    GiveMeAString = (dGiveMeAString)Marshal.GetDelegateForFunctionPointer(pFunction, 
                    typeof(dGiveMeAString));

    IntPtr result = GiveMeAString();
    string s = Marshal.PtrToStringAuto(result); // marshal the pointer to our string as a string

    Console.WriteLine(s); // output: Yoho!

}

You might be wondering why we set IntPtr as the return type here, then use the Marshal.PtrToString method group to actually retrieve our string. In our DLL we return a LPWSTR type, which for all intents and purposes should be a StringBuilder or a string. And that would be true if we passed this as a parameter, however, returning stuff back to a caller without the caller really knowing about how the final product will look like will cause heap corruption. The C# Marshaller will assume that the string was allocated in COM object memory space. Then it will try to deallocate that space, but since we never allocated any COM aware memory the Marshaller breaks our Heap and there we go, a crash!

Wait, what was COM again? COM is the Component Object Model. It’s something Microsoft invented, pitchforks down please, it’s cool, and it allows applications to share memory space between different processes and different languages. It’s basically a COMmunication hub of sorts.

Feel free to read more about COMObjects, but for now let’s fix some strings.

Back in our DLL we will add a new function, one very similar, but this time we will allocate COM aware memory. Note that the objbase header is needed to access the CoTaskMemAlloc function.

#include <objbase.h>
extern "C" _declspec(dllexport) LPWSTR COMGiveMeAString() {
    LPWSTR response = TEXT("Yoho!");	// Create our String	
    int bufferSize = wcslen(response)*sizeof(wchar_t); // get the size of the string in bytes

    LPWSTR buffer = (LPWSTR)CoTaskMemAlloc(bufferSize); // allocate memory for our string, com aware
    wcscpy_s(buffer, bufferSize, response); // copy the contents

    return buffer;
}

Compile the DLL and put it into the Debug folder again, then add the following method to our C# application.

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
internal delegate string dCOMGiveMeAString();
static dCOMGiveMeAString COMGiveMeAString;

public static void MyDLLInvokeString()
{
    IntPtr hMod = LoadLibrary("MyDLL.dll");
    IntPtr pFunction = GetProcAddress(hMod, "COMGiveMeAString");

    COMGiveMeAString = (dCOMGiveMeAString)Marshal.GetDelegateForFunctionPointer(pFunction, 
                        typeof(dCOMGiveMeAString));
    string result = COMGiveMeAString();

    Console.WriteLine(result); // output: Yoho!
}

The only real change here was, that instead of an IntPtr, we changed the delegate definitions to return a string, and it works out of the box now! Well, at least on the C# side of things.

Passing parameters to C++ works the very same way. We have already seen how to declare a delegate with parameters when we accessed the PathYetAnotherMakeUniqueName function. Just make sure that your C++ function and your C# delegate are identical.

Thanks for reading and I’ll ramble to you in the next article! Up next in this series we will do some heavy lifting by marshaling big buffers and make a right mess in the process, then do some cleanup to avoid memory leaks. Lot’s of fun!

Alexander

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.