7

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

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

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

如果我删除任一结构成员(双精度或整数),它工作正常。

我假设问题在某种程度上与结构的布局有关 - 但是当我在 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();
        }
    }
}
4

1 回答 1

9

首先,让我祝贺你提出了一个很好的问题。一次,很高兴收到重现问题所需的所有代码。

问题是由于 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 };
}
于 2013-03-24T11:41:38.633 回答