在C#中以编程方式设置dllimport

9

我在我的解决方案中使用了 DllImport

我的问题是,我有两个版本的同一个DLL,一个是32位的,另一个是64位的。

它们都暴露了相同名称和相同签名的函数。

我的问题是,我必须使用两个静态方法来暴露这些函数,并在运行时使用 IntPtr 大小来确定要调用哪个方法。

private static class Ccf_32
{
    [DllImport(myDllName32)]
    public static extern int func1();
}

private static class Ccf_64
{
    [DllImport(myDllName64)]
    public static extern int func1();
}

我不得不这样做,因为myDllName32myDllName64必须是常量,而我没有找到在运行时设置它的方法。

是否有一种优雅的解决方案,让我可以摆脱代码重复和常量IntPtr大小检查。

如果我能设置文件名,我只需要检查一次,就可以摆脱大量重复的代码。


如果差别在整个编译过程中,选择运行时没有意义。 - Havenard
9个回答

20

我喜欢使用从kernel32.dll中调用LoadLibrary的方法,强制让特定的DLL从指定路径加载。

如果你将32位和64位的DLL命名相同但放在不同的路径下,那么您可以使用以下代码根据您所运行的Windows版本来加载正确的DLL。只需在引用ccf类的任何代码之前调用ExampleDllLoader.LoadDll()

private static class ccf
{
    [DllImport("myDllName")]
    public static extern int func1();
}

public static class ExampleDllLoader
{
    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    private extern static IntPtr LoadLibrary(string librayName);

    public static void LoadDll()
    {
        String path;

        //IntPtr.Size will be 4 in 32-bit processes, 8 in 64-bit processes 
        if (IntPtr.Size == 4)
            path = "c:/example32bitpath/myDllName.dll";
        else
            path = "c:/example64bitpath/myDllName.dll";

        LoadLibrary(path);
    }
}

看起来这个解决方案需要用户具有管理员权限。有人可以确认吗? - Julio Garcia
1
只要用户有读取被加载的DLL的权限,这就不需要管理员权限。我在普通用户运行应用程序的情况下使用过它。 - Josh Sklare

13

你可以使用#if关键字实现这个功能。如果你定义了一个名为win32的条件编译符号,下面的代码将使用win32块,如果你移除它,它将使用另一个块:

#if win32
    private static class ccf_32
    {
        [DllImport(myDllName32)]
        public static extern int func1();
    }
#else    
    private static class ccf_64
    {
        [DllImport(myDllName64)]
        public static extern int func1();
    }
#endif

这可能意味着您现在可以删除已有的类包装:

    private static class ccf
    {
#if win32
        [DllImport(myDllName32)]
        public static extern int func1();
#else    
        [DllImport(myDllName64)]
        public static extern int func1();
#endif
    }
为了方便起见,我想你可以创建构建配置来控制编译符号。

3
是的,但我想保留“任意 CPU”选项,而不是拥有 32 位和 64 位版本。 - Matt
虽然这种方法可以工作,但仍会导致代码重复,这正是原帖作者想要避免的,而且还限制了您只能为32位和64位编译单独的版本。您可以采用deanis的方法使用SetDllDirectory或者我的方法使用LoadLibrary来消除代码重复,并继续以一个编译的DLL/EXE目标32位和64位版本。 - Josh Sklare

11

我知道这是一个很旧的问题(我是新手 - 回答旧问题是否不好?),但我必须解决相同的问题。我的.EXE编译为Any CPU,我必须根据操作系统动态引用32位或64位的DLL。

你可以使用DLLImport,并且不需要使用LoadLibrary()。

我通过使用SetDLLDirectory实现了这一点。与名称相反,SetDLLDirectory将添加到DLL搜索路径,而不是替换整个路径。这使我能够在Win32和Win64子目录中具有相同名称的DLL(本讨论中为“TestDLL.dll”)并适当调用。

public partial class frmTest : Form
{
    static bool Win32 = Marshal.SizeOf(typeof(IntPtr)) == 4;
    private string DLLPath = Win32 ? @"\Win32" : @"\Win64";

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool SetDllDirectory(string lpPathName);
    [DllImport("TestDLL.dll", SetLastError = true)]
    static extern IntPtr CreateTestWindow();

    private void btnTest_Click(object sender, EventArgs e)
    {
        string dllDir = String.Concat(Directory.GetCurrentDirectory(), DLLPath);
        SetDllDirectory(dllDir);

        IntPtr newWindow = CreateTestWindow();
    }
}

3
这是一个很好的方法,尤其是当你需要32位和64位版本的多个DLL时。我认为我更喜欢使用SetDllDirectory方法,而不是我在答案中解释的LoadDll方法。这也比包装器方法少了很多代码,并且可以让你编译任何CPU。 - Josh Sklare

2
为什么不将它们封装成一个方法?
private static class ccf_32_64
{
    private static class ccf_32
    {
        [DllImport(myDllName32)]
        private static extern int func1();
    }

    private static class ccf_64
    {
        [DllImport(myDllName64)]
        private static extern int func1();
    }

    public static int func1()
    {
        if (32bit)
        {
            return ccf_32.func1();
        }
        else
        {
            return ccf_64.func1();
        }
    }
}

这基本上就是我现在拥有的 :-) - Matt
一旦你把它包装起来,就不用再担心它了。 - ChaosPandion

2

一种替代方案是使32位和64位未托管的DLL版本具有相同的名称,但将它们放置在构建输出的不同文件夹中(例如x86\和x64\)。

然后,您的安装程序或其他分发方式需要更新,以便它知道为正在安装的平台安装正确的DLL。


1

您可以创建两种方法并在运行时选择其中一种,这样您就可以保留Any CPU

public static class Ccf
{
    [DllImport(myDllName32)]
    private static extern int func32();

    [DllImport(myDllName64)]
    private static extern int func64();


    public static int func()
    {
        if(Environment.Is64BitProcess)
        {
            return func64();
        }
        return func32();
    }

}


0

嗯,我在想你是否可以创建一个接口,然后基于32位和64位的dll创建一个类,并编写相应的方法。

我不确定是否有明确的方法来确定您是否正在运行64位,但以下方法可能有效:允许不安全代码并具有获取指向某个地址的指针的不安全函数,然后确定指针的大小是4字节还是8字节。根据结果确定要创建哪个接口实现。


0
你可以通过检查 IntPtr 类型的大小(无论如何它都被称为 native int)来确定是否正在运行 64 位。 然后,您可以使用导入的 LoadLibraryW 调用加载相应的 DLL,使用 GetProcAddress 获取函数指针,然后查看 Marshal.GetDelegateForFunctionPointer
这并不像看起来那么复杂。您必须同时 DllImport LoadLibraryW 和 GetProcAddress。

0
你不能按照你想要的方式做这件事。你需要把 DllImport 属性看作是在编译时使用的元数据。因此,你不能动态地更改它导入的 DLL。
如果你想保持你的托管代码针对 "Any CPU",那么你需要导入两个不同的函数,分别包装 32 位和 64 位库,以便在运行时根据运行环境调用其中一个,或者使用一些额外的 Win32 API 调用来在运行时延迟加载正确版本的非托管程序集,并使用其他 Win32 调用来执行所需的方法。缺点是你将没有编译时支持任何类型安全等代码的支持。

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