我正在尝试从我的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的过程中,运行时链接程序将找到此符号。
到目前为止,我已经尝试了这些步骤,但是没有一个帮助:
在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
-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”库?
有人可以帮我吗?预先感谢您耐心阅读这个较长的问题!
答案 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_hs
和gendef
,它们都位于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#方面还有更多的东西:正确地编组参数等,而且我还必须处理那些参数才能使结果正确。我可以覆盖所有这些棘手问题的唯一地方是在博客中:-)
请随时对我的解决方案进行交叉验证,并评论任何更好的方法。这是我挣扎后找出的最好方法!