根据平台加载x64或x86 DLL?

18

我有一个以“Any CPU”为目标平台构建的应用程序,并且有两个针对x86和x64的相同库的第三方DLL。 我想在客户端机器上根据平台在运行时包含其中一个库。最佳方法是什么?


1
这些是托管程序集还是本机代码? - Darin Dimitrov
为什么你不想只构建应用程序的x86和x64版本? - outcoldman
@outcoldman,那本来可以解决我所有的问题,但这意味着需要改变很多构建过程和对用户的支持。 - Ammark
1
反编译不会提供太多信息。你怎么称呼它们?如果你使用 p/invoke,那么 DLL 就是非托管的。 - David Heffernan
如果它是托管的,为什么不将类库编译为任何 CPU? - Yaur
显示剩余6条评论
3个回答

21

如果我们谈论未经管理的DLL,那么声明p/invokes应该像这样:

[DllImport("DllName.dll")]
static extern foo();

请注意,我们并未指定DLL的路径,只是指定了其名称,我认为32位和64位版本的名称应该是相同的。

在调用任何P/Invoke函数之前,先将库加载到进程中。可以通过P/Invoke调用LoadLibrary API函数来实现。此时,您将确定您的进程是32位还是64位,并相应地构建DLL的完整路径。这个完整的路径就是您传递给LoadLibrary的参数。

现在,当您调用库的p/invokes函数时,它们将被加载的模块解析。

对于托管程序集,您可以使用Assembly.LoadFile指定程序集的路径。这可能有点棘手,但这篇优秀的文章向您展示了如何实现:Automatically Choose 32 or 64 Bit Mixed Mode DLLs。与混合模式和本机DLL依赖项相关的细节可能与您无关。关键是使用AppDomain.CurrentDomain.AssemblyResolve事件处理程序。


太酷了。我不知道这个。 - Ani
谢谢David,我需要创建一个包装器来包装现有的dll吗?因为我需要访问的方法不是静态的。 - Ammark
你现在在调用代码并编译为 x86 或 x64 时,是否正在使用 DllImport - Scott Chamberlain
@ScottChamberlain 不,我实际上没有。 - Ammark
那么这不是解决您问题的方法(好吧,可能仍然是,但指示会略有不同),请编辑并澄清您原始问题中链接到的是托管DLL,而不是本地DLL。 编辑:算了,我刚看到David的编辑。您想要David放在底部的段落。 - Scott Chamberlain

2

我在这个话题上有一些经验,所以我想根据我在Pencil.Gaming中使用的方法来回答。首先,你需要从32位dll和64位dll(或者其他平台使用的dylib等)中"DllImport"两个函数。

static class Foo32 {
    [DllImport("32bitdll.dll")]
    internal static extern void Foo();
}
static class Foo64 {
    [DllImport("64bitdll.dll")]
    internal static extern void Foo();
}

那么您需要一个包含委托的中间类,并根据 IntPtr 的大小从32位或64位Interop导入它们(我不使用 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,因为它广泛使用了这种方法(这里是其被广泛使用的示例)。
另一个好处是,这种方法在所有平台上都可以使用,不依赖于任何WinAPI函数。

1
你不需要任何委托。你可以使用普通的 DllImport 来导入所有函数完成整个操作。 - David Heffernan
@DavidHeffernan 但这样你就不能使用“任何 CPU”了。然后你需要为32位和64位系统重新分发应用程序的不同版本。 - antonijn
当然可以使用AnyCPU。我的回答会解释如何操作。你需要确保DLLs的名称相同,但是它们必须位于不同的文件夹中。但这对于具有32/64位性质的DLL来说是很正常的。 - David Heffernan
@DavidHeffernan 我说的是使用 P/Invoke 调用 WinAPI 函数。我在这里没有看到任何 WinAPI 函数。 - antonijn
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/30076/discussion-between-antonijn-and-david-heffernan - antonijn
显示剩余5条评论

2

我的问题的完整解决方案是使用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;
        };

1
然而,这仅适用于 .net dlls,而不适用于任何 dll。 - Tyron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接