【问题标题】:Returning a struct from PInvoke从 PInvoke 返回一个结构
【发布时间】:2021-06-17 22:47:33
【问题描述】:

我试图从 c# 对 c++ dll 的调用返回一个结构,但我得到了一些我不理解的复杂和不良行为。如果我的结构包含一个构造函数,我在返回它时会遇到内存访问冲突,如果它小于 12 个字节。如果大一点就没有问题。如果我删除构造函数,它适用于所有尺寸。我想这可能与我的调用是 c 风格有关,但我找不到有关此的信息。因此,如果有人可以解释或指出我正在发生的事情的一个好的方向,将不胜感激。以下是有效和无效的代码示例:

有效的代码

C++ 端标题:

#define DLL_API __declspec(dllexport)

struct Struct4Byte
{
 int x1;
};

struct Struct12Byte
{
 int x1;
 int x2;
 int x3;
 Struct12Byte() { x1 = 0; x2 = 1; x3 = 2; }
};

#ifdef __cplusplus
extern "C" {
#endif
 DLL_API Struct4Byte Function4Byte(int x);
 DLL_API Struct12Byte Function12Byte(int x);
#ifdef __cplusplus
}
#endif

C++ 文件:

Struct4Byte Function4Byte(int x)
{
 Struct4Byte output;
 output.x1 = 1 + x;
 return output;
}

Struct12Byte Function12Byte(int x)
{
 Struct12Byte output;
 output.x1 = 1 + x;
 output.x2 = 2 + x;
 output.x3 = 3 + x;
 return output;
}

在调用方 (C#) 我这样做:

[StructLayout(LayoutKind.Sequential)]
internal struct Struct4Byte
{
 public int x1;
}

[StructLayout(LayoutKind.Sequential)]
internal struct Struct12Byte
{
 public int x1;
 public int x2;
 public int x3;
}

class Program
{
    static void Main(string[] args)
    {
            Struct4Byte result1 = Function4Byte(3);
            Struct12Byte result2 = Function12Byte(3);
    }

    [DllImport(@"PInvokeCheck.dll")]
    internal static extern Struct4Byte Function4Byte(int x);

    [DllImport(@"PInvokeCheck.dll")]
    internal static extern Struct12Byte Function12Byte(int x);
}

无效的代码

如果我现在在头文件中将 Struct4Byte 的定义更改为:

struct Struct4Byte
{
 int x1;
 Struct4Byte(){ x1 = 0; }
};

然后我得到内存访问冲突。

我注意到了一些可能感兴趣的东西。问题已经在调用 Function4Byte 时出现了。在函数中放置一个断点并查看 x(我在下面再次放置了函数)表明 x 得到了一些随机值。

Struct4Byte Function4Byte(int x)
{
 Struct4Byte output;
 output.x1 = 1 + x;
 return output;
} 

【问题讨论】:

  • CallingConvention 应该是Cdecl 吧?那么填充呢,x64 会填充到 8 个字节
  • 这似乎没有任何效果。但我不是 100% 确定如何进行填充。我在 c# 中使用了[StructLayout(LayoutKind.Sequential, Pack = 8)],我也在 c++ 中尝试了#pragma pack(8)。但没有改善。
  • 奇怪的是,对于具有两个 int 成员(所以 8 个字节)但不是 12 个字节或更大的结构,我也有同样的问题。我想我可以通过不包含任何构造函数或添加一些额外的数据来解决这个问题。但是,我真的不知道问题是否已解决,或者它是否只是偶然发生的。
  • 不返回结构体,而是将结构体的引用作为参数传递呢? Marshaling Classes, Structures, and Unions
  • 这显然是一个选项,我已经为我返回的大部分数据做了什么。但我主要关心的不是让它工作,而是理解它为什么不工作。否则,当我以其他方式返回我的结构时,这也可能是一个问题。

标签: c# c++ pinvoke


【解决方案1】:

我可能已经找到了问题所在。他们是我错过的警告:警告C4190:'Function4Byte'已指定C-linkage,但返回与C不兼容的UDT'Struct4Byte'。从这个讨论中可以清楚地看出它可能导致一些未定义的行为并且结构的布局可能会改变:Error in C++ code linkage: warning C4190: type has C-linkage specified, but returns UDT which is incompatible with C

所以这似乎解释了我遇到的问题。

【讨论】:

    【解决方案2】:

    是的,不支持返回这样的结构,并且 Microsoft 的主线 P/Invoke 文章都没有代表返回这样的结构的示例。反正我没有读过。

    这是一个 git hub 示例,说明您的项目以正确的方式进行操作。

    https://github.com/ditchcode/PInvokeTesting

    我发现这篇文章对结构和类的编组最有帮助 https://docs.microsoft.com/en-us/dotnet/framework/interop/passing-structures

    【讨论】:

      【解决方案3】:

      我认为原因在调用约定中有所解释。

      x64 calling convention

      用户定义的类型可以从全局函数和静态成员函数中按值返回。要在 RAX 中按值返回用户定义的类型,它的长度必须为 1、2、4、8、16、32 或 64 位。它还必须没有用户定义的构造函数、析构函数或复制赋值运算符。它不能有私有或受保护的非静态数据成员,也不能有引用类型的非静态数据成员。它不能有基类或虚函数。而且,它只能拥有也满足这些要求的数据成员。 (此定义本质上与 C++03 POD 类型相同。由于 C++11 标准中定义已更改,因此我们不建议在此测试中使用 std::is_pod。)否则,调用者必须分配返回值的内存并将指向它的指针作为第一个参数传递。然后将剩余的参数向右移动一个参数。 RAX 中的被调用者必须返回相同的指针。

      x86 calling convention

      在 x86 平台上,所有参数在传递时都扩展为 32 位。返回值也扩展为 32 位并在 EAX 寄存器中返回,但 8 字节结构除外,它们在 EDX:EAX 寄存器对中返回。较大的结构在 EAX 寄存器中作为指向隐藏返回结构的指针返回。参数从右到左压入堆栈。不是 POD 的结构不会在寄存器中返回。

      按照 x64 的说法去做是一个很好的替代解决方案。

      调用者必须为返回值分配内存并传递一个指向它的指针作为第一个参数

      所以你的代码看起来像这样

      Struct4Byte Function4Byte(Struct4Byte& output, int x)
      {
       output.x1 = 1 + x;
      }
      
      Struct12Byte Function12Byte(Struct12Byte& output, int x)
      {
       output.x1 = 1 + x;
       output.x2 = 2 + x;
       output.x3 = 3 + x;
      }
      
      class Program
      {
          static void Main(string[] args)
          {
              Struct4Byte = Function4Byte(out var result1, 3);
              Function12Byte(out var result2, 3);
          }
      
          [DllImport(@"PInvokeCheck.dll")]
          internal static extern void Function4Byte(out Struct4Byte result, int x);
      
          [DllImport(@"PInvokeCheck.dll")]
          internal static extern void Function12Byte(out Struct12Byte result, intx);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多