Click here to Skip to main content
15,888,579 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.7K   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 
I am doing a project to develop ”Software Defined Radio”. I have purchased a hardware radio receiver called WiNRADiO G303e. It connects to the computer via USB interface. It feeds the computer with down converted + demodulated RF signal (12kHz) to the computer. Which I have to process in some way.

The winradio company has provided an API to communicate with the radio receiver According to them
“The G303 receivers API is implemented as a single 32-bit library WRG303API.DLL. It provides functions for all communication with all types of G303 receivers. It can be used by any 32-bit application under Windows 98, ME as well as 2000 and XP.”
I am making my project in C#. I have written an API wrapper class. I used P/Invoke and defined all the method signatures in that class. I have used help from various different tools and web forums to write this class.
Now I particular problem is in the methods which use pointer to memory location, arrays or passing the reference of these arrays
The example usage of these methods provided with the SDK Documentation are given are at the end and also found at http://www.winradio.com/home/g303_sdk.htm

These methods are

METHOD 1

The method uses a callback mechanism which can be provided by Delegates in c # but I can’t figure out the syntax to it.

C/C++ declaration given in the API
BOOL __stdcall CodecStart (int hRadio, void __stdcall (*CallbackFunc) (void *), void *CallbackTarget);
I have been suggested to use the following code for this function but I have absolutely no clue how to do coding for this method. There is an example usage of this function in the Example Useges with this name Codec streaming
Coz I don’t know which one is the delegate 2nd param or the third param…?????
delegate void CallbackFunc(IntPtr p);

static extern bool CodecStart(int hRadio, CallbackFunc func, IntPtr CallbackTarget);


METHOD 2

There is a Delphi example for that as well
C/C++ declaration given in the API

BOOL __stdcall G3BlockScan (int hRadio, DWORD *Freqs, int Count,int StopSquelchRaw,DWORD feedbackTime, HWND WinHandle,DWORD Msg);
The description of this function is as follows
///
Scanned RAW signal strength values are written back to the Freqs buffer. Each frequency in the Freqs buffer is replaced by appropriate RAW value. Parts of this buffer are sent to the appplication after Feedback time interval expires. The block scanning automatically ends when scanned RAW signal strength value rises StopSquelchRaw parameter. The scanned RAW values are sent using window message Msg to the window procedure of the window handle WinHandle with buffer pointer as WParam and buffer length as LParam.
///

/// <param name="hRadio" />Handle to a radio device that was returned by OpenRadioDevice or G3Open.
/// <param name="Freqs" />The array of frequencies to be scanned.
/// <param name="Count" />The count of frequencies to be scanned.
/// <param name="StopSquelchRaw" />The value of the RAW signal strength at which the scanning should stop.
/// <param name="FeedbackTime" />The time interval in ms after which G3 API sends you scanned data.
/// <param name="WinHandle" />The window handle to which the scanned data will arrive.
/// <param name="Msg" />The constant for window mesage that will bring the scanned data.
/// <returns>If the scan started, the return value is TRUE. Otherwise FALSE is returned.
[DllImport("wrg303api.dll")]
public static extern bool G3BlockScan(int hRadio, ref uint [] Freqs, int Count,int StopSquelchRaw,uint FeedbackTime,IntPtr WinHandle,uint Msg);

Here Freqs is an array whose contents are mofied by the The API function call.

I have wrapped this function in C# as follows..

[DllImport("wrg303api.dll")] <br />
public static extern bool G3BlockScan(int hRadio, ref uint [] Freqs, int Count,int StopSquelchRaw,uint FeedbackTime,IntPtr WinHandle,uint Msg);<br />
<br />
..... some code.....<br />
uint[] Freqs = new uint[1001]; // buffer for frequencies to scan<br />
..... some code....<br />
if (!clsApiWrapper.G3BlockScan(radioHandle,ref Freqs, 1001, 256, 100000, pointer,Msg))<br />
<br />
MessageBox.Show("The block scanning failed to start", "WiNRADiO - Error Message", MessageBoxButtons.OK);<br />

Now when I access the Freqs array after the method call I encounter a strange behaviour. The size of Freqs array is reduced from 1001 to 1. I can't figure out what is going wrong.

I have tried to remove the ref keyword from both places, but now when I run the app it doesn't change the contents of the array. Size is still 1001 but no change in the contants so I believe I need to pass the reference of the array to the function. And if I change the code like this to pass the reference of the array
Can any1 plz suggest what to do.

I have also tried to use different data structure like arrayList etc… I have also used [In, Out] in the Dllimport statement in place of ref… but no use?
This code could be found in clsApiWrapper.cs and in the public void blockScan() function in the clsRadioTest.cs
-----------------------------------------------------
<br />
 #include "stdafx.h"<br />
