pinvokestack失衡不是由调用约定引起的
本文关键字:约定 调用 pinvokestack | 更新日期: 2023-09-27 18:05:26
我不知道这里有什么问题。
我有大量的p/invoke调用工作没有事故…除了这个
我已经设法将我的问题简化为以下示例代码。
如果我删除任何一个struct成员(无论是double型还是int型),它都可以正常工作。
我假设这个问题在某种程度上与结构体的布局有关-但是当我在C中执行sizeof()和在c#中执行Marshal.SizeOf()时,它们都返回相同的值…那么如果c#和C中的结构体大小相同,问题会是什么呢?
我显然遗漏了一些基本的东西。
SampleDLLCode.c
#pragma pack(1)
typedef struct SampleStruct {
double structValueOne;
int structValueTwo;
} SampleStruct;
__declspec(dllexport) SampleStruct __cdecl SampleMethod(void);
SampleStruct SampleMethod(void) {
return (SampleStruct) { 1, 2 };
}
构建脚本
gcc -std=c99 -pedantic -O0 -c -o SampleDLLCode.o SampleDLLCode.c
gcc -shared --out-implib -o SampleDLL.dll SampleDLLCode.o
c#代码
using System;
using System.Runtime.InteropServices;
namespace SampleApplication
{
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct SampleStruct {
public double structValueOne;
public int structValueTwo;
}
class Program
{
[DllImport("SampleDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern SampleStruct SampleMethod();
static void Main(string[] args)
{
SampleStruct sample = SampleMethod();
}
}
}
首先让我祝贺你提了一个非常好的问题。这是一件令人高兴的事,这一次,收到了重现这个问题所需的所有代码。
问题是由于gcc和Microsoft工具使用的函数返回值的abi略有不同。对于可以装入寄存器的返回值,例如int
返回值没有区别。但是,由于您的结构太大而无法容纳在单个寄存器中,并且在这种情况下api之间存在差异。
对于较大的返回值,调用者传递一个指向函数的隐藏指针。这个隐藏指针由调用者压入堆栈。该函数将返回值写入由该隐藏指针指定的内存地址。abi的不同之处在于谁将隐藏的指针从堆栈中弹出。Microsoft工具使用的ABI要求调用者弹出隐藏指针,但默认的gcc ABI要求调用者这样做。
现在,gcc几乎是无限可配置的,有一个开关允许您控制ABI。您可以使gcc使用与Microsoft工具相同的规则。这样做需要callee_pop_aggregate_return
函数属性。
把你的C代码改成这样:
__declspec(dllexport) SampleStruct __cdecl SampleMethod(void)
__attribute__((callee_pop_aggregate_return(0)));
// specifies that caller is responsible for popping the hidden pointer
SampleStruct SampleMethod(void) {
return (SampleStruct) { 1, 2 };
}