Click here to Skip to main content
Email Password   helpLost your password?

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:

[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.

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:

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.

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:

[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

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:

#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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMarshalling: Using native DLLs in .NET
meukjeboer
3:20 25 Sep '08  
Please read the following article:
http://blog.rednael.com/2008/08/29/MarshallingUsingNativeDLLsInNET.aspx[^]

It's an in-depth article about how to use a native DLL (or C++ DLL) in your managed .Net code. The article shows which types are interoperable, how to import a DLL, how to pass strings, how to pass structures and how to de-reference pointers.

And C# source code examples are included.
GeneralWin32 APIs have native Unicode versions
Bababooey
11:55 19 Mar '05  
Maybe I'm wasting my time writing this a year after the article first appeared, but I got here via a link from .NET 247 and am rating this article "Poor". Heath Stewart pointed out a year ago that the whole idea of breaking out to an unmanaged DLL using this method was unnecessary to achieve late binding with .NET. Not only did the author solve a problem that didn't exist, he also solved another non-existant problem along the way. Native WIn32 API calls have both ANSI and Unicode entry points. Leave the Unicode string alone and call "LoadLibraryW" instead of "LoadLibrary". The same goes for GetProcAddressW vs. GetProcAddress.

Of course, the number of VB programmers who know this already is vanishingly small. Something that never ceases to amaze me is when one of them figures out how to actually use a pointer for something and decides to share this information with others. They pat themselves on the back and think they've just done a write-up on an advanced topic when, in fact, all they've done is overly dramatize the complexity of some elementary function they really don't understand in the first place.

That's exactly what we have here. This article is worse than useless since the unsuspecting reader is going to be left with the impression that there are gotchas when using Unicode strings with WIn32 API calls, and nothing could be further from the truth.

I'll cut the kid some slack because of his age, but it reeks of arrogance - at any age - to think you know enough to write an article for publication on some topic about which you really have no clue.
GeneralRe: Win32 APIs have native Unicode versions
kdash
21:56 11 Oct '05  
http://www.codeproject.com/csharp/cscpplatebind.asp?msg=1251050#xx1251050xx[^]
General.NET DOES have late-binding support
Heath Stewart
19:14 29 Feb '04  
.NET does provide late-binding functionality through reflection, interfaces, and Types. There are MANY articles that already cover this functionality and in a much easier way that is 100% .NET-based in whatever language you want (since all languages targeting the CLR compile down to IL and can be used by any other language).

For a simple example. I could define an interface like IPlugin. My plugin classes (Types) would implement this interface. I could put these in a .config file (there's MANY already defined in the machine.config for support of the .NET Framework), in a database, or wherever. Then I simply do something like this:
IPlugin plugin =
Type.GetType("MyNamespace.MyPlugin, MyAssembly");
If the assembly was found and the Type was loaded, I've now got a late-bound interface from which to call methods and query/set properties. I could go to further lengths and use reflection to discover methods and invoke them in a late-binding manner. In fact, serialization, Type loading (like the example above), and many other technologies in the .NET Framework rely on reflection.

 

Microsoft MVP, Visual C#
My Articles
GeneralRe: .NET DOES have late-binding support
James Brannan
19:19 29 Feb '04  
Thanks for the reply Heath Smile I have found that through reflection, it is much harder to implement a plugin system using native DLL's; It is however, much easier to use .NET assemblies with reflection. This is provided merely as a (pretty) simple way to access DLL functions without using interfaces or assembler.
GeneralRe: .NET DOES have late-binding support
Heath Stewart
19:26 29 Feb '04  
I agree that reflection isn't always easy, but using interfaces are the easiest way to control the contract with a plugin. An interface is - after all - a contract. Barring good exception handling, loading a plugin Type and casting it to an interface is a one-liner. Sorry, but nothing (regarding late-binding) could be simpler.

I don't have anything against providing alternative means to accomplish a task, but it was mostly your opening statement that .NET doesn't providing late-binding support that caught my attention (though I read the rest of the article first). This is utterly wrong.

Heck, late-binding plugin-like assemblies was one of the firsts things I started doing back in the .NET 1.0 beta days and is a major feature of the enterprise application I've architected and help develop for work. Without it, the app would be one monolithic application that doesn't tolerate even small changes. And - like most COM components - our "plugins" (I use this term loosely since it's rather ambiquous) expose many different interfaces to facilitate whatever behavior we need. With interfaces, this is a snap. That's what interfaces are for.

 

Microsoft MVP, Visual C#
My Articles
GeneralRe: .NET DOES have late-binding support
James Brannan
19:28 29 Feb '04  
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
Heath Stewart
19:31 29 Feb '04  
That's just it: it's interesting but not useful. You make hard work of something so trivial. Now, if you were to re-organize your article to cover writing native plugins for a managed application using late-binding calls, that would be something different and useful. You've practically already done it.

 

Microsoft MVP, Visual C#
My Articles
GeneralRe: .NET DOES have late-binding support
kdash
21:44 11 Oct '05  
Well actually its not that its not useful. Perhaps not good enough for plug-in style architectures, but a good use of it could be to hide critical and complex code (supposedly safe from ordinary prying eyes). For example I was looking for specifically this kind of functionality that

- Does not link in a lot of un-necessary or say run-once code. Keep the working set mimimum.
- That is not very apparent. Ofcourse a seasoned hacker can do fancy things but i'm targetting say ordinary users of a licensed API library here.

I could use this to stub out license verification ar activation tasks to compiled c++ code (which IMHO is suficiently obscure). Doing the same in C# is not safe at all (even after obfuscation). There are tools that provide *protected* managed code, but they bear a lot of overhead.

Just thoughts!

Just realized that is thread is more than a year old, but my point still remains Smile

-- modified at 2:45 Wednesday 12th October, 2005


Last Updated 1 Mar 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010