We have a supplier who provides a library for access to their hardware. Unfortunately, if you have multiple devices, you need to import their library multiple times, with different dll names. As a consequence, we have a metric ton of duplicated code, and I'm worried that it will soon become be a maintenance nightmare.
What we have at the moment is somthing like:
namespace MyNamespace {
public static class Device01 {
public const string DLL_NAME = @"Device01.dll";
[DllImport(DLL_NAME, EntryPoint = "_function1")]
public static extern int Function1(byte[] param);
...
[DllImport(DLL_NAME, EntryPoint = "_function99")]
public static extern int Function99(int param);
}
....
public static class Device16 {
public const string DLL_NAME = @"Device16.dll";
[DllImport(DLL_NAME, EntryPoint = "_function1")]
public static extern int Function1(byte[] param);
...
[DllImport(DLL_NAME, EntryPoint = "_function99")]
public static extern int Function99(int param);
}
}
If I were using C or C++, I would just define the functions one file and #include them multiple times in the static classes, not pretty but better than the alternative, but in C# I don't have that option.
If anyone has any clever ideas about how to effectively define a factory which would allow us to generate as many static device classes as we need, I would be very interested.
Thanks,
Edit: The function prototypes are quite videly varied, so any method which relies on them being the same wouldn't be suitable. Thanks for the suggestions so far, I wasn't expacting so many ideas quite so quickly.
Just some considerations:
EDIT: this approach requires changing compiled methods, which is hard and requires injection, assembly modification or other methods that are commonly used in AOP-land. Consider approach two below, which is easier.
GetIlAsByteArray
to create a dynamic method of your DllImport
methodEDIT: This alternative approach seems a bit involved at first, but someone already did the work for you. Look up this excellent CodeProject article and simply download and use its code to dynamically create DllImport style methods. Basically, it comes down to:
LoadLibrary
or LoadLibraryEx
using the dllimport API functionsMethodBuilder
.EDIT: looking further, there's an easier approach: simply use DefinePInvokeMethod
which does all you need. The MSDN link already gives a good example, but a full wrapper that can create any Native DLL based on DLL and function name is provided at this CodeProject article.
DefinePInvokeMethod
Here's how this approach looks in code, you can reuse the returned delegate as much as you like, the costly building of the dynamic method should be done only once per method.
EDIT: updated the code sample to work with any delegate and to automatically reflect the correct return type and parameter types from the delegate signature. This way, we decoupled the implementation completely from the signature, which is, given your current situation, the best we can do. Advantages: you have type safety and single-point-of-change, which means: very easily manageable.
// expand this list to contain all your variants
// this is basically all you need to adjust (!!!)
public delegate int Function01(byte[] b);
public delegate int Function02();
public delegate void Function03();
public delegate double Function04(int p, byte b, short s);
// TODO: add some typical error handling
public T CreateDynamicDllInvoke<T>(string functionName, string library)
{
// create in-memory assembly, module and type
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("DynamicDllInvoke"),
AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = assemblyBuilder.DefineDynamicModule("DynamicDllModule");
// note: without TypeBuilder, you can create global functions
// on the module level, but you cannot create delegates to them
TypeBuilder typeBuilder = modBuilder.DefineType(
"DynamicDllInvokeType",
TypeAttributes.Public | TypeAttributes.UnicodeClass);
// get params from delegate dynamically (!), trick from Eric Lippert
MethodInfo delegateMI = typeof(T).GetMethod("Invoke");
Type[] delegateParams = (from param in delegateMI.GetParameters()
select param.ParameterType).ToArray();
// automatically create the correct signagure for PInvoke
MethodBuilder methodBuilder = typeBuilder.DefinePInvokeMethod(
functionName,
library,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.PinvokeImpl,
CallingConventions.Standard,
delegateMI.ReturnType, /* the return type */
delegateParams, /* array of parameters from delegate T */
CallingConvention.Winapi,
CharSet.Ansi);
// needed according to MSDN
methodBuilder.SetImplementationFlags(
methodBuilder.GetMethodImplementationFlags() |
MethodImplAttributes.PreserveSig);
Type dynamicType = typeBuilder.CreateType();
MethodInfo methodInfo = dynamicType.GetMethod(functionName);
// create the delegate of type T, double casting is necessary
return (T) (object) Delegate.CreateDelegate(
typeof(T),
methodInfo, true);
}
// call it as follows, simply use the appropriate delegate and the
// the rest "just works":
Function02 getTickCount = CreateDynamicDllInvoke<Function02>
("GetTickCount", "kernel32.dll");
Debug.WriteLine(getTickCount());
Other approaches are possible, I guess (like the templating approach mentioned by someone else in this thread).
Update: added a link to excellent codeproject article.
Update: third and way easier approach added.
Update: added code sample
Update: updated code sample to work seamlessly with any function prototype
Update: fixed dreadful error: typeof(Function02)
should be typeof(T)
of course
How about using T4 (Text Template Transformation Toolkit). Create a .tt file with the following content:
<#@ template language="C#" #>
using System.Runtime.InteropServices;
namespace MyNamespace {
<# foreach(string deviceName in DeviceNames) { #>
public static class <#= deviceName #>
{
public const string DLL_NAME = @"<#= deviceName #>.dll";
<# foreach(string functionName in FunctionNames) { #>
[DllImport(DLL_NAME, EntryPoint = "<#= functionName #>")]
public static extern int <#= functionName.Substring(1) #>(byte[] param);
<# } #>
}
<# } #>
}
<#+
string[] DeviceNames = new string[] { "Device01", "Device02", "Device03" };
string[] FunctionNames = new string[] { "_function1", "_function2", "_function3" };
#>
Visual Studio will then convert this into:
using System.Runtime.InteropServices;
namespace MyNamespace {
public static class Device01
{
public const string DLL_NAME = @"Device01.dll";
[DllImport(DLL_NAME, EntryPoint = "_function1")]
public static extern int function1(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function2")]
public static extern int function2(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function3")]
public static extern int function3(byte[] param);
}
public static class Device02
{
public const string DLL_NAME = @"Device02.dll";
[DllImport(DLL_NAME, EntryPoint = "_function1")]
public static extern int function1(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function2")]
public static extern int function2(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function3")]
public static extern int function3(byte[] param);
}
public static class Device03
{
public const string DLL_NAME = @"Device03.dll";
[DllImport(DLL_NAME, EntryPoint = "_function1")]
public static extern int function1(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function2")]
public static extern int function2(byte[] param);
[DllImport(DLL_NAME, EntryPoint = "_function3")]
public static extern int function3(byte[] param);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With