Установка dllimport программно в C # - PullRequest
9 голосов
/ 24 августа 2009

Я использую 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();
}

Я должен сделать это, потому что myDllName32 и myDllName64 должны быть постоянными, и я не нашел способа установить его во время выполнения.

Есть ли у кого-нибудь элегантное решение для этого, чтобы я мог избавиться от дублирования кода и постоянной проверки размера IntPtr.

Если бы я мог установить имя файла, мне пришлось бы проверять только один раз, и я мог бы избавиться от тонны повторяющегося кода.

Ответы [ 9 ]

18 голосов
/ 13 декабря 2009

Я предпочитаю делать это с помощью вызова LoadLibrary из kernel32.dll, чтобы принудительно загрузить определенную библиотеку DLL по определенному пути.

Если вы называете свои 32-разрядные и 64-разрядные библиотеки DLL одинаковыми, но размещаете их по разным путям, вы можете использовать следующий код для загрузки правильной версии в зависимости от используемой версии Windows. Все, что вам нужно сделать, это вызвать ExampleDllLoader.LoadDll () ПЕРЕД любым кодом, ссылающимся на класс ccf , на который ссылаются:

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);
    }
}
12 голосов
/ 24 августа 2009

Вероятно, этого можно добиться с помощью ключевого слова #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
    }

Для удобства, я думаю, вы могли бы создать конфигурации сборки для управления символом компиляции.

10 голосов
/ 01 мая 2012

Я знаю, что это действительно старый вопрос (я новичок - плохо ли отвечать на старый вопрос?), Но мне просто нужно было решить эту же проблему. Мне пришлось динамически ссылаться на 32-битную или 64-битную DLL на основе ОС, в то время как мой .EXE скомпилирован для любого процессора.

Вы можете использовать DLLImport, и вам не нужно использовать LoadLibrary ().

Я сделал это с помощью SetDLLDirectory . В отличие от имени, SetDLLDirectory добавляет к пути поиска DLL и не заменяет весь путь. Это позволило мне иметь DLL с тем же именем («TestDLL.dll» для этого обсуждения) в подкаталогах Win32 и Win64 и вызывать ее соответствующим образом.

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();
    }
}
2 голосов
/ 24 августа 2009

Почему бы не обернуть их в метод?

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();
        }
    }
}
1 голос
/ 26 октября 2017

вы можете создать два метода и выбрать один во время выполнения, чтобы вы могли сохранить 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();
    }

}

1 голос
/ 24 августа 2009

Один из альтернативных вариантов - иметь 32- и 64-разрядные версии неуправляемой DLL с одинаковыми именами, но они должны находиться в отдельных папках в выходных данных сборки (скажем, x86 \ и x64 \).

Затем ваш установщик или любой другой продукт, который вы распространяете, обновляется, поэтому он знает, как установить правильную DLL для платформы, на которую он устанавливает.

0 голосов
/ 17 ноября 2009

Вы можете определить, используете ли вы 64-битную версию или нет, проверив размер типа IntPtr (который в любом случае называется native int). Затем вы можете загрузить соответствующую библиотеку DLL с помощью импортированного вызова LoadLibraryW, получить указатель на функцию с помощью GetProcAddress, а затем проверить Marshal.GetDelegateForFunctionPointer

Это не так сложно, как может показаться. Вы должны DllImport и LoadLibraryW, и GetProcAddress.

0 голосов
/ 17 ноября 2009

Хм, мне интересно, не могли бы вы создать интерфейс, а затем класс с методами, основанными на 32-битных и 64-битных библиотеках.

Я не уверен, существует ли явный метод определения того, выполняется ли у вас 64-разрядная версия, но может сработать следующее: разрешить небезопасный код и иметь небезопасную функцию, которая получает указатель на некоторый адрес, а затем определяет, является ли указатель имеет размер 4 или 8 байт. На основании результата определите, какую реализацию интерфейса создать.

0 голосов
/ 24 августа 2009

Вы не можете делать это так, как хотите. Вы должны думать об атрибуте DllImport как о метаданных, которые используются во время компиляции. В результате вы не можете изменить DLL, которую она импортирует динамически.

Если вы хотите, чтобы ваш управляемый код был нацелен на «Любой ЦП», то вам нужно либо импортировать как 32-битные, так и 64-битные библиотеки как две разные функции, которые вы можете вызывать в зависимости от среды выполнения, или использовать дополнительные вызовы Win32 API для поздней загрузки правильной версии неуправляемой сборки во время выполнения и дополнительные вызовы Win32 для выполнения необходимых методов. Недостаток в том, что у вас не будет поддержки времени компиляции для любого типа кода для безопасности типов и т. Д.

...