Ответ 1
В то время как ваш путь работает, стоит отметить, что ваши плоды, к которым вы столкнулись, к сожалению (а не ошибка в GHC): ((Предполагается, что вы использовали документацию GHC при создании библиотеки DLL и загрузили RTS в основной DLL).
В первой части проблемы с распределением памяти, которые вы представляете, существует гораздо более простой способ обработки С#, что является небезопасным кодом. Любая память, выделенная в небезопасном коде, будет выделена вне управляемой кучи. Таким образом, это отрицает необходимость использования трюков C.
Вторая часть - это использование LoadLibrary в С#. Причина P/Invoke не может найти ваш экспорт довольно просто: в вашем коде Haskell вы объявили инструкцию экспорта с помощью ccall
, в то время как в .NET стандартное соглашение об именах stdcall
, которое также является стандартом для вызовов Win32.
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);
тоже должен работать, но это не так, поскольку Маршаллер ожидает, что String будет выделен 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 выполняется в С#, это не повлияет на CWString, которая является копией.
Однако проблема заключается в том, что когда мы освобождаем ее в коде Haskell, мы не можем использовать freeCWString. Указатель не был выделен с помощью C (msvcrt.dll) alloc. Есть три способа (которые я знаю), чтобы решить эту проблему.
- используйте char * в коде С# вместо String при вызове функции Haskell. Затем у вас есть указатель на свободный, когда вы вызываете return, или инициализируете указатель, используя fixed.
- импортировать CoTaskMemFree в Haskell и освободить указатель в Haskell
- используйте StringBuilder вместо String. Я не совсем уверен в этом, но идея в том, что, поскольку StringBuilder реализован как собственный указатель, Marshaller просто передает этот указатель на ваш код Haskell (который также может его обновить). Когда GC выполняется после возврата вызова, StringBuilder должен быть освобожден.