#include <stdio.h><br />
#include <windows.h><br />
<br />
// G3 API function type declarations<br />
typedef int (__stdcall *FNCOpenRadioDevice)(int iDeviceNum);<br />
typedef BOOL (__stdcall *FNCCloseRadioDevice)(int hRadio);<br />
typedef BOOL (__stdcall *FNCSetAtten)(int hRadio, BOOL fAtten);<br />
typedef BOOL (__stdcall *FNCSetPower)(int hRadio, BOOL fPower);<br />
typedef BOOL (__stdcall *FNCSetAGC)(int hRadio, int iAGC);<br />
typedef BOOL (__stdcall *FNCCodecStart)(int hRadio,void (__stdcall *CallbackFunc)(void *),<br />
void *CallbackTarget);<br />
typedef BOOL (__stdcall *FNCCodecStop)(int hRadio);<br />
typedef unsigned int (__stdcall *FNCCodecRead)(int hRadio,void *Buf,unsigned int Size);<br />
<br />
typedef struct {<br />
int hRadio;<br />
FILE *IFFile;<br />
FNCCodecRead CodecRead;<br />
} CallbackContext;<br />
<br />
void __stdcall CodecCallback(void *Target)<br />
{<br />
unsigned int i;<br />
char Buf[4096];<br />
CallbackContext *Ctx=(CallbackContext *)Target;<br />
<br />
while ((i=Ctx->CodecRead(Ctx->hRadio,Buf,sizeof(Buf)))!=0) <br />
	fwrite(Buf,i,1,Ctx->IFFile);<br />
}<br />
//int _tmain(int argc, _TCHAR* argv[])<br />
int main(int argc, char* argv[])<br />
{<br />
// load the G3 API library<br />
HMODULE dll=LoadLibraryA("wrg303api.dll");<br />
if (!dll) {puts("WRG303API.DLL not found !"); return 0;}<br />
<br />
// link G3 API functions<br />
FNCOpenRadioDevice OpenRadioDevice=(FNCOpenRadioDevice)GetProcAddress(dll,"OpenRadioDevice");<br />
FNCCloseRadioDevice CloseRadioDevice=(FNCCloseRadioDevice)GetProcAddress(dll,"CloseRadioDevice");<br />
FNCSetAtten SetAtten=(FNCSetAtten)GetProcAddress(dll,"SetAtten");<br />
FNCSetPower SetPower=(FNCSetPower)GetProcAddress(dll,"SetPower");<br />
FNCSetAGC SetAGC=(FNCSetAGC)GetProcAddress(dll,"SetAGC");<br />
FNCCodecStart CodecStart=(FNCCodecStart)GetProcAddress(dll,"CodecStart");<br />
FNCCodecStop CodecStop=(FNCCodecStop)GetProcAddress(dll,"CodecStop");<br />
FNCCodecRead CodecRead=(FNCCodecRead)GetProcAddress(dll,"CodecRead");<br />
<br />
// open the first available radio<br />
int hRadio=OpenRadioDevice(0);<br />
if (!hRadio) {puts("No WR-G303 device could be opened !");FreeLibrary(dll); return 0;}<br />
<br />
// set Power, AGC and Attenuator<br />
if (SetPower(hRadio,TRUE)) puts("The device is turned ON"); else puts("The device failed to turn ON");<br />
if (SetAtten(hRadio,FALSE)) puts("The attenuator is OFF"); else puts("The attenuator failed to turn OFF");<br />
if (SetAGC(hRadio,3)) puts("The AGC is FAST"); else puts("The AGC failed to switch");<br />
<br />
// open the IF samples destination file<br />
FILE *f=fopen("IF2.pcm","wb");<br />
<br />
// start the IF stream from the codec<br />
CallbackContext Ctx;<br />
<br />
Ctx.hRadio=hRadio;<br />
Ctx.IFFile=f;<br />
Ctx.CodecRead=CodecRead;<br />
if (!CodecStart(hRadio,CodecCallback,&Ctx)) puts("Codec streaming couldn't be started!");<br />
else {<br />
puts("Codec streaming properly started!");<br />
while (!ftell(f) ) <br />
{<br />
}<br />
CodecStop(hRadio);<br />
fclose(f);<br />
}<br />
<br />
// close the device handle<br />
if (CloseRadioDevice(hRadio)) puts("The device is closed properly"); else puts("The device failed to close");<br />
<br />
// free the G3 API library<br />
FreeLibrary(dll);<br />
<br />
return 0;<br />
}<br />



o O º(`'·.,(`'·., ☆,.·''),.·'')º O o°
»·'"`»* *☆ t4ure4n ☆* *«·'"`«
°o O º(,.·''(,.·'' ☆`'·.,)`'·.,)º O o°

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.