Подтвердить что ты не робот

Ссылка GNU C (POSIX) DLL, встроенная в GCC для Cygwin, из С#/NET

Вот что я хочу: у меня есть огромная устаревшая кодовая база C/C++, написанная для POSIX, включая некоторые очень специфичные для POSIX вещи, такие как pthreads. Это можно скомпилировать в Cygwin/GCC и запустить как исполняемый файл под Windows вместе с Cygwin DLL.

То, что я хотел бы сделать, это встроить саму кодовую базу в Windows DLL, на которую я могу затем ссылаться из С#, и написать обертку вокруг нее для программного доступа к некоторым ее частям.

Я попробовал этот подход на очень простом примере "hello world" на http://www.cygwin.com/cygwin-ug-net/dll.html, и он, похоже, не работает.

#include <stdio.h>
extern "C" __declspec(dllexport) int hello();

int hello()
{
  printf ("Hello World!\n");
 return 42;
}

Я считаю, что я должен иметь возможность ссылаться на DLL, созданную с помощью приведенного выше кода в С#, используя что-то вроде:

[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);

[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int hello();

static void Main(string[] args)
{
    var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "helloworld.dll");
    IntPtr pDll = LoadLibrary(path);
    IntPtr pAddressOfFunctionToCall = GetProcAddress(pDll, "hello");

    hello hello = (hello)Marshal.GetDelegateForFunctionPointer(
                                                                pAddressOfFunctionToCall,
                                                                typeof(hello));

    int theResult = hello();
    Console.WriteLine(theResult.ToString());
    bool result = FreeLibrary(pDll);
    Console.ReadKey();
}

Но этот подход, похоже, не работает. LoadLibrary возвращает ноль. Он может найти DLL (helloworld.dll), точно так же, как он не может загрузить ее или найти экспортированную функцию.

Я уверен, что если я получу этот базовый пример, я смогу ссылаться на остальную часть моей кодовой базы таким образом. Любые предложения или указатели, или кто-нибудь знает, возможно ли то, что я хочу? Благодарю.

Изменить: Изучил мою DLL с Dependency Walker (отличный инструмент, спасибо), и, кажется, правильно экспортировать функцию. Вопрос: я должен ссылаться на него, так как имя функции Dependency Walker, кажется, находит (_Z5hellov)? Dependency Walker Output

Edit2: просто чтобы показать вам, что я пробовал это, связывая непосредственно с DLL по относительному или абсолютному пути (то есть не используя LoadLibrary):

    [DllImport(@"C:\.....\helloworld.dll")]
    public static extern int hello();


    static void Main(string[] args)
    {
        int theResult = hello();
        Console.WriteLine(theResult.ToString());
        Console.ReadKey();
    }

Это приводит к ошибке: "Невозможно загрузить DLL" C:.....\helloworld.dll ": Неверный доступ к расположению памяти. (Исключение из HRESULT: 0x800703E6)

***** Редактировать 3: ***** Олег предложил запустить dumpbin.exe на моей DLL, это вывод:

Дамп файла helloworld.dll

Тип файла: DLL

Раздел содержит следующие экспорты для helloworld.dll

00000000 characteristics
4BD5037F time date stamp Mon Apr 26 15:07:43 2010
    0.00 version
       1 ordinal base
       1 number of functions
       1 number of names

ordinal hint RVA      name

      1    0 000010F0 hello

Резюме

    1000 .bss
    1000 .data
    1000 .debug_abbrev
    1000 .debug_info
    1000 .debug_line
    1000 .debug_pubnames
    1000 .edata
    1000 .eh_frame
    1000 .idata
    1000 .reloc
    1000 .text





Редактировать 4 Спасибо всем за помощь, мне удалось заставить его работать. Ответ Олега дал мне информацию, необходимую для выяснения, что я делаю не так.

Есть 2 способа сделать это. Один из них - это сборка с флагом компилятора gcc -mno-cygwin, который собирает dll без dll cygwin, в основном, как если бы вы создали его в MingW. Создавая его таким образом, я получил пример с моим привет миром! Однако MingW не имеет всех библиотек, которые есть у cygwin в установщике, поэтому, если ваш код POSIX имеет зависимости от этих библиотек (у меня были кучи), вы не сможете сделать это таким образом. И если в вашем коде POSIX не было этих зависимостей, почему бы просто не начать сборку для Win32 с самого начала. Так что это не сильно поможет, если вы не хотите тратить время на правильную настройку MingW.

