如何从Haskell链接到C#(即托管)DLL?

时间:2018-07-02 05:54:50

标签: haskell haskell-stack haskell-ffi

我正在尝试从我的Haskell代码构建Windows DLL。该DLL中的功能应该从C#中的托管代码中调用。并且,至少要从该DLL中的一个函数调用该函数(在C#代码中定义)中的一个。

冒着过度解释的危险,下面是一个小图来描述我想要的东西:

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

我设法使(1)完美工作,即DLL中的Haskell函数由C#代码调用,具有正确的结构和数组编组,并且在Haskell中执行该函数的结果也正确。到目前为止,一切都很好。

问题出在(2),即Haskell(在DLL中)的函数调用C#中定义的托管函数。问题出在构建本身中-我还没有超越它来实际检查(2)的结果。

由于C#托管代码中的fn_Called_from_hs()是在C#中定义的,因此在Haskell代码(在DLL中)中,我只有功能符号“ imported”:

foreign import ccall fn_called_from_hs :: IO CString

现在,当我使用堆栈构建Haskell项目时,它可以毫无问题地构建Haskell DLL,但是该构建仍继续链接“ main.exe”-并失败了(显然),因为没有函数fn_drawn_from_hs()定义在Haskell代码的任何地方(在c#中定义)。

在构建HsDLL.dll之后,有什么方法可以阻止堆栈继续构建main.exe吗?我对带有未解析符号(fn_drawn_from_hs())的HsDLL.dll表示满意,因为在托管C#代码加载此DLL的过程中,运行时链接程序将找到此符号。

到目前为止,我已经尝试了这些步骤,但是没有一个帮助:

  1. 从package.yaml中删除了“可执行文件”和“测试”
  2. 在package.yaml中添加了GHC选项:-no-hs-main。 package.yaml 包含HsDLL构建的部分如下所示:

    library:   
      source-dirs: 
      - src
      - src/csrc   
      include-dirs: src/csrc   
      ghc-options:
      - -shared
      - -fno-shared-implib 
      - -no-hs-main
    
    1. 完全删除了Main模块(即,从堆栈中自动从“ app”文件夹中删除了Main.hs)
    2. 我在ghc-options中添加了-dynamic标志,希望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)我是否应该继续向GHC添加-dynamic标志(保持上面所有其他标志)?在这种情况下,如何获取堆栈来安装GHC抱怨的“ dyn”库?

有人可以帮我吗?预先感谢您耐心阅读这个较长的问题!

1 个答案:

答案 0 :(得分:5)

所以最后,我设法自己解决了!经过一周的奋斗,就是这样。欢迎任何有用的评论来补充这个答案。

我这样做如下:

在C#类DLL中:

我必须找到一种将函数fn_called_from_hs()“导出”到不安全的本机代码的方法。我发现这并不是很简单,在互联网上确实有相当多的文章来解释如何做到这一点。一切实际上都等于通过工具ildasm来反汇编.NET DLL,并在生成的中间IL文件中,向我们要导出的函数添加“ .export”前缀,然后再次将IL文件反汇编使用ilasm到DLL形式。

我发现所有这些步骤都是由NUGetPackage Unmanaged Exports自动执行的,因此第一步是将该软件包安装为.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相同的函数(根据C#中的命名约定) Haskell)。这样,当您查看Haskell代码时,将看不到任何看起来不合适的地方。

要使其真正起作用,最重要的步骤之一就是确保将要导出功能的项目以x64或x86为目标-默认情况下,项目以“ Any CPU”为目标-{{1}如果项目的目标是“任何CPU”,则}无效。

现在构建项目以获取包含您导出的RGiesecke.DllExport csharp.dll

在链接Haskell代码之前

Mingw GCC(Windows上的ghc内部使用)可以直接与DLL链接,前提是它们以前是用gcc创建的。但是,由于我们使用.NET编译器csc创建了C#DLL,因此我们需要专门创建一个Haskell可以看到的导入库。

我们使用两种工具来帮助您:fn_called_from_hsgendef,它们都位于ghc安装中的“ mingw \ bin”文件夹中(因此,当然,您需要具有此工具在PATH环境变量中访问这些工具)。

这是我的处理方式:

  • 创建了一个.def文件,该文件可用于创建导入库:

    dlltool
  • 使用dlltool创建了一个导入库:

    gendef csharp.dll
    
  • 将上述导入库复制到DLL所在的目录中。

最后一步(下面)现在将使用此导入库实际链接csharp DLL。

将Haskell代码与上述导入库链接

这有点棘手,可能使我遇到了堆栈/ GHC中的错误(不确定),但是已经filed here

我对此进行了如下操作:

  • 在我的stack.yaml中添加了dlltool -k -d csharp.def -l csharp.lib ,并添加了创建以上import-lib的目录:

    extra-lib-dirs

(请注意,这也可能已添加到您的package.yaml中的“库”下,但我选择将其放入我的stack.yaml中。)

  • 在库下将extra-lib-dirs: ["<drive>:\\path\\to\\importlib"] 添加到我的stack.yaml中。

    extra-libraries
  • 并且,还为我的ghc-options添加了选项-l和-L来链接我的库。这是我为避免(strong)(可能的)错误 而做的,该错误在链接过程中以某种方式未将extra-libraries: csharp extra-lib-dirs传递给ghc和ld 。所以,我在package.yaml中的最后一个“库”部分看起来像这样(将其与上面的问题进行比较):

    extra-libraries

结论

完成所有这些操作后,我的Haskell代码现在可以使用常规的 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代码时,我还检查了c#函数stack build的实际调用,并正确返回了结果。

当然,在c#方面还有更多的东西:正确地编组参数等,而且我还必须处理那些参数才能使结果正确。我可以覆盖所有这些棘手问题的唯一地方是在博客中:-)

请随时对我的解决方案进行交叉验证,并评论任何更好的方法。这是我挣扎后找出的最好方法!