Ссылка на библиотеку GNU C (POSIX), встроенную в GCC для Cygwin, из C # / NET - PullRequest
12 голосов
/ 26 апреля 2010

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

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

Я попробовал этот подход на очень простом примере "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, созданную с помощью приведенного выше кода на C #, используя что-то вроде:

[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)? Вывод зависимостей Walker http://i44.tinypic.com/1yagqv.png

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 Спасибо всем за помощь, мне удалось заставить ее работать. Ответ Олега дал мне информацию, необходимую для выяснения, что я делаю неправильно.

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

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

    //[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();
    }

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

Ответы [ 5 ]

9 голосов
/ 03 мая 2010

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

#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. (не забудьте запустить командную строку из «Командная строка Visual Studio (2010)», чтобы установить все Visual Studio).

ОБНОВЛЕНО : Поскольку вы не пишете об успехе, я думаю, что существуют некоторые проблемы. В мире Win32 / Win64 (неуправляемый мир) это работает. Код, который я разместил, я протестировал. Загрузка CygWin DLL в .NET может иметь некоторые проблемы. В http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw можно прочитать « Убедитесь, что у вас есть 4K пустого пространства внизу стека ». Это требование может быть неверным в .NET. Стек является частью потока, а не процесса. Таким образом, вы можете попробовать использовать библиотеки CygWin в новой .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. Но по-настоящему интересно найти два способа без каких-либо уловок:

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

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

2 голосов
/ 02 мая 2010

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

Для этого взгляните на документацию (например, вам нужно явно указать в своем примере "Hello World", используете ли вы Cdecl (чего вы в данный момент не делаете)): Использование неуправляемых функций DLL

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

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

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

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

1 голос
/ 02 мая 2010

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

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

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

1 голос
/ 30 апреля 2010

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

1 голос
/ 26 апреля 2010

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

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

...