Параметризация DllImport для использования в приложении c#
у нас есть поставщик, который предоставляет библиотеку для доступа к их оборудованию. К сожалению, если у вас есть несколько устройств, необходимо импортировать библиотеку несколько раз, с разными именами файлов. Как следствие, у нас есть метрическая тонна дублированного кода, и я беспокоюсь, что это скоро станет кошмаром обслуживания.
то, что мы имеем на данный момент, что-то вроде:
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);
}
}
если бы я использовал C или c++, я бы просто определил функции одного файла и #включил их несколько раз в статические классы, не очень, но лучше, чем альтернатива, но в C# у меня нет этой опции.
если у кого-нибудь есть умные идеи о том, как эффективно определить фабрику, которая позволит нам генерировать столько статических классов устройств, сколько нам нужно, мне было бы очень интересно.
спасибо,
Edit: прототипы функций они довольно разнообразны, поэтому любой метод, который полагается на то, что они одинаковы, не подходит. Спасибо за предложения до сих пор, я не экспонировал так много идей так быстро.
3 ответов
просто некоторые соображения:
альтернатива #one
EDIT: этот подход требует изменения скомпилированных методов, что трудно и требует инъекции, модификации сборки или других методов, которые обычно используются в AOP-land. Рассмотрим подход два ниже, который проще.
- удалите все функции с одной и той же подписью, оставьте одну из них
- использовать
GetIlAsByteArray
создать динамический методDllImport
метод - использовать описанная здесь техника чтобы управлять IL функции, здесь вы можете изменить атрибуты DllImport и т. д.
- создайте делегат этих функций и кэшируйте свои вызовы
- вернуть делегата
вариант № два:
EDIT: этот альтернативный подход кажется немного вовлеченным сначала, но кто-то уже сделал работу за вас. Искать этот отличный CodeProject статья и просто загрузите и используйте его код для динамического создания методов стиля DllImport. В основном, это сводится к:
- удалить все DllImport
- создайте свою собственную оболочку DllImport: принимает имя dll и имя функции (при условии, что все подписи равны)
- обертка делает" ручной " DllImport с
LoadLibrary
илиLoadLibraryEx
использование функций DllImport API - обертка создает метод с
MethodBuilder
. - возвращает делегат этому методу, который можно использовать в качестве функции.
альтернатива #три
EDIT: глядя дальше, есть более простой подход: просто используйте DefinePInvokeMethod
который делает все, что вам нужно. Ссылка MSDN уже дает хороший пример, но полная оболочка, которая может создать любую собственную DLL на основе DLL и имени функции, предоставляется в этот CodeProject статья.
- удалите все подписи стиля DllImport
- создайте простой метод обертки вокруг
DefinePInvokeMethod
- обязательно добавьте простое кэширование (словарь?) чтобы предотвратить построение всего метода при каждом вызове
- возврат делегата из оболочки.
вот как этот подход выглядит в коде, вы можете использовать возвращенный делегат столько, сколько хотите, дорогостоящее построение динамического метода должно только один раз за метод.
EDIT: обновлен образец кода для работы с любым делегатом и автоматического отражения правильного типа возврата и типов параметров из подписи делегата. Таким образом, мы полностью отделили реализацию от подписи, что, учитывая вашу текущую ситуацию, является лучшим, что мы можем сделать. Преимущества: Вы имеете тип безопасность и одиночн-пункт -- изменения, который значит: очень легко управляемый.
// 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());
другие подходы, Я думаю (например, подход к шаблону, упомянутый кем-то еще в этой теме).
обновление: добавлена ссылка отличная статья codeproject.
обновление: добавлен третий и более легкий подход.
обновление: добавил пример кода
обновление: обновленный образец кода для работы с любым прототипом функции
обновление: исправлена страшная ошибка: typeof(Function02)
должно быть typeof(T)
конечно!--11-->
Как насчет использования T4 (Набор Инструментов Преобразования Текстовых Шаблонов). Создать .TT-файл со следующим содержимым:
<#@ 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 преобразует это в:
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);
}
}
Я бы также просто предложил использовать родной LoadLibrary
и GetProcAddress
.
С последним, вы просто называете Marshal.GetDelegateForFunctionPointer
С типом делегата, который соответствует сигнатуре метода pinvoke.