Wrap native DLL для C#
Я написал C++ DLL, и теперь мне нужно вызвать собственную функцию из управляемого приложения.
Экспортированная собственная функция выглядит следующим образом:
extern "C" __declspec(dllexport)
bool NativeMethod(char *param1, char *param2, char *result);
Итак, из C# я вызову эту функцию, передавая 2 входных парама, 1 выходной парам, и, очевидно, я прочитаю возвращаемое значение bool.
Я пытался обернуть все это разными способами, но всегда получал исключениеPInvokeStackImbalance
.
Единственный известный мне способ вызвать собственную функцию-это применить CallingConvention = CallingConvention.Cdecl
) к объявлению функции .NET. Однако в этом смысле я не такой возможность считывать выходные параметры (это всегда пустая строка), а также возвращаемое значение всегда true.5 ответов:
Во-первых, я бы настроил прототип вашей родной функции.
Поскольку эта функция имеет интерфейсC , вы должны использовать тип C для булевых значений, а не Тип C++, какbool
. Вы можете использовать тип Win32BOOL
.Более того, как это происходит в настоящее время, ваша функция склонна к переполнению буфера: лучше добавить другой параметр, чтобы указать максимальный размер строкового буфера назначения
result
.Отметим также, что широко распространенный вызов соглашение для DLL, экспортирующих чистые функции интерфейса C (как и множество функций Win32 API), является
__stdcall
(не__cdecl
). Я бы тоже этим воспользовался.Последнее, так как первые два параметра являютсявходными строками, вы можете использовать
const
, чтобы сделать его понятным и обеспечить const-корректность.Итак, я бы сделал прототип экспортируемой нативной функции следующим образом:
extern "C" __declspec(dllexport) BOOL __stdcall NativeFunction( const char *in1, const char *in2, char *result, int resultMaxSize);
Затем, на стороне C#, вы можете использовать следующий P / Invoke:
[DllImport( "NativeDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool NativeFunction( string in1, string in2, StringBuilder result, int resultMaxSize);
Обратите внимание, что для используется строка вывода A
StringBuilder
.Обратите также внимание, что
CharSet = CharSet.Ansi
используется для маршалинга строк Unicode UTF-16 C#в ANSI (обратите внимание, что преобразование является с потерями - Если вы хотите преобразование без потерь, просто используйте строкиwchar_t*
на стороне C++).Я сделал тест с простой c++ native DLL:
// NativeDll.cpp #include <string.h> #include <windows.h> extern "C" __declspec(dllexport) BOOL __stdcall NativeFunction( const char *in1, const char *in2, char *result, int resultMaxSize) { // Parameter check if (in1 == nullptr || in2 == nullptr || result == nullptr || resultMaxSize <= 0) return FALSE; // result = in1 + in2 strcpy_s(result, resultMaxSize, in1); strcat_s(result, resultMaxSize, in2); // All right return TRUE; }
И он успешно вызывается следующим кодом консольного приложения C#:
using System; using System.Runtime.InteropServices; using System.Text; namespace CSharpClient { class Program { [DllImport( "NativeDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool NativeFunction( string in1, string in2, StringBuilder result, int resultMaxSize); static void Main(string[] args) { var result = new StringBuilder(200); if (! NativeFunction("Hello", " world!", result, result.Capacity)) { Console.WriteLine("Error."); return; } Console.WriteLine(result.ToString()); } } }
Вы избавите себя от многих головных болей P/Invoke, если вместо этого будете использовать COM-взаимодействие. Поместите метод в COM-интерфейс и измените сигнатуру, чтобы следовать соглашениям COM:
interface ISomeInterface : IUnknown { HRESULT NativeMethod([in] BSTR bstrParam1, [in] BSTR bstrParam2, [out] BSTR* pbstrParam3, [out, retval] VARIANT_BOOL* pvbResult); }
Я изменил char* на BSTR и bool на VARIANT_BOOL, потому что это типы, используемые COM для строк и bools соответственно. Кроме того, все COM-методы должны возвращать значение HRESULT. Если вы хотите получить "фактическое" возвращаемое значение, вы должны добавить его как последнее out параметр, а также пометить его атрибутомretval .
Затем добавьте ссылку на COM-компонент из проекта C#, и вы получите интуитивно понятную сигнатуру C# без необходимости угадывать, как сопоставить типы C++ с типами C#:
bool NativeMethod(string bstrParam1, string bstrParam2, out string pbstrParam3)
(Вот как это выглядит в Обозревателе объектов.)
Почему обратите внимание на использование маршалинга кода .Net с помощью DLLImport, например, следующее
[DllImport(@"C:\TestLib.dll")] public static extern void ProtectDocument( out [MarshalAs(UnmanagedType.LPStr)]string validToDate);
И затем вы можете вызвать функцию как локальную функцию следующим образом
string x=string.empty; ProtectDocument(out x);
[DllImport("MyDll.dll", EntryPoint = "NativeMethod", CallingConvention = CallingConvention.Cdecl)] static extern bool NativeMethod( [MarshalAs(UnmanagedType.LPStr)]string param1, [MarshalAs(UnmanagedType.LPStr)]string param2, [MarshalAs(UnmanagedType.LPStr)]string param3);
Замените
LPStr
наLPWStr
, Если вы работаете с широкими символами.
[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")] public static unsafe extern bool NativeMethod( [MarshalAs(UnmanagedType.LPStr)] string param1, [MarshalAs(UnmanagedType.LPStr)] string param2, [MarshalAs(UnmanagedType.LPStr)] char *param3);
Выходной параметр должен быть
char *
, так как строки C# неизменяемы. Вы вызываете метод (в небезопасном контексте) следующим образом:char[] output = new char[100]; fixed (char *param = &output[0]) { NativeMethod("blahblah", "blahblah", param); }
Если выходной параметр не является строкой, а только одним символом, в этом случае вы можете просто сделать следующее:
[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")] public static unsafe extern bool NativeMethod( [MarshalAs(UnmanagedType.LPStr)] string param1, [MarshalAs(UnmanagedType.LPStr)] string param2, out char param3);
И вы можете просто использовать его следующим образом:
char output; NativeMethod("blahblah", "blahblah", out output);