Как сделать ссылку на C # (т.е. управляемую) DLL от Haskell? - PullRequest
0 голосов
/ 02 июля 2018

Я пытаюсь собрать Windows DLL из моего кода на Haskell. Функции в этой DLL должны вызываться из управляемого кода в C #. И, по крайней мере, одна из функций (определенных в коде c #) должна вызываться из функции в этой DLL.

С риском объяснения, вот небольшая диаграмма, чтобы изобразить то, что я хочу:

+----------------------+           +------------------------+
|   Managed C# code    |           |  Haskell code (in DLL) |
|                      |     (1)   |                        |
|  fn_calling_hs()  -----------------> fn_called_from_cs()  |   
|                      |           |                        |
|                      |           |                        |          
| fn_called_from_hs() <--------------- fn_calling_cs()      |
|                      |     (2)   |                        |
+----------------------+           +------------------------+

Мне удалось заставить (1) работать отлично, то есть функция Haskell в DLL вызывается из кода C #, с правильным распределением структур и массивов, и результаты выполнения функции в Haskell также верны. Пока все хорошо.

Проблема в (2), то есть функции из Haskell (в DLL), вызывающей управляемую функцию, определенную в C #. Проблема в самой сборке - я еще не прошел мимо этого, чтобы на самом деле проверить результаты (2).

Поскольку fn_called_from_hs () в управляемом коде c # определено в C #, у меня только символ функции, «импортированный» в коде Haskell (в DLL):

foreign import ccall fn_called_from_hs :: IO CString

