Click here to Skip to main content
15,886,067 members
Articles / Programming Languages / C#
Article

Late-Binding DLLs in C#

Rate me:
Please Sign up or sign in to vote.
2.92/5 (11 votes)
29 Feb 20046 min read 119.1K   919   41   9
Creating plug-in DLL libraries for use in C# applications.

Introduction

Creating plug-ins for C# applications can be difficult, because .NET does not give you an easy solution to calling late-bound libraries that aren't assemblies, or do not implement interfaces. One such example for the use of this code is for DLLs that you do not have the source code to. Using an external "proxy" library written in C++, you can easily solve this.

Background

This article builds on some of the concepts covered in Richard Birkby's "Late binding on native DLLs with C#". Instead of using an ASM-driven DLL however, this approach uses an intermediate library written in C++ to handle the actual calling of the function.

Late Binding

This article is based on information gathered from my own experience, and the work of others is listed after the body of the article.

It has taken a considerable amount of time to create the code and understand the problems and solutions herein. I hope to explain in depth, the solution I have come up with to implement a plug-in functionality into a Microsoft .NET application.

The main program, or host application, is written in C#. This application is going to be the program that accepts the plug-in libraries. The point of adding plug-ins is to allow a program to extend its functionality to provide the end user a way to accomplish a task that is otherwise impossible, or harder to do, without an easy function call.

The application I am in the process of writing is a simplistic, ASM-like, interpreted programming language. The main feature of the language is the ability to create functions as a library and use them at runtime. In order to implement this, I need to be able to late-bind the libraries.

Early-binding, or statically binding libraries is when you link the library to the application before compiling. Because the name of the library to load is not known until runtime, we must late-bind it. To do this, there has to be a way to load a library into memory, map its functions to an address, and execute them.

To make the process easier, each of the user-made functions must consist of the same signature (sans function name). This means, the parameters and return type must be consistent. All the functions for AIC (the project I am working on) return void, and accept one parameter. The parameter passed is a pointer to a memmap structure, defined as follows:

C#
[StructLayout(LayoutKind.Sequential)]
   public struct memmap {
       public int GRA, GRB, GRG, GRD;
       public int LMR, RMR;
       public int LCR, RCR;
       public int LIR, RIR;
       public string LSR, RSR; 
   }

In order to marshal the structure correctly, we must add the StructLayout directive above the structure declaration. In order to emulate the passing of a pointer variable, we have to pass the actual parameter as ref, or by reference. This, in effect, passes the address in memory of the variable, instead of an actual copy of it. The full API declaration of the library is as follows.

[DllImport(@"C:\InvokeDLL\invdll.dll", CharSet=CharSet.Unicode)]
public extern static void CallRemote(string Library, 
                         string Func, ref memmap map);

The Approach

This approach uses an external DLL as a "proxy", to which you pass the name of the library and function name to call, as well as the parameter(s) to pass to the function. Because of the limitations of C# (mainly, no type definitions and pointer restrictions), we are going to use this proxy address to load the library, map the necessary functions, and call the function.

The proxy DLL is written in C++. Here is the code in full.

MC++
struct map {
   int GRA, GRB, GRG, GRD;
   int LMR, RMR;
   int LCR, RCR;
   int LIR, RIR;
   char *LSR, *RSR;
};
   
typedef VOID (*MYPROC)(map*);
   
extern "C" __declspec(dllexport) __stdcall 
  void CallRemote(char *Library, char *Func, map *a) {
    HINSTANCE hinstLib;
    MYPROC ProcAdd;
    hinstLib = LoadLibrary(Library);
   
    if (hinstLib != NULL) {
        ProcAdd = (MYPROC) GetProcAddress(hinstLib, Func);
        if (NULL != ProcAdd) {
            (ProcAdd) (a);
        }
        FreeLibrary(hinstLib);
    }
}

The first structure is simply the declaration of the map structure in C++. The second type definition is that of the DLL function we're calling. It declares VOID as a pointer to a procedure, needing one parameter of type map (or more specifically, a pointer to the map type).

The function CallRemote uses the STDCALL calling convention as to make it easier to interact with .NET (it requires no altering of the stack). The _declspec(dllexport) convention is used to stop name mangling in the DLL when it is published. The function requires two strings, the library and function names, and the map reference.

CallRemote uses the LoadLibrary API to load the DLL file into memory, and then calls GetProcAddress to load the required function, returning the address in memory. The line that executes the function is:

(ProcAdd) (a);

which uses the VOID type definition as a function reference. After calling the function, it frees the allocated memory used for loading the library. Also note here that these functions will not return any value at all. Instead, the return value will be in the referenced map structure (pointer).

So, what's the problem?

When I added a few debugging procedures into the proxy DLL, I found that the function name and the library name were being truncated after the first character. Upon closer inspection, I found that because C# utilizes Unicode strings by default, the ASCII character set effectively terminates the string sequence. Unicode uses two bytes to store each character possible, while ASCII uses one byte. So, all ASCII characters' low byte will have a null character ('\0'). Strings are just null-terminated character arrays, and so this premature end-of-string character is understood as such.

The fix is to convert the library and function names to ASCII characters. This is done in .NET using the System.Text namespace. Take the following code:

C#
string lib = @"C:\MyPlugin.dll";
string fnc = "MyFunctionName";
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] blib = enc.GetBytes( lib );
byte[] bfnc = enc.GetBytes( fnc );

This converts the library and function strings into arrays of bytes. Obviously, this is incompatible with the previously defined function declaration, so we must change it. But before we do this, it is easier for the proxy DLL to understand native strings than arrays, so let's give it what it wants!

This function, taken from a message board online, emulates the Visual Basic VarPtr() function - it takes an object and returns the address in memory in which it resides.

C#
static int VarPtr(object o) {
   System.Runtime.InteropServices.GCHandle GC = 
   System.Runtime.InteropServices.GCHandle.Alloc(o, 
   System.Runtime.InteropServices.GCHandleType.Pinned);
   int ret = GC.AddrOfPinnedObject().ToInt32();

   return ret;
}

Because C++ strings are represented by their beginning position in memory, this will work great with our byte arrays. So now, let us change the proxy function declaration to:

C#
[DllImport(@"C:\InvokeDLL\invdll.dll", CharSet=CharSet.Unicode)]
public extern static void CallRemote(int Library, int Func, ref memmap map);

And finally, we have ourselves the beginnings of a working plug-in architecture. Let's sum things up with a simple example of the DLL late-binding in process.

Late Binding Process

C#
memmap r = new memmap();
r.LMR = 15;
r.RMR = 16;
   
string lib = @"C:\MyPlugin.dll";
string fnc = "Base";
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] blib = enc.GetBytes( lib );
byte[] bfnc = enc.GetBytes( fnc );

CallRemote(VarPtr(blib),VarPtr(bfnc),ref r);
Console.WriteLine("The result was {0}!", r.LSR);

And finally, the C++ DLL example, in full:

MC++
#include <windows.h>
#include <vcl.h>
   
int WINAPI DllEntryPoint(HINSTANCE hinst, 
         unsigned long reason, void *lpReserved) {
    return 1;
}
   
struct map {
    int GRA, GRB, GRG, GRD;
    int LMR, RMR;
    int LCR, RCR;
    int LIR, RIR;
    char *LSR, *RSR;
};
   
extern "C" __declspec(dllexport) __stdcall void Base(map *a) {
    // LMR: number, RMR: base, RESULT: LSR
    AnsiString b = "";
    char chr[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    if (a->RMR > 26) return;

    a->LSR = "";
    while (a->LMR != 0) {
        b = (AnsiString)chr[(a->LMR%a->RMR)] + b;
        a->LMR/=a->RMR;
    }

    a->LSR = b.c_str();
}

The example DLL compiles only in Borland C++, as it used the Visual Component Library; with minor modification it should work in MSVC++.

The example function is called Base and converts numbers from decimal into the decided base. It requires the decimal number in the LMR field of the map structure, and the base to convert to in the RMR field. The result is placed into LSR.

I hope this example begins to show you the possibilities of using dynamic link libraries within .NET to accomplish tasks that C# alone can't complete. If you need any help understanding or getting the code to run, please feel free to e-mail me at Programmer_Zero@hotmail.com.

References:

  • MSDN

    This article describes the process of executing a loaded library.

  • The Code Project

    This was the original article posted by Richard Birkby, which also relates to this article.

Points of Interest

Please note that there is one bug in the code as it stands. In the proxy DLL, the function starts by looping a number of times, doing nothing. This is because, without it, the function does not seem to work. If you can find a fix for this, please notify me.

History

  • 29 Feb 2004 - Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
James Brannan is a 16 year old programmer attending high school in Lake Saint Louis, Missouri. He enjoys writing code in Perl, C++, and .NET in his free time, but also enjoys the finer things in life -- sleeping and eating! His homepage is at http://www.binary-craft.com and http://www.domainjames.com

Comments and Discussions

 
GeneralMarshalling: Using native DLLs in .NET Pin
meukjeboer25-Sep-08 2:20
meukjeboer25-Sep-08 2:20 
GeneralWin32 APIs have native Unicode versions Pin
Bababooey19-Mar-05 10:55
Bababooey19-Mar-05 10:55 
GeneralRe: Win32 APIs have native Unicode versions Pin
.Shoaib11-Oct-05 20:56
.Shoaib11-Oct-05 20:56 
General.NET DOES have late-binding support Pin
Heath Stewart29-Feb-04 18:14
protectorHeath Stewart29-Feb-04 18:14 
GeneralRe: .NET DOES have late-binding support Pin
James Brannan29-Feb-04 18:19
James Brannan29-Feb-04 18:19 
GeneralRe: .NET DOES have late-binding support Pin
Heath Stewart29-Feb-04 18:26
protectorHeath Stewart29-Feb-04 18:26 
GeneralRe: .NET DOES have late-binding support Pin
James Brannan29-Feb-04 18:28
James Brannan29-Feb-04 18:28 
Ah well, I can see where you're coming from. Even so, I hope you've rated my article based on the usefulness (even as an alternative) and not purely on the validity of my opinions Smile | :)
GeneralRe: .NET DOES have late-binding support Pin
Heath Stewart29-Feb-04 18:31
protectorHeath Stewart29-Feb-04 18:31 
GeneralRe: .NET DOES have late-binding support Pin
.Shoaib11-Oct-05 20:44
.Shoaib11-Oct-05 20:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.