将指向对象的非托管 C++ 指针转换为托管 C# 对象
Convert unmanaged C++ pointer to an object to a managed C# object
我有一个用 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' 方法 returns 指向 'MyData' 对象的指针。
我使用 'PInvoke' 将这个库导入到 C# projeсt 并调用了 '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' 类型的 'data' 成员(在 C++ 和 C# 代码中),我将在 C# 代码中的这一行出现异常:
var ms = Marshal.PtrToStructure(ptr);
Error:
System.Runtime.InteropServices.SafeArrayTypeMismatchException:
'Mismatch has occurred between the runtime type of the array and the
sub type recorded in the metadata.'
据我了解 'FieldOffset' 属性中的偏移量参数 - 这是在将非托管 C++ 对象转换为托管 C# 对象期间非托管内存中的字节偏移。
字段 'index' 有 4 个字节大小,因为它是 32 位类型。
字段'name' 是指向字符数组的指针。对于 32 位架构,它也是 32 位数字(4 个字节)。
我需要为 'data' 字段使用 'FieldOffset' 属性中的哪个偏移量?
你做起来并不容易...正如 Ðа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 位,因为该结构将根据指针的大小进行拉伸。
我有一个用 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' 方法 returns 指向 'MyData' 对象的指针。
我使用 'PInvoke' 将这个库导入到 C# projeсt 并调用了 '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' 类型的 'data' 成员(在 C++ 和 C# 代码中),我将在 C# 代码中的这一行出现异常:
var ms = Marshal.PtrToStructure(ptr);
Error: System.Runtime.InteropServices.SafeArrayTypeMismatchException:
'Mismatch has occurred between the runtime type of the array and the sub type recorded in the metadata.'
据我了解 'FieldOffset' 属性中的偏移量参数 - 这是在将非托管 C++ 对象转换为托管 C# 对象期间非托管内存中的字节偏移。
字段 'index' 有 4 个字节大小,因为它是 32 位类型。
字段'name' 是指向字符数组的指针。对于 32 位架构,它也是 32 位数字(4 个字节)。
我需要为 'data' 字段使用 'FieldOffset' 属性中的哪个偏移量?
你做起来并不容易...正如 Ðа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 位,因为该结构将根据指针的大小进行拉伸。