Теперь, когда я собираю свой проект на Haskell со стеком, он без проблем собирает DLL на Haskell, но сборка продолжает также связывать «main.exe» - и это не получается (очевидно), потому что нет функции fn_called_from_hs () определяется в любом месте кода Haskell (он определен в c #).

Есть ли способ остановить стек для продолжения сборки main.exe после сборки HsDLL.dll? Я в порядке с HsDLL.dll с неразрешенным символом (fn_called_from_hs ()), потому что этот символ будет найден компоновщиком времени выполнения во время загрузки этой DLL управляемым кодом C #.

Пока я пробовал эти шаги, но ни один из них не помог:

  1. Удалены «исполняемые файлы» и «тест» из package.yaml
  2. Добавлена ​​опция GHC: -no-hs-main в package.yaml. Package.yaml часть, которая содержит построение HsDLL, выглядит так:

    library:   
      source-dirs: 
      - src
      - src/csrc   
      include-dirs: src/csrc   
      ghc-options:
      - -shared
      - -fno-shared-implib 
      - -no-hs-main
    
    1. Полностью удален основной модуль (т. Е. Удален Main.hs, автоматически созданный стеком из папки «app»)
    2. Я добавил флаг -dynamic в опции ghc в надежде, что GHC будет предполагать, что неразрешенные символы будут определены в другом месте, но это породило другие проблемы: теперь GHC жалуется, что ему нужны библиотеки "dyn" базы, и т.д.

Итак, наконец, я всегда получаю следующее:

PS C:\workspace\Haskell\hscs\src\csrc> stack build
hscs-0.1.0.0: configure (lib)
Configuring hscs-0.1.0.0...
hscs-0.1.0.0: build (lib)
Preprocessing library for hscs-0.1.0.0..
Building library for hscs-0.1.0.0..
Linking main.exe ...
.stack-work\dist\5c8418a7\build\HsLib.o:fake:(.text+0x541): undefined reference to `fn_called_from_hs'
collect2.exe: error: ld returned 1 exit status
`gcc.exe' failed in phase `Linker'. (Exit code: 1)

--  While building custom Setup.hs for package hscs-0.1.0.0 using:
      C:\tools\HaskellStack\setup-exe-cache\x86_64-windows\Cabal-simple_Z6RU0evB_2.0.1.0_ghc-8.2.2.exe --builddir=.stack-work\dist\5c8418a7 build lib:hscs --ghc-options " -ddump-hi -ddump-to-file -fdiagnostics-color=always"
    Process exited with code: ExitFailure 1

Итак, мои вопросы: (1) Я понятия не имею, как перестать ссылаться на "main.exe"! Я знаю, что функция fn_called_from_hs () не определена в HsDLL, но, как я уже сказал, я в порядке, потому что она определена в управляемом коде c #. Я просто хочу, чтобы main.exe не создавался.

OR

(2) Стоит ли добавлять флаг -dynamic в GHC (оставив все остальные флаги, как указано выше)? В таком случае, как мне получить стек для установки библиотек "dyn", на которые жалуется GHC?

Кто-нибудь может мне помочь? Заранее благодарим за терпение в чтении этого (довольно) длинного вопроса!

1 Ответ

0 голосов
/ 06 июля 2018

И вот наконец мне удалось решить это самому! После недели борьбы. И любые полезные комментарии, чтобы добавить его, этот ответ приветствуется.

Я сделал это следующим образом:

В DLL класса C #:

Мне нужно было найти способ "экспортировать" мою функцию fn_called_from_hs() в небезопасный собственный код. Я обнаружил, что это не совсем просто, и в Интернете действительно есть довольно много статей, объясняющих, как это делается. Все сводится к фактической разборке .NET DLL с помощью инструмента ildasm, и в сгенерированном промежуточном файле IL добавление префикса «.export» к функции, которую мы хотим экспортировать, и затем снова сборка файла IL обратно в Форма DLL с использованием ilasm.

Я обнаружил, что все эти шаги автоматизированы с помощью NUGetPackage Неуправляемый экспорт , поэтому первым шагом является установка этого пакета как части вашего проекта .NET, а затем добавление атрибута DLLExport в ваш функция для экспорта. Убедитесь, что в вашем списке импорта RGiesecke.DllExport:

using RGiesecke.DllExport;

[DllExport("fn_called_from_hs", CallingConvention=CallingConvention.Cdecl)]
public static string FnCalledFromHs()
{
      // Your function code here
}

Как видите, я назвал фактическую функцию как FnCalledFromHs() (в соответствии с соглашением об именах в C #), но экспортировал ту же функцию, что и fn_called_from_hs (в соответствии с соглашением об именах в Haskell). Таким образом, когда вы смотрите на код на Haskell, вы не увидите ничего, что выглядит неуместным.

Один из самых важных шагов для фактической работы - убедиться, что проект, в который вы экспортируете функцию, выполнен для цели x64 или x86 - по умолчанию проекты нацелены на «Любой процессор» - RGiesecke.DllExport делает не работает, если проект нацелен на «Любой процессор».

Теперь соберите проект, чтобы получить csharp.dll , который содержит экспортированный файл fn_called_from_hs.

Перед установкой ссылки на код Haskell

Mingw GCC (который ghc используется в Windows для внутреннего использования) может напрямую связываться с DLL, если они были созданы с помощью gcc ранее. Однако, поскольку мы создали нашу C # DLL с использованием компилятора .NET csc, нам нужно специально создать библиотеку импорта, которую может видеть наш Haskell.

Мы используем два инструмента: gendef и dlltool, оба из которых находятся в папке «mingw \ bin» в вашей установке ghc (поэтому, конечно, вам нужно иметь это в вашей PATH env переменная для доступа к этим инструментам).

Вот как я это сделал:

  • Создан файл .def, который, в свою очередь, можно использовать для создания библиотеки импорта:

    gendef csharp.dll
    
  • Создана библиотека импорта с помощью dlltool:

    dlltool -k -d csharp.def -l csharp.lib
    
  • Скопировал указанную выше библиотеку импорта в тот же каталог, в котором находилась DLL.

Последний шаг (ниже) теперь будет использовать эту библиотеку импорта для фактического связывания с библиотекой csharp.

Связывание кода на Haskell с указанной выше библиотекой импорта

Это было немного хитрее, и, возможно, заставило меня исправить ошибку в стеке / GHC (не уверен), но уже подал сюда .

Я сделал это следующим образом:

  • Добавлен extra-lib-dirs в мой stack.yaml и добавлен каталог, в котором был создан вышеупомянутый import-lib:

    extra-lib-dirs: ["<drive>:\\path\\to\\importlib"]
    

(обратите внимание, что это также может быть добавлено в ваш package.yaml в разделе "библиотеки", но я решил добавить его в мой stack.yaml).

  • Добавлено extra-libraries в мой stack.yaml, под библиотеками.

    extra-libraries: csharp
    
  • И добавил также опции -l и -L к моим опциям ghc для связывания моей библиотеки. Это то, что я сделал, чтобы обойти (возможную) ошибку , так как стек не передает extra-lib-dirs и extra-libraries в ghc и ld во время соединения. Итак, мой последний раздел «library» в package.yaml выглядит следующим образом (сравните его с тем, что было раньше в моем вопросе выше):

     library:
       source-dirs: 
       - src
       - src/csrc
       include-dirs: src/csrc
       ghc-options:
       - -shared
       - -fno-shared-implib
       - -lcslib
       - -L<drive>:\\path\\to\\importlib
       extra-libraries: csharp
    

Заключение

После всего этого мой код на Haskell теперь просто хорошо собирается с помощью обычной команды stack build, без каких-либо ошибок "unreferenced symbols". Выполняя мой код на Haskell, я также проверил, что функция c # fn_called_from_hs действительно была вызвана, и результаты были возвращены правильно.

Конечно, в стороне от c # есть еще кое-что: правильная сортировка параметров и т. Д., И мне пришлось работать над ними, чтобы получить правильный результат. Единственное место, где я могу охватить все эти мелочи, находится в блоге :-)

Пожалуйста, не стесняйтесь перепроверить мое решение, а также прокомментировать любой лучший способ сделать это. Это был лучший способ, который я мог понять после моей борьбы!

...