Вопрос

Я хочу использовать функцию Haskell следующего типа::string -> string из программы C#.

я хочу использовать hs-дотнет соединить оба мира.Автор утверждает, что это возможно, но не приводит примеров этого случая.Единственные предоставленные образцы — это те, которые используют .NET из Haskell.

Есть ли образец этого использования или как его использовать?(Я использовал .NET-отражатель по мостовой сборке, но я ничего не понял.)

Это было полезно?

Решение

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

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

Вторая часть — это использование LoadLibrary в C#.Причина П/вызов Не могу найти свой экспорт, это довольно просто:в вашем коде 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);

тоже должно работать, но это не так, поскольку 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

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

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

  • используйте char* в коде C# вместо String при вызове функции Haskell.Затем у вас есть указатель на освобождение, когда вы вызываете возвраты, или инициализируете указатель, используя зафиксированный.
  • Импортировать CoTaskMemFree в Haskell и освободите указатель в Haskell
  • используйте StringBuilder вместо String.Я не совсем уверен в этом, но идея состоит в том, что, поскольку StringBuilder реализован как собственный указатель, Marshaller просто передает этот указатель вашему коду Haskell (который, кстати, также может его обновлять).Когда сборщик мусора выполняется после возврата вызова, StringBuilder должен быть освобожден.

Другие советы

В качестве обновления я решил проблему, создав библиотеку Haskell DLL и соединив таким образом два мира.

Если вы хотите пойти по тому же пути, обязательно используйте ::CoTaskMemAlloc для выделения данных для мира .net.Еще одна ошибка — использование LoadLibrary/GetProcAdress, по какой-то неизвестной причине импорт не работает автоматически так, как должен.Более подробная статья чтобы помочь вызвать Haskell из .net.

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

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

Если вам нужен Haskell на .NET, просто используйте Ф#.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top