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

Using legacy plug-ins with .NET - Part 2

Rate me:
Please Sign up or sign in to vote.
3.46/5 (3 votes)
7 Sep 2004CPOL5 min read 51.6K   686   15   12
So far, using Win32 plug-ins in .NET involved complex solutions like using CodeDOM or another legacy DLL. Now, I came up with a pure .NET solution.

Introduction

Since I started working in .NET, I was able to load Win32 libraries dynamically in an application and call its exported functions. When several libraries expose the same functions, it is possible for an application to load and work with them dynamically. They are not required to exist when the application is run. This is the basic functionality of legacy plug-ins.

When .NET came out, the way of working with these libraries changed and new ways of working the plug-in concept also showed up. Although these new ways provide many advantages, there are lots of pre-existing plug-ins that could improve new .NET applications.

Background

For some time now, I've been working on a way to load these same Win32 plug-ins in .NET (C#, specifically). This has lead to a number of trials and and many failures, but I finally came with a fully managed Win32 plug-in loader for .NET applications.

In the past, in the days before I learned C#, it was possible to load dynamic libraries using Windows API functions LoadLibrary, GetProcAddress, and FreeLibrary. However, to my surprise, when I tried to load legacy libraries within .NET with some code derived from what I used to do using Delphi, I found out it would not be that simple. After some time, I got tired of trying (even using unsafe) and posted a question on a MS newsgroup. There, I came to know that it was really not possible. The problem is: I still wanted a solution.

DllImportAttribute

What we first learn about using Win32 libraries in .NET is to use the DllImportAttribute. It allows us to declare a static method having its implementation in a Win32 library. The example below shows how to load the MessageBeep function from Windows API:

C#
[DllImport ("user32.dll")]
public extern static void MessageBeep(int value);

This is the simplest thing we could do, but what happens when we have to load several libraries with the very same methods? We could create several different new classes, one for each plug-in library we have to handle. This is not a very good idea because we'll have to create a new class for each new plug-in to be used, and this is not a simple task.

On the other hand, we can create a class for each library. The trick involves System.Reflection.Emit namespace. It could be a little difficult, at first, for those who are unfamiliar with Reflection, to understand the steps to build new classes dynamically, but it will result in a very large source code and will also consume lots of resources from the machine every time a library requires a new class. I'm not wasting my time explaining how this works, sorry.

Legacy for the legacy

Then it came to me that I could still know only one library to call the plug-ins. We are not allowed to convert and call the function pointer returned by GetProcAddress as, for example, a .NET delegate. Although, we can still use the pointer returned by LoadLibrary. So, we could create another Win32 library to be imported with the following code:

C#
[DllImport ("user32_stub.dll")]
public extern static void MessageBeep(IntPtr lib, int value);

This is where the trick lies. We have a well-known DLL but it isn't the plug-in call yet. Notice the first parameter in this method; it could be anything, but we'll use the return value of the LoadLibrary function as its value. Once we can't call the dynamic function directly, this new library will perform the GetProcAddress call, convert the returned pointer, and call the desired function. It works as a front-end just to call any plug-in. The front-end looks like a plug-in library, but every exported function has an additional IntPtr parameter. It's very important to remember that every method of the plug-in libraries must have a correspondent in this front-end in order to be used.

I'm not extending myself to explain how this process works in practice, since I wrote another article about it previously. This article can be found here. The important thing to notice is that its overhead is smaller than creating new classes every time the application runs.

A solution with managed code

This need for another legacy library to use Win32 plug-ins always annoyed me a little. Although I tried to move ahead, I was always looking for some new idea to better solve this question.

In the past few days, I found another article on CodeProject (Execute Native Code from .NET, by Maxim Alekseikin) which gave a new idea to try. In the article, the author explained how to embed native code in a .NET application (as a constant byte array) and execute it using the Marshal class. So far I had a limited view of the capabilities of that class and, to me, every new idea was worth trying.

Using this new knowledge, I derived the author's sample to use a few extra types and steps to finally convert the address returned by GetProcAddress into a Delegate. Also, to reduce the code and type casting required for the example to work, I began to use Generics from .NET Framework 2.0.

The new sample involves Generics, MarshalAsAttribute, and a new pair structure-delegate (one for each function exported by plug-in libraries), and effectively allowed me to cast the function pointer (passing through a single-IntPtr structure and a free-form memory reference) to the desired Delegate. This combination allowed me to code it simple, easy to understand, and use it quickly.

Points of interest

Still, I'm not sure if it's possible to cast the pointer conversion (from GetProcAddress) to a delegate using Delphi 8 the same way we used to, once they tried to keep full compatibility with previous versions. My first supposition is no, but if anyone has tested and made it work, share it too.

Also, I haven't had the time to test the code under .NET 1.0 or 1.1, so it may not work as expected, but considering the framework hasn't changed in this part, maybe it will work. I'll leave that for someone else to do the test.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Brazil Brazil
architecture student (5/10)​ · designer · developer · geek

Comments and Discussions

 
GeneralAPi Problem [modified] Pin
t4ure4n6-Aug-06 11:31
t4ure4n6-Aug-06 11:31 
GeneralRe: APi Problem Pin
Leonardo Pessoa7-Aug-06 2:42
Leonardo Pessoa7-Aug-06 2:42 
GeneralC# Unknown return types Pin
Dhanjel_16-Jun-06 3:25
Dhanjel_16-Jun-06 3:25 
AnswerRe: C# Unknown return types Pin
Leonardo Pessoa19-Jun-06 2:28
Leonardo Pessoa19-Jun-06 2:28 
NewsEasy now in 2.0 Pin
snortblt4-Nov-05 11:02
snortblt4-Nov-05 11:02 
GeneralRe: Easy now in 2.0 Pin
Leonardo Pessoa5-Nov-05 1:46
Leonardo Pessoa5-Nov-05 1:46 
GeneralRe: Easy now in 2.0 Pin
Leonardo Pessoa20-Apr-06 1:48
Leonardo Pessoa20-Apr-06 1:48 
GeneralRe: Easy now in 2.0 Pin
Lev Shisterov16-Jul-06 23:47
Lev Shisterov16-Jul-06 23:47 
GeneralRe: Easy now in 2.0 Pin
Leonardo Pessoa17-Jul-06 2:13
Leonardo Pessoa17-Jul-06 2:13 
Generalnot working in 1.1 Pin
mgambrell2-Oct-04 21:41
mgambrell2-Oct-04 21:41 
GeneralRe: not working in 1.1 Pin
Leonardo Pessoa3-Oct-04 7:33
Leonardo Pessoa3-Oct-04 7:33 
GeneralRe: not working in 1.1 Pin
mgambrell3-Oct-04 8:25
mgambrell3-Oct-04 8:25 

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.