虽然您的方法可行,但值得注意的是,不幸的是您遇到的困难是您自己造成的(而不是 GHC 中的错误):((以下假设您在构建 DLL 时使用了 GHC 文档并拥有RTS 正在加载 DLL 主文件)。
对于第一部分,您提出的内存分配问题,有一种更简单的 C# 原生方式来处理这个问题,即不安全的代码。在不安全代码中分配的任何内存都将在托管堆之外分配。所以这将否定 C 技巧的需要。
第二部分是C#中LoadLibrary的使用。 P/Invoke 找不到您的导出的原因很简单:在您的 Haskell 代码中,您使用 ccall 声明了导出语句,而在 .NET 中,标准命名约定是 stdcall,这也是 @987654324 的标准@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);
几乎正确。
正确的是例如
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public unsafe static extern char* myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
不再需要 loadLibrary 等。要获取托管字符串,只需使用
String result = new String(myExportedFunction("hello"));
例如。
有人会这样认为
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
[return : MarshalAs(UnmanagedType.LPWStr)]
public static extern string myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
应该也可以,但它不会,因为 Marshaller 期望字符串已经被 CoTaskMemAlloc 分配,并且会在其上调用 CoTaskMemFree,然后崩溃。
如果你想完全留在管理的土地上,你总是可以这样做
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
然后就可以当做
string result = Marshal.PtrToStringUni(myExportedFunction("hello"));
工具在此处可用http://hackage.haskell.org/package/Hs2lib-0.4.8
更新:我最近发现了一个大问题。我们必须记住,.NET 中的 String 类型是不可变的。因此,当编组器将其发送到 Haskell 代码时,我们得到的 CWString 是原始的副本。我们必须释放它。在 C# 中执行 GC 时,它不会影响 CWString,它是一个副本。
然而问题是当我们在 Haskell 代码中释放它时,我们不能使用 freeCWString。指针未使用 C (msvcrt.dll) 的 alloc 分配。有三种方法(据我所知)可以解决这个问题。
- 在调用 Haskell 函数时,在 C# 代码中使用 char* 而不是 String。然后,当您调用 return 时,您将获得指向 free 的指针,或者使用 fixed 初始化指针。
- 在 Haskell 中导入 CoTaskMemFree 并在 Haskell 中释放指针
- 使用 StringBuilder 代替 String。我不完全确定这一点,但想法是,由于 StringBuilder 是作为本机指针实现的,Marshaller 只是将此指针传递给您的 Haskell 代码(顺便说一句,它也可以更新它)。调用返回后执行 GC 时,应释放 StringBuilder。