Вызовите функцию Haskell в.NET

Одна передача.

a = [1,1,'a','b','c','c']

new_list = []
prev = None

while 1:
    try:
        i = a.pop(0)
        if i != prev:
            new_list.append(i)
        prev = i
    except IndexError:
        break
14
задан Peter Mortensen 30 April 2011 в 14:13
поделиться

3 ответа

Хотя ваш способ работает, стоит отметить, что трудности, с которыми вы столкнулись, к сожалению, были вашего собственного изготовления (а не ошибка в GHC) :( (Ниже предполагается, что вы использовали документацию GHC при сборке DLL и у вас RTS загружается в DLL main).

Что касается первой части, проблемы с выделением памяти, которую вы представляете, есть гораздо более простой нативный способ обработки этого в C#, который заключается в небезопасном коде. Любая память, выделенная в небезопасном коде, будет выделена вне управляемой кучи. Таким образом, необходимость в хитрости C отпадает.

Вторая часть - это использование LoadLibrary в C#. Причина, по которой 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);

его почти корректным.

Правильным будет, например,

[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);

тоже должно работать, но это не так, поскольку маршаллер ожидает, что строка была выделена с помощью 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

Обновление : Есть несколько большая загвоздка, которую я недавно обнаружил. Мы должны помнить, что тип String в .NET является неизменяемым. Поэтому, когда маршаллер отправляет его в код Haskell, CWString, который мы получаем там, является копией оригинала. Мы должны освободить ее. Когда GC выполняется в C#, это не влияет на CWString, которая является копией.

Проблема, однако, в том, что когда мы освобождаем его в коде Haskell, мы не можем использовать freeCWString. Указатель не был выделен с помощью alloc в C (msvcrt.dll). Есть три способа (о которых я знаю) решить эту проблему.

  • использовать char* в коде C# вместо String при вызове функции Haskell. Тогда у вас будет указатель, который нужно освободить при вызове return, или инициализировать указатель с помощью fixed.
  • импортируйте CoTaskMemFree в Haskell и освободите указатель в Haskell
  • используйте StringBuilder вместо String. Я не совсем уверен в этом, но идея заключается в том, что поскольку StringBuilder реализован как собственный указатель, Marshaller просто передает этот указатель вашему коду на Haskell (который также может обновлять его, кстати). Когда GC выполняется после возврата вызова, StringBuilder должен быть освобожден.
14
ответ дан 1 December 2019 в 15:22
поделиться

По крайней мере, вы можете вызвать Haskell из C - вы используете "внешний экспорт" в файле Haskell, а GHC генерирует заголовок C, который затем можно импортировать и использовать для вызова Haskell из C.

Я не видел, чтобы это было сделано для привязок .NET, поэтому я думаю, что лучше всего обратиться за примерами как к автору - Sigbjorn -, так и к haskell-cafe @ .

2
ответ дан 1 December 2019 в 15:22
поделиться

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

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.

4
ответ дан 1 December 2019 в 15:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: