在.NET中调用Haskell函数

时间:2022-02-26 17:07:30

I want to use a Haskell function with the following type :: string -> string from a C# program.

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

I want to use hs-dotnet to bridge both worlds. The author claim that it's possible, but provide no sample of this case. The only samples provided are the one to use .NET from Haskell.

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

Is there a sample of this use, or how to use it? (I used .NET Reflector on the bridging assembly, but I didn't understand a thing.)

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

4 个解决方案

#1


14  

While your way works, it's worth noting that the dificulties you encountered were of your own doing unfortunately (and not a bug in GHC) :( (The following assumes you used the GHC documentation when building the DLL and have your RTS loading in DLL main).

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

For the first part, the memory allocation issues you present, there's a much easier C# native way of handling this, which is unsafe code. Any memory allocated in unsafe code will be allocated outside the managed heap. So this would negate the need for C trickery.

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

The second part is the use of the LoadLibrary in C#. The reason P/Invoke can't find your export is quite simple: in your Haskell code you declared the export statement using ccall, while in .NET the standard naming convention is stdcall, which is also the standard for Win32 API calls.

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

stdcall and ccall have different name manglings and resposibilities in term of argument cleanup.

stdcall和ccall在参数清理方面有不同的名称和重复性。

In particular, GHC/GCC will have exported "wEval" while .NET by default would be looking for "_wEval@4". Now that's quite easy to fix, just add CallingConvention = CallingConvention.Cdecl.

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

But using this calling convention the caller needs to clean up the stack. So you would need extra work. Now assuming you're only going to use this on Windows, just export your Haskell function as stdcall. This makes your .NET code simpler and makes

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

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

almost correct.

What's correct would be for example

例如,正确的是什么

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

No more need for loadLibrary or the like. And to get a managed string just use

不再需要loadLibrary等。并且只需使用托管字符串

String result = new String(myExportedFunction("hello"));

for instance.

One would think that

有人会这么想

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

should work too, but It doesn't since the Marshaller expects the String to have been allocated with CoTaskMemAlloc and will call CoTaskMemFree on it and crash.

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

If you want to stay completely in managed land, you could always do

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

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

and then it can be used as

然后它可以用作

string result = Marshal.PtrToStringUni(myExportedFunction("hello"));

Tool is available here http://hackage.haskell.org/package/Hs2lib-0.4.8

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

Update : There's somewhat of a big gotcha that I've recently discovered. We have to remember that the String type in .NET is immutable. So when the marshaller sends it to out Haskell code, the CWString we get there is a copy of the original. We have to free this. When GC is performed in C# it won't affect the the CWString, which is a copy.

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

The problem however is that when we free it in the Haskell code we can't use freeCWString. The pointer was not allocated with C (msvcrt.dll)'s alloc. There are three ways (that I know of) to solve this.

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

  • use char* in your C# code instead of String when calling a Haskell function. You then have the pointer to free when you call returns, or initialize the pointer using fixed.
  • 在调用Haskell函数时,在C#代码中使用char *而不是String。然后,当您调用return时,指针将*,或者使用fixed指定初始化指针。

  • import CoTaskMemFree in Haskell and free the pointer in Haskell
  • 在Haskell中导入CoTaskMemFree并释放Haskell中的指针

  • use StringBuilder instead of String. I'm not entirely sure about this one, but the idea is that since StringBuilder is implemented as a native pointer, the Marshaller just passes this pointer to your Haskell code (which can also update it btw). When GC is performed after the call returns, the StringBuilder should be freed.
  • 使用StringBuilder而不是String。我不完全确定这个,但是我的想法是,因为StringBuilder是作为本机指针实现的,所以Marshaller只是将这个指针传递给你的Haskell代码(也可以通过btw更新它)。在调用返回后执行GC时,应释放StringBuilder。

#2


4  

Just as an update, I've solved the problem by making an haskell DLL and bridging the two worlds that way.

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

If you want to take the same path, be sure to use ::CoTaskMemAlloc to allocate data for the .net world. Another gotcha is the use of LoadLibrary/GetProcAdress, for some unknown reason, imports doesn't work automatically the way they're supposed to be. A more in depth article to help calling haskell from .net.

如果您想采用相同的路径,请务必使用:: CoTaskMemAlloc为.net世界分配数据。另一个问题是LoadLibrary / GetProcAdress的使用,由于某些未知原因,导入不会按照它们应有的方式自动运行。一篇更深入的文章,帮助从.net调用haskell。

#3


2  

You can certainly call Haskell from C at least -- you use "foreign export" in the Haskell file, and GHC generates a C header which you can then import and use to call into Haskell from C.

你当然可以从C调用Haskell - 你在Haskell文件中使用“foreign export”,然后GHC生成一个C头,然后你可以导入并使用它从C调用Haskell。

I've not seen this done for the .NET bindings -- so I think it is best to ask both the author - Sigbjorn - and on haskell-cafe@ for examples.

我没有看到这对.NET绑定做了 - 所以我认为最好同时询问作者--Sigbjorn和haskell-cafe @。

#4


-11  

If you want Haskell on .NET, just use F#.

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

#1


14  

While your way works, it's worth noting that the dificulties you encountered were of your own doing unfortunately (and not a bug in GHC) :( (The following assumes you used the GHC documentation when building the DLL and have your RTS loading in DLL main).

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

For the first part, the memory allocation issues you present, there's a much easier C# native way of handling this, which is unsafe code. Any memory allocated in unsafe code will be allocated outside the managed heap. So this would negate the need for C trickery.

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

The second part is the use of the LoadLibrary in C#. The reason P/Invoke can't find your export is quite simple: in your Haskell code you declared the export statement using ccall, while in .NET the standard naming convention is stdcall, which is also the standard for Win32 API calls.

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

stdcall and ccall have different name manglings and resposibilities in term of argument cleanup.

stdcall和ccall在参数清理方面有不同的名称和重复性。

In particular, GHC/GCC will have exported "wEval" while .NET by default would be looking for "_wEval@4". Now that's quite easy to fix, just add CallingConvention = CallingConvention.Cdecl.

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

But using this calling convention the caller needs to clean up the stack. So you would need extra work. Now assuming you're only going to use this on Windows, just export your Haskell function as stdcall. This makes your .NET code simpler and makes

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

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

almost correct.

What's correct would be for example

例如,正确的是什么

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

No more need for loadLibrary or the like. And to get a managed string just use

不再需要loadLibrary等。并且只需使用托管字符串

String result = new String(myExportedFunction("hello"));

for instance.

One would think that

有人会这么想

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

should work too, but It doesn't since the Marshaller expects the String to have been allocated with CoTaskMemAlloc and will call CoTaskMemFree on it and crash.

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

If you want to stay completely in managed land, you could always do

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

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

and then it can be used as

然后它可以用作

string result = Marshal.PtrToStringUni(myExportedFunction("hello"));

Tool is available here http://hackage.haskell.org/package/Hs2lib-0.4.8

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

Update : There's somewhat of a big gotcha that I've recently discovered. We have to remember that the String type in .NET is immutable. So when the marshaller sends it to out Haskell code, the CWString we get there is a copy of the original. We have to free this. When GC is performed in C# it won't affect the the CWString, which is a copy.

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

The problem however is that when we free it in the Haskell code we can't use freeCWString. The pointer was not allocated with C (msvcrt.dll)'s alloc. There are three ways (that I know of) to solve this.

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

  • use char* in your C# code instead of String when calling a Haskell function. You then have the pointer to free when you call returns, or initialize the pointer using fixed.
  • 在调用Haskell函数时,在C#代码中使用char *而不是String。然后,当您调用return时,指针将*,或者使用fixed指定初始化指针。

  • import CoTaskMemFree in Haskell and free the pointer in Haskell
  • 在Haskell中导入CoTaskMemFree并释放Haskell中的指针

  • use StringBuilder instead of String. I'm not entirely sure about this one, but the idea is that since StringBuilder is implemented as a native pointer, the Marshaller just passes this pointer to your Haskell code (which can also update it btw). When GC is performed after the call returns, the StringBuilder should be freed.
  • 使用StringBuilder而不是String。我不完全确定这个,但是我的想法是,因为StringBuilder是作为本机指针实现的,所以Marshaller只是将这个指针传递给你的Haskell代码(也可以通过btw更新它)。在调用返回后执行GC时,应释放StringBuilder。

#2


4  

Just as an update, I've solved the problem by making an haskell DLL and bridging the two worlds that way.

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

If you want to take the same path, be sure to use ::CoTaskMemAlloc to allocate data for the .net world. Another gotcha is the use of LoadLibrary/GetProcAdress, for some unknown reason, imports doesn't work automatically the way they're supposed to be. A more in depth article to help calling haskell from .net.

如果您想采用相同的路径,请务必使用:: CoTaskMemAlloc为.net世界分配数据。另一个问题是LoadLibrary / GetProcAdress的使用,由于某些未知原因,导入不会按照它们应有的方式自动运行。一篇更深入的文章,帮助从.net调用haskell。

#3


2  

You can certainly call Haskell from C at least -- you use "foreign export" in the Haskell file, and GHC generates a C header which you can then import and use to call into Haskell from C.

你当然可以从C调用Haskell - 你在Haskell文件中使用“foreign export”,然后GHC生成一个C头,然后你可以导入并使用它从C调用Haskell。

I've not seen this done for the .NET bindings -- so I think it is best to ask both the author - Sigbjorn - and on haskell-cafe@ for examples.

我没有看到这对.NET绑定做了 - 所以我认为最好同时询问作者--Sigbjorn和haskell-cafe @。

#4


-11  

If you want Haskell on .NET, just use F#.

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