我有一个构建为“任何 CPU”的应用程序,并且有两个针对 x86 和 x64 的同一库的第三方 Dll。我想在运行时包含其中一个库,具体取决于它将在客户端计算机上运行的平台。最好的方法是什么?
3 回答
如果我们谈论的是非托管 DLL,请像这样声明 p/invokes:
[DllImport("DllName.dll")]
static extern foo();
请注意,我们没有指定 DLL 的路径,只是它的名称,我认为 32 位和 64 位版本的名称相同。
然后,在您调用任何 p/invokes 之前,将库加载到您的进程中。通过 p/调用LoadLibrary
API 函数来做到这一点。此时,您将确定您的进程是 32 位还是 64 位,并相应地构建 DLL 的完整路径。那条完整的路径就是你传递给LoadLibrary
.
现在,当您为库调用 p/invokes 时,它们将由您刚刚加载的模块解析。
对于托管程序集,您可以使用它Assembly.LoadFile
来指定程序集的路径。这可能有点难以编排,但这篇优秀的文章向您展示了如何:自动选择 32 或 64 位混合模式 DLL。有很多与混合模式和本机 DLL 依赖项相关的细节可能与您无关。关键是AppDomain.CurrentDomain.AssemblyResolve
事件处理程序。
我实际上对这个话题有点经验,所以我想我会根据我在 Pencil.Gaming 中使用的方式发布答案。首先,您必须“ DllImport
”两个函数,一个来自 32 位 dll,一个来自 64 位 dll(左右,或 dylib,无论您的平台使用什么)。
static class Foo32 {
[DllImport("32bitdll.dll")]
internal static extern void Foo();
}
static class Foo64 {
[DllImport("64bitdll.dll")]
internal static extern void Foo();
}
然后你需要一个包含委托的中间类,并根据一个的大小从 32 位或 64 位互操作中导入它们IntPtr
(我不使用Environment.Is64BitProcess
,因为这是一个 .NET 4 函数):
internal delegate void FooDelegate();
static class FooDelegates {
internal static FooDelegate Foo;
static FooDelegates() {
Type interop = (IntPtr.Size == 8) ? typeof(Foo64) : typeof(Foo32);
FieldInfo[] fields = typeof(FooDelegates).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
foreach (FieldInfo fi in fields) {
MethodInfo mi = glfwInterop.GetMethod(fi.Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
Delegate function = Delegate.CreateDelegate(fi.FieldType, mi);
fi.SetValue(null, function);
}
}
}
然后我通常使用一个“真实”类,其中包含您导入的函数(尽管这在技术上不是必需的):
public static class FooApi {
public static void Foo() {
FooDelegates.Foo();
}
}
如果您只需要一个或两个函数,这真的很痛苦,但是导入委托的方式对于更大规模的库/应用程序来说非常有效。您可能想查看 github 上的 Pencil.Gaming,因为它非常广泛地使用了这种方法(这里是它被大量使用的示例)。
这种方法的另一个好处是它是 100% 跨平台的,并且不依赖于任何 WinAPI 函数。
我对我的问题的完整解决方案是使用 David Heffernan 提供的第二个链接。我所做的是 1. 在项目中引用了一个虚拟 dll。2.指定两个预构建事件
xcopy /y "$(SolutionDir)\Assemblies\Lib\x86\(Assembly name)*" "$(TargetDir)"
xcopy /y "$(SolutionDir)\Assemblies\Lib\x64\(Assemble name)*" "$(TargetDir)"
3. 在程序集解析事件中启动应用程序时,根据平台更改相应的程序集。
var currentDomain = AppDomain.CurrentDomain;
var location = Assembly.GetExecutingAssembly().Location;
var assemblyDir = Path.GetDirectoryName(location);
if (assemblyDir != null && (File.Exists(Path.Combine(assemblyDir, "(Assembly name).proxy.dll"))
|| !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x86.dll"))
|| !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x64.dll"))))
{
throw new InvalidOperationException("Found (Assembly name).proxy.dll which cannot exist. "
+ "Must instead have (Assembly name).x86.dll and (Assembly name).x64.dll. Check your build settings.");
}
currentDomain.AssemblyResolve += (sender, arg) =>
{
if (arg.Name.StartsWith("(Assembly name),", StringComparison.OrdinalIgnoreCase))
{
string fileName = Path.Combine(assemblyDir,
string.Format("(Assembly).{0}.dll", (IntPtr.Size == 4) ? "x86" : "x64"));
return Assembly.LoadFile(fileName);
}
return null;
};