使用std :: string为PInvoke定制Marshaler
免责声明:C ++ / CLI Noob问题
我正在尝试在签名中具有std :: string的C ++ DLL上使用PInvoke。 目前我只是测试:我的目标是将字符串传递给本机DLL,然后返回它。
本机导出如下所示:
#define NATIVE_CPP_API __declspec(dllexport) NATIVE_CPP_API void hello_std(std::string inp, char* buffer) { const char* data = inp.data(); strcpy(buffer, data); }
我正在尝试使用自定义封送器以正常方式对其进行PInvoke:
[DllImport("native_cpp.dll", EntryPoint = "?hello_std@@YAPADV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z", CallingConvention = CallingConvention.Cdecl)] private static extern void hello_std( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(clr_wrapper.string_marshaler))] String inp, StringBuilder buffer); static void Main(string[] args) { var buffer = new StringBuilder(100); hello_std("abcdefg", buffer); Console.WriteLine(buffer); Console.ReadLine(); }
此处指定的自定义封送程序clr_wrapper.string_marshaler
是C ++ / CLI项目中的ICustomMarshaler
,用于获取System::String
输入并将其转换为本机std::string
。 我的MarshalManagedToNative
实施是在黑暗中刺伤。 我尝试了一些东西,但这是我最好的猜测:
IntPtr string_marshaler::MarshalManagedToNative( Object^ ManagedObj ) { String^ val = (String^) ManagedObj; size_t size = (size_t)val->Length; char* ptr = (char*) Marshal::StringToHGlobalAnsi(val->ToString()).ToPointer(); std::string * str = new std::string(ptr, size); IntPtr retval = (IntPtr) str; return retval; }
不幸的是,当我尝试运行它时,PInvoke调用会触发AccessViolationException
。
我做错了什么,或者整个风险投资是否构思错误?
首先编辑,完成清单
1. C#Console应用程序
class Program { static void Main(string[] args) { var buffer = new StringBuilder(100); hello_std("abcdefg", buffer); Console.WriteLine(buffer); Console.ReadLine(); } [DllImport("native_cpp.dll", EntryPoint = "?hello_std@@YAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PAD@Z", CallingConvention = CallingConvention.Cdecl)] private static extern void hello_std( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(clr_wrapper.string_marshaler))] [In] String inp, StringBuilder buffer ); }
2.原生C ++ DLL项目“native_cpp”
native_cpp.h
#ifdef NATIVE_CPP_EXPORTS #define NATIVE_CPP_API __declspec(dllexport) #else #define NATIVE_CPP_API __declspec(dllimport) #endif #include NATIVE_CPP_API void hello_std(std::string inp, char* buffer);
native_cpp.cpp
#include "native_cpp.h" void hello_std(std::string inp, char* buffer) { const char* data = inp.data(); strcpy(buffer, data); }
3. C ++ / CLI项目“clr_wrapper”
clr_wrapper.h
#pragma once using namespace System; using namespace System::Runtime::InteropServices; namespace clr_wrapper { public ref class string_marshaler : public ICustomMarshaler { public: string_marshaler(void); virtual Object^ MarshalNativeToManaged( IntPtr pNativeData ); virtual IntPtr MarshalManagedToNative( Object^ ManagedObj ); virtual void CleanUpNativeData( IntPtr pNativeData ); virtual void CleanUpManagedData( Object^ ManagedObj ); virtual int GetNativeDataSize(); static ICustomMarshaler ^ GetInstance(String ^ pstrCookie) { return gcnew string_marshaler(); } private: void* m_ptr; int m_size; }; }
clr_wrapper.cpp
#include "clr_wrapper.h" #include using namespace clr_wrapper; using namespace System::Text; string_marshaler::string_marshaler(void) { } Object^ string_marshaler::MarshalNativeToManaged( IntPtr pNativeData ) { return Marshal::PtrToStringAnsi(pNativeData); } IntPtr string_marshaler::MarshalManagedToNative( Object^ ManagedObj ) { String^ val = (String^) ManagedObj; size_t size = (size_t) val->Length; char* ptr = (char*) Marshal::StringToHGlobalAnsi(val->ToString()).ToPointer(); std::string * str = new std::string(ptr, size); m_size = sizeof(str*); m_ptr = (void*) str; IntPtr retval = (IntPtr) str; return retval; } void string_marshaler::CleanUpNativeData( IntPtr pNativeData ) { //Marshal::FreeHGlobal(pNativeData); delete (std::string*) m_ptr; } void string_marshaler::CleanUpManagedData( Object^ ManagedObj ) { } int string_marshaler::GetNativeDataSize() { return m_size; }
结束第一次编辑
如果您可以使用完全相同的编译器版本,打包,类成员对齐,调用约定,CRT链接,库选项(如_ITERATOR_DEBUG_LEVEL,调试/发布配置等)与本机DLL构建C ++ / CLI DLL,则可以传递STL类在DLL边界上 。 像这样的包装函数可能有效:
public ref class Wrapper { void hello_std_managed(String^ inp, array^ buffer) { IntPtr inpCopy = Marshal::StringToHGlobalAnsi(inp); std::string inpNative(static_cast(inpCopy.ToPointer())); pin_ptr nativeBuffer = &buffer[0]; hello_std(inpNative,nativeBuffer); Marshal::FreeHGlobal(inpCopy); } }
但是,由于这是一个很大的IF,您可能需要让DLL的作者将方法的签名更改为原始C / COM类型,如char *或BSTR。 实际上这种方式更好,无论语言或构建配置如何,DLL现在都可以使用。