从C#向非托管的C++代码传递一个指向类的指针

6

我有一个在dll中的C++导出函数:

int MyMethod(ulong pid, MyStruct* struct);

MyStruct类描述如下:

class MyStruct
{
public:
uchar   nVersion;
uchar   nModuleType;
uchar   nMachine64;
uchar   nReserved;
ulong  data1;
ulong  data2;
ulong  data3;
};

我将尝试将这个函数导入到我的C#代码中,代码如下:

[DllImport("mydll.dll", EntryPoint = "#24")]
private static extern int _MyMethod(long pid, ref MyStruct struct);

C#中的类:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
stuct MyStruct
{
    public byte nVersion;
    public byte nModuleType;
    public byte nMachine64;
    public byte nReserved;
    public ulong data1;
    public ulong data2;
    public ulong data3;
}

我遇到了System.AccessViolationException异常:

MyStruct struct = new MyStruct();
_MyMethod(4728, ref struct);

有什么问题吗?

更新: System.Runtime.InteropServices.Marshal.SizeOf(struct)返回32。为什么?我原以为应该是4 * 1 + 8 * 3 = 28。


它与“unsafe”关键字无关,只是一个标准的marshal问题... - user287107
@user287107,你说得对,我把问题的背景脱离了回答。 - iMortalitySX
顺便说一下,我曾经编写了相同的代码用于 Delphi 调用 C++ DLL - 并且它可以正常工作。因此,DLL 中不可能存在问题。 - Denis Kildishev
1
大小不同是因为填充。编译器会将结构体大小增加到方便的数字。 - Dan
C++ 会做同样的事情吗?我想也许这就是问题所在。 - Denis Kildishev
正如其他人所说,改变其中一个端点的ulong类型,使它们都具有相同的大小。然后检查两端的sizeof()并确保它们相同 - 如果它们不同,则需要调整结构的打包方式。 - Will Dean
2个回答

1
在C#中,我们有classstruct。所有class类型都是引用类型,而struct类型是值类型。这意味着当你有像class MyStruct这样的东西,并且你写MyStruct s时,它实际上是一个指向基类的指针,当你通过引用传递它时,你实际上传递的是该指针的地址,因此它与期望指向主struct的C++没有任何关系。根据这个解决方案,你的问题是将class转换为struct
在C#中,longulong是64位类型,而在C++(至少是MSVC)中它们是32位的,因此当你声明函数的第一个参数为long时,你发送了额外的32位值,可能会覆盖下一个参数并导致它无效:
Stack:
    32 bit: [first 32 bit of the first parameter]
    32 bit: [second 32 bit of the first parameter]
    32 bit: [address of your structure]

因此,当函数被调用时,它将把一个无效的参数作为结构体地址。所以只需更改您的函数定义为:

[DllImport("mydll.dll", EntryPoint = "#24")]
private static extern int _MyMethod(int pid, ref MyStruct struct);

将您的结构体修改为:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
stuct MyStruct
{
    public byte nVersion;
    public byte nModuleType;
    public byte nMachine64;
    public byte nReserved;
    public uint data1;
    public uint data2;
    public uint data3;
}

可能你的错误源于函数的第一个参数,因为该函数期望一个32位的值,而你提供了一个64位的值,实际上你提供了两个32位的值给这个函数,导致了函数的错误。

@DenisKildishev 另外需要注意的是,在C#中ulong对应CLR中的UInt64,但在C++中ulong对应CLR中的UInt32或者C#中的uint。当然,这并不是你代码中AccessViolationException的原因。 - BigBoss
谢谢!我不知道那个。_MyMethod的参数中的long当然是关键的。 - Denis Kildishev

1

SizeOf()返回32,因为ulong是一个8字节的值类型,需要对齐要求。StructLayoutAttribute.Pack的默认值为8,与本机代码中使用的默认打包相同。因此,data1被对齐到偏移量8,并且在nReserved和data1之间有4个字节的间隙。所以4 x 1 + 4 + 3 x 8 = 32。

您可能因为在使用MSVC编译器编译的本机C++代码中,ulong是4字节而得到了该间隙。在C#中,uint与之相同。因此,请修复结构声明,将ulong替换为uint。

下一个问题,AV的原因是您在C#中将结构声明为类。这是一个引用类型,总是通过引用传递。您当前的声明等同于C++代码中的MyStruct**。请从声明中删除ref或将其声明为struct而不是class。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接