【问题标题】:Convert unmanaged C++ pointer to an object to a managed C# object将指向对象的非托管 C++ 指针转换为托管 C# 对象
【发布时间】:2017-08-16 01:57:48
【问题描述】:

我有一个用 C++ 编写的非托管静态库 (.dll):

// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "program.h"

struct MyData
{
    int32_t index;
    char* name;
    //uint8_t* data;
};

extern "C" {
    __declspec(dllexport) MyData* GetMyData()
    {
        MyData* ms = new MyData();
        ms->index = 5;
        ms->name = "Happy string";
        //ms->data = new uint8_t[5] { 4, 8, 16, 32, 64 };
        return ms;
    }
}

“GetMyData”方法返回指向“MyData”对象的指针。

我使用“PInvoke”将此库导入 C# 项目并调用了“GetMyData”方法。

// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
public class MyData
{
    [FieldOffset(0)]
    public Int32 index;

    [FieldOffset(4)]
    public String name;

    //[FieldOffset(8)]
    //public Byte[] data;
};

class Program
{
    [DllImport("TestCpp.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr GetMyData();

    public static void Main(string[] args)
    {
        // Call 'GetMyData' method and get structure from pointer using marshaling.
        var ptr = GetMyData();
        var ms = Marshal.PtrToStructure<MyData>(ptr);

        // Print my data into console
        var str = ms.index.ToString();
        str += ", " + ms.name;
        //str += ", [" + string.Join(", ", ms.data) + "]";
        Console.WriteLine(str);
        Console.ReadKey();
    }
}

此代码工作正常,但如果我取消注释使用“MyData”类型的“数据”成员(在 C++ 和 C# 代码中),我将在这一行的 C# 代码中得到异常:

var ms = Marshal.PtrToStructure(ptr);

错误: System.Runtime.InteropServices.SafeArrayTypeMismatchException:
'数组的运行时类型与 元数据中记录的子类型。'

据我了解,'FieldOffset' 属性中的偏移量参数 - 在将非托管 C++ 对象转换为托管 C# 对象期间,非托管内存中的字节数发生了变化。

字段“索引”的大小为 4 字节,因为它是 32 位类型。

字段 'name' 是指向 char 数组的指针。对于 32 位架构,它也是 32 位数字(4 字节)。

“数据”字段需要使用“FieldOffset”属性中的哪个偏移量?

【问题讨论】:

  • 由于我的项目实现的特殊性,没有办法使用C++/CLI。
  • 您从字符串名称中得到错误。 c++ 中的字符串实际上是 c# 中的 byte[],它是指向数组的指针。所以使用 Intptr 名称。
  • 将名称固定为 IntPtr 后,您不再需要 FieldOffset。

标签: c# c++ .net pinvoke marshalling


【解决方案1】:

你不能轻易做到...正如 Ðаn 建议的那样手动或

[FieldOffset(8)]
public IntPtr _data;

public byte[] GetData()
{
    // YOU MUST FREE _data C-side! You can't use
    // C++ delete C#-side
    var bytes = new byte[5];
    Marshal.Copy(_data, bytes, 0, bytes.Length);
    return bytes;
}

这里还有另一个(小)问题:我反对使用LayoutKind.Explicit,除非你真的需要它。从LayoutKind.Sequential 开始,看看是否足够。使用LayoutKind.Sequential,您将更容易从 32 位切换到 64 位,因为结构将根据指针的大小进行拉伸。

【讨论】:

  • 谢谢,它对我有用,但必须将参数 'unmanagedType' 从 'UnmanagedType.LPArray' 替换为 'UnmanagedType.ByValArray' 才能正常工作。使用 'UnmanagedType.LPArray' 我得到异常。
  • @IvanKishchenko 不,它不适用于 ByValArray。不接受回复,所以我可以删除它。
  • 我不接受你所要求的回复,但它对我来说很好。
  • @IvanKishchenko 它可以是随机的... ByValArray 适用于当您拥有struct MyStruct { uint8_t bytes[5]; }
  • @Ðаn 很少在您的解决方案中引入另一个项目,以您不知道的语法 (C++/CLI) 将使事情变得“更容易”。虽然您可能非常喜欢 C++/CLI,但您可能是地球上少数喜欢它的人之一 :-)(否则您会在 SO 上找到更多关于它的问题)。我认为 C++/CLI 是“核选项”,而不是 pinvoke 问题的第一道防线。
猜你喜欢
  • 2011-07-12
  • 2011-10-03
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多