CallingConvention不会导致PInvokeStackImbalance

时间:2013-03-24 07:05:50

标签: c# c pinvoke

我不知道这里有什么问题。

我有大量的p / invoke调用正常工作......除了这个。

我设法将我的问题减少到以下示例代码。

如果我删除结构成员(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();
        }
    }
}

1 个答案:

答案 0 :(得分:9)

首先,请允许我祝贺你提出一个非常好的问题。曾经很高兴收到重现问题所需的所有代码。

问题是由于gcc和Microsoft工具用于函数返回值的ABI略有不同。对于可以适合寄存器的返回值,例如int返回值没有差异。但是,由于您的结构太大而无法放入单个寄存器中,因此在这种情况下API之间存在差异。

对于较大的返回值,调用者将隐藏指针传递给该函数。这个隐藏的指针被调用者推入堆栈。该函数将返回值写入该隐藏指针指定的内存地址。 ABI的区别在于谁将隐藏指针弹出堆栈。 Microsoft工具使用ABI,要求调用者弹出隐藏指针,但默认的gcc ABI要求被调用者执行此操作。

现在,gcc几乎可以无限配置,有一个开关可以让你控制ABI。并且您可以使gcc使用与Microsoft工具相同的规则。这样做需要callee_pop_aggregate_return function attribute

将您的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 };
}