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++ 中的 int
和 long
(通常)是 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 签名的主要内容,如果未能解决你的问题,请参考以下文章
在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组
C# DllImport“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ”