多次导入同一个DLL库,动态DllImport注解
Import same DLL library multiple times, dynamic DllImport annotation
关于之前涉及该主题的 QA
情况:
- 我有闭源 DLL +
.cs
header,由第 3 方提供(我也有相同的 C .h
和 .lib
版本)
- 我已经验证了两次使用库(在不同的文件名下(参见 )是有效的,但是它需要 header class 两次,在不同的 class 名称和不同的 Dll 文件名用于
[DllImport]
注释
我正在寻找的是解决方案,它将允许我扩展经过验证的解决方案,从 2 个 DLL 实例到可能的 1000 个实例(如果硬件允许)。
我可以设法复制 DLL 文件并维护代码实例与文件实例映射,但是要准备 100 个 dll code-instances,我需要准备 100 个 .cs 副本 header 文件,这是不切实际和丑陋的。 header 文件有大约 30 个结构和 60 个接口方法。
header 文件的片段
// ExternalLibrary.cs
public class ExternalLibrary {
// Changing this (eg. "ExternalLibrary2.dll") along with class name (ExternalLibrary2) and filename (ExternalLibrary2.cs) is enough, nothing else imho needs to be changed
public const String ApiDll = "ExternalLibrary.dll";
[DllImport(ApiDll, CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 ExternalRoutine(UInt32 Input, out UInt32 Output);
}
- 是否有可能使用不同的文件名动态创建 class (header) 实例,这会影响
[DllImport]
注释?
- 如果不是,可以使用什么可扩展的解决方案?
感谢 @David Heffernan 和其他一些人,我提出了解决方案,它也可能适用于不同构建的库,所以我分享它。
这在很大程度上依赖于三件重要的事情
- 每次都必须从 DLL 文件的单独副本加载库
- ExternalLibrary.dll 已实现,因此它可以存在于自己的目录中,不会污染项目分发文件的其他部分
- 这里介绍的FunctionLoader只是Windows-compatible(使用Kernel32.dll提供的例程)
解决方案是在 Microsoft.NET.Sdk.Web
sdk 和 net5.0
目标之上测试的,项目是使用 Kestrel 而非 IIS 部署的,因此在测试时请注意这一点。
1。在分发中包含模板目录结构和原始库
具有这样的文件结构
/RootSolution.sln
/Project/
/Project/Project.csproj
/Project/ExternalLibrary/1/ExternalLibrary.dll
允许像这样创建 per-instance(GUID 标识)目录
/Project/ExternalLibrary/1/ExternalLibrary.dll
/Project/ExternalLibrary/516bbd6d-a5ec-42a5-93e0-d1949ca60767/ExternalLibrary.dll
/Project/ExternalLibrary/6bafaf3c-bc2b-4a1f-ae5c-696c37851b22/ExternalLibrary.dll
/Project/ExternalLibrary/0d0589fc-fc37-434d-82af-02e17a26d927/ExternalLibrary.dll
2。改造原库头文件
从如下所示的原始库头文件开始:
// ExternalLibrary.cs
public class ExternalLibrary {
public const String ApiDll = "ExternalLibrary.dll";
[DllImport(ApiDll, CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 ExternalRoutine(UInt32 Input, out UInt32 Output);
}
将其转化为
// ExternalLibrary.cs
using System;
using System.IO;
public class ExternalLibrary {
public string LibraryName { get; }
public Guid InstanceNo { get; }
public string DefaultLibrarySource { get; } = Path.Combine("ExternalLibrary", "1", "ExternalLibrary.dll");
public string LibrarySourceTemplate { get; } = Path.Combine("ExternalLibrary", "{0}", "ExternalLibrary.dll");
public ExternalLibrary(Guid InstanceNo)
{
// use constructor provided Guid to construct full path to copy of library and it's living directory
LibraryName = String.Format(LibrarySourceTemplate, InstanceNo);
LibraryName = Path.GetFullPath(LibraryName);
InstanceNo = InstanceNo;
// create guid-appropriate directory if it does not exist
var dirName = Path.GetDirectoryName(LibraryName);
if (!Directory.Exists(dirName))
{
Directory.CreateDirectory(dirName);
}
// copy over the source library if it's not yet present in guid-appropriate directory
if (!File.Exists(LibraryName))
{
File.Copy(DefaultLibrarySource, LibraryName);
}
// load function from correct DLL file into exposed delegated routine
ExternalRoutine = FunctionLoader.LoadFunction<_ExternalRoutine>(LibraryName, "ExternalRoutine");
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate Int32 _ExternalRoutine(UInt32 Input, out UInt32 Output);
public _ExternalRoutine ExternalRoutine;
}
3。在您的项目中包含 FunctionLoader class
// FunctionLoader.cs
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.InteropServices;
/// <summary>
/// Helper function to dynamically load DLL contained functions on Windows only
/// </summary>
internal class FunctionLoader
{
[DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr LoadLibrary(string path);
[DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
/// <summary>
/// Map String (library name) to IntPtr (reference from LoadLibrary)
/// </summary>
private static ConcurrentDictionary<string, IntPtr> LoadedLibraries { get; } = new ConcurrentDictionary<string, IntPtr>();
/// <summary>
/// Load function (by name) from DLL (by name) and return its delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dllPath"></param>
/// <param name="functionName"></param>
/// <returns></returns>
public static T LoadFunction<T>(string dllPath, string functionName)
{
// normalize
if (!Path.IsPathFullyQualified(dllPath))
{
dllPath = Path.GetFullPath(dllPath);
}
// Get preloaded or load the library on-demand
IntPtr hModule = LoadedLibraries.GetOrAdd(
dllPath,
valueFactory: (string dllPath) =>
{
IntPtr loaded = LoadLibrary(dllPath);
if (loaded == IntPtr.Zero)
{
throw new DllNotFoundException($"Library not found in path {dllPath}");
}
return loaded;
}
);
// Load function
var functionAddress = GetProcAddress(hModule, functionName);
if (functionAddress == IntPtr.Zero)
{
throw new EntryPointNotFoundException($"Function {functionName} not found in {dllPath}");
}
// Return delegate, casting is hack-ish, but simplifies usage
return (T)(object)(Marshal.GetDelegateForFunctionPointer(functionAddress, typeof(T)));
}
}
请告诉我,如果这个解决方案对您有用,或者您是否找到了更优雅的方法,谢谢
关于之前涉及该主题的 QA
情况:
- 我有闭源 DLL +
.cs
header,由第 3 方提供(我也有相同的 C.h
和.lib
版本) - 我已经验证了两次使用库(在不同的文件名下(参见 )是有效的,但是它需要 header class 两次,在不同的 class 名称和不同的 Dll 文件名用于
[DllImport]
注释
我正在寻找的是解决方案,它将允许我扩展经过验证的解决方案,从 2 个 DLL 实例到可能的 1000 个实例(如果硬件允许)。
我可以设法复制 DLL 文件并维护代码实例与文件实例映射,但是要准备 100 个 dll code-instances,我需要准备 100 个 .cs 副本 header 文件,这是不切实际和丑陋的。 header 文件有大约 30 个结构和 60 个接口方法。
header 文件的片段
// ExternalLibrary.cs
public class ExternalLibrary {
// Changing this (eg. "ExternalLibrary2.dll") along with class name (ExternalLibrary2) and filename (ExternalLibrary2.cs) is enough, nothing else imho needs to be changed
public const String ApiDll = "ExternalLibrary.dll";
[DllImport(ApiDll, CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 ExternalRoutine(UInt32 Input, out UInt32 Output);
}
- 是否有可能使用不同的文件名动态创建 class (header) 实例,这会影响
[DllImport]
注释? - 如果不是,可以使用什么可扩展的解决方案?
感谢 @David Heffernan 和其他一些人,我提出了解决方案,它也可能适用于不同构建的库,所以我分享它。
这在很大程度上依赖于三件重要的事情
- 每次都必须从 DLL 文件的单独副本加载库
- ExternalLibrary.dll 已实现,因此它可以存在于自己的目录中,不会污染项目分发文件的其他部分
- 这里介绍的FunctionLoader只是Windows-compatible(使用Kernel32.dll提供的例程)
解决方案是在 Microsoft.NET.Sdk.Web
sdk 和 net5.0
目标之上测试的,项目是使用 Kestrel 而非 IIS 部署的,因此在测试时请注意这一点。
1。在分发中包含模板目录结构和原始库
具有这样的文件结构
/RootSolution.sln
/Project/
/Project/Project.csproj
/Project/ExternalLibrary/1/ExternalLibrary.dll
允许像这样创建 per-instance(GUID 标识)目录
/Project/ExternalLibrary/1/ExternalLibrary.dll
/Project/ExternalLibrary/516bbd6d-a5ec-42a5-93e0-d1949ca60767/ExternalLibrary.dll
/Project/ExternalLibrary/6bafaf3c-bc2b-4a1f-ae5c-696c37851b22/ExternalLibrary.dll
/Project/ExternalLibrary/0d0589fc-fc37-434d-82af-02e17a26d927/ExternalLibrary.dll
2。改造原库头文件
从如下所示的原始库头文件开始:
// ExternalLibrary.cs
public class ExternalLibrary {
public const String ApiDll = "ExternalLibrary.dll";
[DllImport(ApiDll, CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 ExternalRoutine(UInt32 Input, out UInt32 Output);
}
将其转化为
// ExternalLibrary.cs
using System;
using System.IO;
public class ExternalLibrary {
public string LibraryName { get; }
public Guid InstanceNo { get; }
public string DefaultLibrarySource { get; } = Path.Combine("ExternalLibrary", "1", "ExternalLibrary.dll");
public string LibrarySourceTemplate { get; } = Path.Combine("ExternalLibrary", "{0}", "ExternalLibrary.dll");
public ExternalLibrary(Guid InstanceNo)
{
// use constructor provided Guid to construct full path to copy of library and it's living directory
LibraryName = String.Format(LibrarySourceTemplate, InstanceNo);
LibraryName = Path.GetFullPath(LibraryName);
InstanceNo = InstanceNo;
// create guid-appropriate directory if it does not exist
var dirName = Path.GetDirectoryName(LibraryName);
if (!Directory.Exists(dirName))
{
Directory.CreateDirectory(dirName);
}
// copy over the source library if it's not yet present in guid-appropriate directory
if (!File.Exists(LibraryName))
{
File.Copy(DefaultLibrarySource, LibraryName);
}
// load function from correct DLL file into exposed delegated routine
ExternalRoutine = FunctionLoader.LoadFunction<_ExternalRoutine>(LibraryName, "ExternalRoutine");
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate Int32 _ExternalRoutine(UInt32 Input, out UInt32 Output);
public _ExternalRoutine ExternalRoutine;
}
3。在您的项目中包含 FunctionLoader class
// FunctionLoader.cs
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.InteropServices;
/// <summary>
/// Helper function to dynamically load DLL contained functions on Windows only
/// </summary>
internal class FunctionLoader
{
[DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr LoadLibrary(string path);
[DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
/// <summary>
/// Map String (library name) to IntPtr (reference from LoadLibrary)
/// </summary>
private static ConcurrentDictionary<string, IntPtr> LoadedLibraries { get; } = new ConcurrentDictionary<string, IntPtr>();
/// <summary>
/// Load function (by name) from DLL (by name) and return its delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dllPath"></param>
/// <param name="functionName"></param>
/// <returns></returns>
public static T LoadFunction<T>(string dllPath, string functionName)
{
// normalize
if (!Path.IsPathFullyQualified(dllPath))
{
dllPath = Path.GetFullPath(dllPath);
}
// Get preloaded or load the library on-demand
IntPtr hModule = LoadedLibraries.GetOrAdd(
dllPath,
valueFactory: (string dllPath) =>
{
IntPtr loaded = LoadLibrary(dllPath);
if (loaded == IntPtr.Zero)
{
throw new DllNotFoundException($"Library not found in path {dllPath}");
}
return loaded;
}
);
// Load function
var functionAddress = GetProcAddress(hModule, functionName);
if (functionAddress == IntPtr.Zero)
{
throw new EntryPointNotFoundException($"Function {functionName} not found in {dllPath}");
}
// Return delegate, casting is hack-ish, but simplifies usage
return (T)(object)(Marshal.GetDelegateForFunctionPointer(functionAddress, typeof(T)));
}
}
请告诉我,如果这个解决方案对您有用,或者您是否找到了更优雅的方法,谢谢