在.NET中调用Haskell函数

Rao*_*ter 14 .net interop haskell ffi

我想使用具有以下类型的Haskell函数:: string -> string来自C#程序.

我想用hs-dotnet来桥接两个世界.作者声称这是可能的,但没有提供此案例的样本.提供的唯一示例是使用Haskell的.NET的示例.

是否有此用途的样本,或如何使用它?(我在桥接组件上使用了.NET Reflector,但我不明白.)

Phy*_*hyx 14

虽然你的方式有效,值得注意的是,你遇到的困难是你自己做的不幸(而不是GHC中的错误):((以下假设您在构建DLL时使用GHC文档并在DLL main中加载RTS).

对于第一部分,您提出的内存分配问题,有一种更简单的C#本机处理方式,这是不安全的代码.在不安全代码中分配的任何内存都将在托管堆外部分配.所以这将否定对C技巧的需求.

第二部分是在C#中使用LoadLibrary.究其原因的P/Invoke无法找到出口很简单:在你的Haskell代码使用你声明的导出语句ccall,而在.NET中的标准命名约定是stdcall,这也是标准的Win32 API调用.

stdcall并且ccall在参数清理方面具有不同的名称和重复性.

特别是,GHC/GCC将导出"wEval",而.NET默认会查找"_wEval @ 4".现在这很容易修复,只需添加CallingConvention = CallingConvention.Cdecl.

但是使用这个调用约定,调用者需要清理堆栈.所以你需要额外的工作.现在假设您只在Windows上使用它,只需将您的Haskell函数导出为stdcall.这使您的.NET代码更简单

[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern string myExportedFunction(string in);
Run Code Online (Sandbox Code Playgroud)

几乎正确.

例如,正确的是什么

[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public unsafe static extern char* myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
Run Code Online (Sandbox Code Playgroud)

不再需要loadLibrary等.并获得一个托管字符串只是使用

String result = new String(myExportedFunction("hello"));
Run Code Online (Sandbox Code Playgroud)

例如.

人们会这么认为

[DllImport("foo.dll", CharSet = CharSet.Unicode)]
[return : MarshalAs(UnmanagedType.LPWStr)]
public static extern string myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
Run Code Online (Sandbox Code Playgroud)

也应该工作,但它没有,因为Marshaller期望String已经分配了CoTaskMemAlloc并将在其上调用CoTaskMemFree并崩溃.

如果你想完全留在管理的土地上,你总能做到

[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
Run Code Online (Sandbox Code Playgroud)

然后它可以用作

string result = Marshal.PtrToStringUni(myExportedFunction("hello"));
Run Code Online (Sandbox Code Playgroud)

工具可在此处获取http://hackage.haskell.org/package/Hs2lib-0.4.8

更新:我最近发现了一些大问题.我们必须记住,.NET中的String类型是不可变的.所以当marshaller将它发送给Haskell代码时,我们得到的CWString是原始的副本.我们必须释放这个.在C#中执行GC时,它不会影响CWString,这是一个副本.

但问题是,当我们在Haskell代码中释放它时,我们不能使用freeCWString.指针未分配C(msvcrt.dll)的alloc.有三种方法(我知道)可以解决这个问题.

  • 在调用Haskell函数时,在C#代码中使用char*而不是String.然后,当您调用return时,指针将自由,或者使用fixed指定初始化指针.
  • 在Haskell中导入CoTaskMemFree并释放Haskell中的指针
  • 使用StringBuilder而不是String.我不完全确定这个,但是我的想法是,因为StringBuilder是作为本机指针实现的,所以Marshaller只是将这个指针传递给你的Haskell代码(也可以通过btw更新它).在调用返回后执行GC时,应释放StringBuilder.