使用std :: string为PInvoke定制Marshaler

时间:2013-06-14 01:04:58

标签: c# c++ c++-cli pinvoke

免责声明: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输入并将其转换为本机{{1} }}。我的std::string实施是在黑暗中刺伤。我尝试过一些东西,但这是我最好的猜测:

MarshalManagedToNative

不幸的是,当我尝试运行此操作时,PInvoke调用会触发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; }

我做错了什么,或者这整个冒险都是错误的?


首次编辑,完成列表

1。 C#Console App

AccessViolationException

2。 Native C ++ DLL项目“native_cpp”

native_cpp.h

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

native_cpp.cpp

#ifdef NATIVE_CPP_EXPORTS
#define NATIVE_CPP_API __declspec(dllexport)
#else
#define NATIVE_CPP_API __declspec(dllimport)
#endif

#include <string>

NATIVE_CPP_API void hello_std(std::string inp, char* buffer);

3。 C ++ / CLI项目“clr_wrapper”

clr_wrapper.h

#include "native_cpp.h"

void hello_std(std::string inp, char* buffer)
{
    const char* data = inp.data();
    strcpy(buffer, data);
}

clr_wrapper.cpp

#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;
    };
}

结束优先编辑

1 个答案:

答案 0 :(得分:1)

如果您可以使用完全相同的编译器版本,打包,类成员对齐,调用约定,CRT链接,库选项(如_ITERATOR_DEBUG_LEVEL,调试/发布配置等)与本机DLL构建C ++ / CLI DLL,那么您可以{ {3}}。像这样的包装函数可能有效:

public ref class Wrapper
{
    void hello_std_managed(String^ inp, array<Byte>^ buffer)
    {
        IntPtr inpCopy = Marshal::StringToHGlobalAnsi(inp);
        std::string inpNative(static_cast<const char*>(inpCopy.ToPointer()));
        pin_ptr<BYTE> nativeBuffer = &buffer[0];
        hello_std(inpNative,nativeBuffer);
        Marshal::FreeHGlobal(inpCopy);
    }
}

但是,由于这是一个很大的IF,您可能想要让DLL的作者将方法的签名更改为原始C / COM类型,如char *或BSTR。实际上这种方式更好,无论语言或构建配置如何,DLL现在都可以使用。