C# 编组、不平衡堆栈和正确获取 PInvoke 签名

Posted

技术标签:

【中文标题】C# 编组、不平衡堆栈和正确获取 PInvoke 签名【英文标题】:C# Marshaling, ubalanced stack and getting PInvoke signature correct 【发布时间】:2018-12-19 07:44:44 【问题描述】:

我正在尝试使用封送处理在我的 C# 项目中调用 C DLL,并让一些函数正常工作,但我遇到了其他问题。就像下面的一样。

我的第一个问题是使结构正确,下一个问题是将 PROFILE_INFO 作为包含程序文件列表的数组返回,或者它可能不会返回列表并且 proNum 是一个索引。

C 中的函数

extern "C" __declspec(dllexport) int WINAPI GetProgramFileList (unsigned long proNum, PROFILE_INFO *proFile);

typedef struct
    PROINFO         proInfo;
    __int64             proSize;
    PROGRAM_DATE    createDate;
    PROGRAM_DATE    writeDate;
PROFILE_INFO;

typedef struct
    char wno[33];
    char dummy[7];
    char comment[49];
    char dummy2[7];
    char type;
    char dummy3[7];
PROINFO;

typedef struct
    short   year;
    char    month;
    char    day;
    char    hour;
    char    min;
    char    dummy[2];
PROGRAM_DATE;

我的功能

[DllImport(@".\IFDLL.dll", EntryPoint = "GetProgramFileList", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern int GetProgramFileListTest(ulong proNum, ref PROFILE_INFO pro);

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROFILE_INFO

    [MarshalAs(UnmanagedType.Struct)]
    public PROINFO proInfo;            // WNo/name/type
    public long proSize;               // Program size
    [MarshalAs(UnmanagedType.Struct)]
    public PROGRAM_DATE createDate;    // Program creating date
    [MarshalAs(UnmanagedType.Struct)]
    public PROGRAM_DATE writeDate;     // Program updating date


[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROINFO

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno;         // WNo.
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy;       // dummy
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment;     // program name
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2;      // dummy
    public char type;                                                               // program type
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3;      // dummy


[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE

    public short year;                 // Date (Year) 4-digit
    public char month;                 // Date (Month)
    public char day;                   // Date (Day)
    public char hour;                  // Date (Time)
    public char min;                   // Date (Minutes)
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private char dummy; // Dummy

C# struct PROFILE_INFO 中的 PROGRAM_DATE createDate 将抛出:

无法编组“CClient.Models.PROFILE_INFO”类型的字段“createDate”:该字段的类型定义具有布局信息,但托管/非托管类型组合无效或不可编组。

将 PROGRAM_DATE 字段更改为字符串使其接受它,但该函数返回一个参数 (-60) 错误。虽然不确定我是否离成功更近了。

其他尝试,包括尝试让 PROFILE_INFO 作为数组返回 (ref PROFILE_INFO[]),登陆:

对 PInvoke 函数的调用使堆栈不平衡。这可能是因为托管 PInvoke 签名与非托管目标签名不匹配。

我在 C dll 之后得到了这些描述:

说明

获取标准区节目列表

论据

proNum [in]

指定要获取的数据个数。

proFile [out]

将节目信息存储在标准区,PROFILE_INFO类型。

在执行该函数之前,请确保数据区域为要获取的数据个数。

返回值

如果成功,则返回“0”。如果有错误,则返回“0”以外的值。

我使用的其他函数是 GetProgramDirInfo、SendProgram、ReceiveProgram、SearchProgram 等,但它们不返回任何数组,所以我认为编组数组是我的问题。此外,我试图避免使用不安全的指针,但我不确定是否需要自己进行复制。

感谢任何帮助。

【问题讨论】:

【参考方案1】:

使用 p/invoke 的几点注意事项:

不要添加对 .NET 显而易见的内容(结构就是结构,...) 如果您不确定,请不要添加 pack,默认情况下 .NET 在这方面应该表现得像 C/C++ 如果定义中没有字符串,不要添加Ansi信息(仅当有字符串或TStr等时)。它不会引起问题,但没用 一般来说,如果您不知道属性的用途,请不要添加属性 C/C++ 中的 intlong (通常)是 32 位的。 long 在 C/C++ 中不是 64 位

这是一个应该更好的代码:

  [DllImport(@".\IFDLL.dll", EntryPoint = "GetProgramFileList")]
  public static extern int GetProgramFileListTest(uint proNum, ref PROFILE_INFO pro);

  [StructLayout(LayoutKind.Sequential)]
  public struct PROFILE_INFO
  
      public PROINFO proInfo;            // WNo/name/type
      public long proSize;               // Program size
      public PROGRAM_DATE createDate;    // Program creating date
      public PROGRAM_DATE writeDate;     // Program updating date
  

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  public struct PROINFO
  
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno;         // WNo.
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy;       // dummy
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment;     // program name
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2;      // dummy
      public char type;                                                               // program type
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3;      // dummy
  

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  public struct PROGRAM_DATE
  
      public short year;                 // Date (Year) 4-digit
      public char month;                 // Date (Month)
      public char day;                   // Date (Day)
      public char hour;                  // Date (Time)
      public char min;                   // Date (Minutes)
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private string dummy; // Dummy
  

【讨论】:

非常感谢,这是一个很大的帮助。如果我设置 proNum = 1,我设法取回数据,但如果我设置 proNum >= 2 则不是它不应该返回一个数组吗? @KimJensen - 这取决于您在 C/C++ 代码中所做的工作 名称和描述暗示了一个“列表”,假设它将返回一个数组大小 proNum 是否正确?如果 proNum 是一个索引 proNum = 2 它应该给我一些数据,因为 GetProgramDirInfo 告诉我 totalProNum = 56,但它崩溃了。 如果是数组,尝试将函数定义为GetProgramFileListTest(uint proNum, [In, Out] PROFILE_INFO[] pro)并传递pi where var pi = new PROFILE_INFO[2] 例如

以上是关于C# 编组、不平衡堆栈和正确获取 PInvoke 签名的主要内容,如果未能解决你的问题,请参考以下文章

调用 PInvoke 函数 ... 使堆栈不平衡

在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组

“对 PInvoke 函数的调用使堆栈不平衡”

C# DllImport“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ”

IndexOutOfRangeException - 无法使用 PInvoke 查看调用堆栈

PInvoke - 如何编组'SomeType * []'?