Другим вариантом является сборка с помощью Cygwin DLL. Cygwin DLL требует вызова функции инициализации init(), прежде чем ее можно будет использовать. Вот почему мой код не работал раньше. Код ниже загружает и запускает мой пример "Привет, мир!"

    //[DllImport(@"hello.dll", EntryPoint = "#1",SetLastError = true)]
    //static extern int helloworld(); //don't do this! cygwin needs to be init first

    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr LoadLibrary(string lpFileName);


    public delegate int MyFunction();

    static void Main(string[] args)
    {
        //load cygwin dll
        IntPtr pcygwin = LoadLibrary("cygwin1.dll");
        IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init");
        Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action));
        init(); 

        IntPtr phello = LoadLibrary("hello.dll");
        IntPtr pfn = GetProcAddress(phello, "helloworld");
        MyFunction helloworld = (MyFunction)Marshal.GetDelegateForFunctionPointer(pfn, typeof(MyFunction));

        Console.WriteLine(helloworld());
        Console.ReadKey();
    }

Спасибо всем, кто ответил ~~

4b9b3361

Ответ 1

Основная проблема, которая у вас есть, следующая. Прежде чем вы сможете использовать helloworld.dll, необходимо инициализировать среду cygwin (см. http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw). Таким образом, следующий код в С++ будет работать:

#include <windows.h>

typedef int (*PFN_HELLO)();
typedef void (*PFN_CYGWIN_DLL_INIT)();

int main()
{
    PFN_HELLO fnHello;
    HMODULE hLib, h = LoadLibrary(TEXT("cygwin1.dll")); 
    PFN_CYGWIN_DLL_INIT init = (PFN_CYGWIN_DLL_INIT) GetProcAddress(h,"cygwin_dll_init");
    init(); 

    hLib = LoadLibrary (TEXT("C:\\cygwin\\home\\Oleg\\mydll.dll"));
    fnHello = (PFN_HELLO) GetProcAddress (hLib, "hello");
    return fnHello();
}

