【问题标题】:Custom Marshaler for PInvoke with std::string带有 std::string 的 PInvoke 的自定义编组器
【发布时间】:2013-06-10 13:53:44
【问题描述】:

免责声明:C++/CLI 新手问题

我正在尝试在签名中有 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# 控制台应用程序

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 <string>

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 <string>

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

结束首次编辑

【问题讨论】:

  • 不是一个真正的 C# 人,但看起来你正在将一个指向字符串的指针传递给一个函数,该函数按值接受 std::string。另外,C# 中的 std:string 可能与 C++ 中的 std::string 布局不同。
  • 这是一项英勇的努力,没有任何错误。但是与 std::string 和朋友的互操作是出了名的困难,只有当您的 C++/CLI 程序集和您的 C++ DLL 使用完全相同的共享 CRT 版本时,它才能工作。这里没有足够的代码来自己尝试。
  • 你能修改 C++ 代码还是给你的?根据@Hans Passant 的评论,使用字符数组可能更容易?
  • 第一步:拍摄DLL的作者
  • @HansPassant 谢谢 - 我已经用完整的代码清单更新了我的问题。

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


【解决方案1】:

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

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 现在都可以使用。

【讨论】:

  • 感谢您的回答和有用的链接。这种方法需要针对本机库的头文件或其 .lib 静态库进行构建,不是吗?对于我的任务,这将是我的后备,这是一个很好的解决方案。但我的偏好是直接通过 PInvoke 使用 DLL,而我的问题是,真的可以这样做吗?
  • 如果您将此代码包装在 2 个自定义编组器中,则可以通过显式 pinvoke (每个参数一个类,第一个负责字符串的往返,第二个负责字节数组的往返),但是当编译器被设计为为您执行隐式 pinvoke 时,为什么还要麻烦做所有这些呢?
  • 对于您的问题 - 这是构建复杂性 + 构建时间的问题。使用我的 C# 项目中的显式 Pinvoke,我所要做的就是将 DLL 放入项目输出中。使用隐式 Pinvoke,我必须添加另一个基于原生 C++ 源代码构建的包装 C++/CLI 项目,以及它的所有依赖项。
  • 原始的原生 dll 无法通过 pinvoke 使用,你需要一个 C++/CLI 桥接器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-08
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 1970-01-01
相关资源
最近更新 更多