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

pinvokestack失衡不是由调用约定引起的

首先让我祝贺你提了一个非常好的问题。这是一件令人高兴的事,这一次,收到了重现这个问题所需的所有代码。

问题是由于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 };
}