Из-за чего должен быть найден путь к cygwin1.dll. Вы можете установить C:\cygwin\bin в качестве текущего каталога, использовать функцию SetDllDirectory или легко включить C:\cygwin\bin в глобальной переменной среды PATH (щелкните правой кнопкой мыши на компьютере, выберите "Свойства", затем "Расширенные настройки системы" "," Переменные окружения... ", затем выберите системную переменную PATH и добавьте ее с помощью"; C:\cygwin\bin ").

Далее, если вы скомпилируете DLL, вам лучше использовать DEF файл для определения BASE-адреса DLL во время компиляции и делает все имена функций, которые вы экспортировали более ясно читаемыми (см. http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/win32.html)

Вы можете проверить результаты с помощью dumpbin.exe mydll.dll /exports, если у вас установлена ​​Visual Studio. (не забудьте запустить команду promt из "Visual Studio Command Prompt (2010)", чтобы все настройки Visual Studio).

ОБНОВЛЕНО. Поскольку вы не пишете об успехе, я думаю, что есть некоторые проблемы. В мире Win32/Win64 (неуправляемый мир) он работает. Код, который я опубликовал, я тестировал. Загрузка DLL CygWin в .NET может иметь некоторые проблемы. В http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw можно прочитать " Убедитесь, что у вас есть 4 Кбайта места с нуля в нижней части стека. Это требование может быть неправильным в .NET. Стек - это часть потока, а не процесс. Таким образом, вы можете попробовать использовать CygWin DLL в новом .NET Thread. С .NET 2.0 можно определить максимальный размер стека для потока. Другим способом пытается понять http://cygwin.com/cgi-bin/cvsweb.cgi/~checkout~/src/winsup/cygwin/how-cygtls-works.txt?rev=1.1&content-type=text/plain&cvsroot=src и код, описанный в http://old.nabble.com/Cygwin-dll-from-C--Application-td18616035.html#a18616996. Но действительно интересно, что я нахожу два пути без каких-либо трюков:

  • Компиляция DLL в отношении инструментов MinGW вместо инструментов CygWin. MinGW производит код, который намного совместим с Windows. Я сам не использую CygWin или MinGW, поэтому я не уверен, что вы сможете скомпилировать весь существующий код, используемый функцией POSIX в MinGW. Если это возможно, этот способ может иметь больший успех. Вы можете посмотреть http://www.adp-gmbh.ch/csharp/call_dll.html, чтобы увидеть, что DLL MinGW можно вызывать из С# точно так же, как Windows DLL.
  • Использование библиотеки CygWin внутри неуправляемого процесса или неуправляемого потока. Это стандартный способ, описанный в документации CygWin, и он работает (см. Пример из моего первого сообщения).

P.S. Пожалуйста, напишите коротко в тексте вашего вопроса, если у вас есть успех в одном из этих или иным способом, который вы выбираете в конце. Это интересно для меня независимо от репутации и щедрости.

Ответ 2

Сначала вы должны попробовать запустить простой образец hello world. Существует не так много смысла в попытке создать огромную базу кода C/С++, написанную для POSIX, в зависимости от Cygwin, если вы даже не получите прежнее право. Обратите внимание, что если вы связываете Cygwin, вы должны GPL-лицензировать свою библиотеку.

Для этого взгляните на документацию (например, вам нужно указать явно в вашем примере hello world, если вы используете Cdecl (чего вы в настоящее время не делаете): Потребление неуправляемых функций DLL

На родной стороне вы должны инициализировать Cygwin (см. winsup/cygwin/how-cygtls-works.txt)

Используйте P/Invoke непосредственно в своей библиотеке. Нет смысла в PInvoking в библиотеках Win32, таких как LoadLibrary, чтобы затем вызывать ваши библиотеки. Это просто добавляет еще один слой для ошибок и ничего не набирает.

Убедитесь, что вы правильно настроили архитектуру (приложения .NET запускают 64-битную по умолчанию на 64-битных машинах). Поэтому убедитесь, что ваши dlls соответствуют/поддерживают это или ограничивают .Net до 32 бит.

Если это работает, попробуйте заставить свою другую библиотеку работать. (И вам придётся немного удачи, если вы ожидаете использовать функции, которые смешивают две совершенно разные модели потоков)

Ответ 3

Стандартный компилятор MS C поддерживает большинство интерфейсов POSIX, включая pthreads. Иногда как отдельные реализации, но обычно как макросы, которые преобразуют синтаксис POSIX в вызовы библиотеки Windows.

Если ваш C-код не имеет слишком много "gnuisms", вы должны скомпилировать его с помощью стандартного компилятора Visual C.

Ответ 4

Вы должны иметь возможность ссылаться на DLL, созданную против Cygwin, без необходимости создавать новую DLL. Единственное требование - убедиться, что и "cygwin.dll", и DLL, которые вы пытаетесь загрузить, находятся на соответствующих путях. Вероятно, вам нужно будет SetDllDirectory до вызова "LoadLibrary".

Ответ 5

Код в письменном виде не будет работать, имя экспортируемой функции будет украшено компилятором С++ и больше не будет напоминать "привет". Обычно вы объявляете функцию extern "C" подавлять декор.

Но вы еще не дошли до этого. В вызове LoadLibrary() происходит ошибка Windows 998, ERROR_NOACCESS, "Недопустимый доступ к ячейке памяти". Обычно это происходит, когда точка входа DllMain() в одной из библиотек DLL имеет зависимость от бомб с аппаратным исключением AccessViolation. Это должно быть видимым в отладчике (окно вывода в Visual Studio), вы должны увидеть "исключение первого шанса" с кодом исключения 0xc0000005.

Это, вероятно, будет неприятно для диагностики, у вас есть несколько DLL, которые являются кандидатами и чьи отладочные символы, вероятно, плохо подходят для используемого вами отладчика. Попробуйте изолировать это, написав небольшую тестовую программу в C, которая вызывает LoadLibrary. Настройте отладчик, чтобы он остановился на первом исключении. В Visual Studio вы сделаете это с помощью флажка Отладка + Исключения, Брошенный. Удачи вам!