在 .NET 中调用 Haskell 函数

Posted

技术标签:

【中文标题】在 .NET 中调用 Haskell 函数【英文标题】:Call a Haskell function in .NET 【发布时间】:2010-12-02 10:37:26 【问题描述】:

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

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

是否有这种用法的示例,或者如何使用它? (我在桥接组件上使用了.NET Reflector,但我什么都不懂。)

【问题讨论】:

我不知道 Haskell,所以我不禁好奇在 C# 中实现 Haskell 函数是否更简单。这个函数是不是这么重要这么难,用C#不能重写? 好吧,我有一个用haskell写的完整程序,函数只是一个“简单”的接口,所以不,它不能用C#重写 Haskell 中的 'H' 不是无声的。因此,您应该使用正确的冠词“a”而不是“an”。 【参考方案1】:

虽然您的方法可行,但值得注意的是,不幸的是您遇到的困难是您自己造成的(而不是 GHC 中的错误):((以下假设您在构建 DLL 时使用了 GHC 文档并拥有RTS 正在加载 DLL 主文件)。

对于第一部分,您提出的内存分配问题,有一种更简单的 C# 原生方式来处理这个问题,即不安全的代码。在不安全代码中分配的任何内存都将在托管堆之外分配。所以这将否定 C 技巧的需要。

第二部分是C#中LoadLibrary的使用。 P/Invoke 找不到您的导出的原因很简单:在您的 Haskell 代码中,您使用 ccall 声明了导出语句,而在 .NET 中,标准命名约定是 stdcall,这也是 @987654324 的标准@API 调用。

stdcallccall 在参数清理方面具有不同的名称修饰和职责。

特别是,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。

【讨论】:

谢谢,如果我能以正确的方式做到这一点,那就更好了。我想我一定是被文档淹没了……【参考方案2】:

作为更新,我通过制作一个 haskell DLL 并以这种方式桥接两个世界来解决了这个问题。

如果你想走同样的路,一定要使用::CoTaskMemAlloc为.net世界分配数据。另一个问题是 LoadLibrary/GetProcAdress 的使用,由于某种未知的原因,导入不会按照它们应该的方式自动工作。更多in depth article 帮助从.net 调用haskell。

【讨论】:

【参考方案3】:

您当然至少可以从 C 中调用 Haskell——您在 Haskell 文件中使用“外部导出”,然后 GHC 会生成一个 C 标头,然后您可以将其导入并用于从 C 中调用 Haskell。

我还没有看到为 .NET 绑定做过这件事——所以我认为最好向作者 - Sigbjorn - 和 haskell-cafe@ 询问示例。

【讨论】:

【参考方案4】:

如果您想在 .NET 上使用 Haskell,只需使用 F#。

【讨论】:

以上是关于在 .NET 中调用 Haskell 函数的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 中的单子——洪峰老师讲创客道(三十五)

用sublime text 3编译haskell

如何在纯函数中跳过不必要的 IO?

FFI 可以处理数组吗?如果是这样,怎么做?

安全执行不受信任的 Haskell 代码

工具rest:Haskell的REST开源框架