如何从C++显示 C# 中的结构中的值

How to display the values from structures in C# from C++

本文关键字:结构 显示 C++      更新时间:2023-10-16

abc.h 文件

typedef struct sp_BankNoteTypeList
{
int cim_usNumOfNoteTypes;
struct sp_notetype
{
USHORT cim_usNoteID;
CHAR   cim_cCurrencyID[3];
ULONG  cim_ulValues;
bool   cim_bConfigured;
}SP_CIMNOTETYPE[12];
}SP_CIMNOTETYPELIST,*SP_LPCIMNOTETYPELIST;

BNA_API int BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType);

abc.cpp (DLL 文件(

int BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType)
{
LPWFSCIMNOTETYPE    fw_notetypedata;
LPWFSCIMNOTETYPELIST    lpNoteTypeList;   //output param
hResult = WFSGetInfo(hService, WFS_INF_CIM_BANKNOTE_TYPES, (LPVOID)NULL, 400000, &res);
lpNoteTypeList=(LPWFSCIMNOTETYPELIST)res->lpBuffer;
if(hResult!=0)
{
return (int)hResult;
}
sp_BankNoteType->cim_usNumOfNoteTypes = lpNoteTypeList->usNumOfNoteTypes;
for(int i=0;i<lpNoteTypeList->usNumOfNoteTypes;i++)
{
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_usNoteID = lpNoteTypeList->lppNoteTypes[i]->usNoteID;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_ulValues = lpNoteTypeList->lppNoteTypes[i]->ulValues;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_bConfigured = lpNoteTypeList->lppNoteTypes[i]->bConfigured;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[0] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[0];
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[1] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[1];
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[2] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[2];        
} 
return (int)hResult;
}

结构:-

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct sp_notetype
{
public ushort cim_usNoteID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] cim_cCurrencyID;
public ulong cim_ulValues;
public bool cim_bConfigured;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct sp_BankNoteTypeList
{ 
public int cim_usNumOfNoteTypes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public sp_notetype[] SP_CIMNOTETYPE;    
}; 
public sp_notetype[] SP_CIMNOTETYPE;
public sp_BankNoteTypeList SP_CIMNOTETYPELIST;

函数调用:-

[DllImport(@"abc.dll")]
public static extern int BanknoteType(out sp_BankNoteTypeList SP_CIMNOTETYPELIST);

public string BNA_BankNoteType(out int[] NoteID,out string[]CurrencyID,out string[] Values,out bool[] Configured)
{
NoteID = new int[12];
CurrencyID = new string[12];
Values = new string[12];
Configured = new bool[12];
try
{
trace.WriteToTrace(" Entered in BNA_BankNoteType ", 1);
hResult = BanknoteType(out SP_CIMNOTETYPELIST);
for (int i = 0; i < 12; i++)
{
NoteID[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_usNoteID);
CurrencyID[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[0]).ToString() + (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[1]).ToString() + (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[2]).ToString();
Values[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_ulValues).ToString();
Configured[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_bConfigured);
}
return DicErrorCode(hResult.ToString());
}
catch (Exception ex)
{
return "FATAL_ERROR";
}

当我尝试在 C# 中调用它们时,我在 C# 中得到垃圾值。 当我调试它们时,我发现正确存储值。 关于如何传递值的任何帮助都将非常有用。 提前谢谢。

C# 声明在正确的轨道上,只是细节有点错误。 这篇文章是一个很好的起点,它向您展示了如何编写一些测试代码来验证结构声明是否匹配。 在这个特定的上这样做:

C++: auto len = sizeof(SP_CIMNOTETYPELIST);                    // 196 bytes
C# : var len = Marshal.SizeOf(typeof(sp_BankNoteTypeList));    // 296 bytes

不接近,您必须获得完全匹配才能有希望正确编组。 内部结构的 C# 声明应如下所示:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct sp_notetype {
public ushort cim_usNoteID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] cim_cCurrencyID;
public uint cim_ulValues;
private byte _cim_bConfigured;
public bool cim_bConfigured {
get { return _cim_bConfigured != 0; }
}
};
[DllImport(@"abc.dll", CallingConvention = CallingConvention.Stdcall)]
public static extern int BanknoteType([Out]out sp_BankNoteTypeList list);

sp_BankNoteTypeList声明是可以的。 重新运行测试,您现在应该在 C# 中也应该获得 196 字节。 注释更改:

  • CharSet = CharSet.Ansi
    这是让 CHAR 成员正确编组所必需的。 它是char的Windows类型,一个8位的类型。 如果本机结构使用 WCHAR,CharSet.Auto 将是正确的选择。
  • public uint cim_ulValues;
    Windows 使用 LLP64 数据模型,该模型在 32 位和 64 位程序中将ULONG保持在 32 位。 这使得uint正确的 C# 等效项,而不是 ulong。

  • private byte _cim_bConfigured;bool是一个非常棘手的类型,标准化很差。 它是 1 个字节的C++字节、4 字节的 C 字节、2 字节的 COM 互操作、1 字节的托管代码。 默认封送处理假定BOOL作为匹配的本机类型,就像在 winapi 中完成的那样。 使用公共属性获取器将其声明为私有字节是一种方法,我在这里喜欢的方法,将 [MarshalAs(UnmanagedType.U1(] 属性应用于字段将是另一种方法。
  • CallingConvention = CallingConvention.Stdcall明确
    这一点非常重要,这里的另一个常见选择是CallingConvention.Cdecl。 我无法从本机代码段中判断哪一个是正确的,BNA_API是一个宏,但你没有提到 PInvokeStackImbalance MDA 抱怨它,所以 Stdcall 在某种程度上可能是正确的。 请确保您没有将其关闭。
  • [Out]out sp_BankNoteTypeList list
    [Out] 属性在这里是必需的,以说服 pinvoke 封送器需要复制结构。 这是非常不直观的,大多数程序员认为out就足够了。 但这是编组者不知道的 C# 语言细节。 必须明确请求复制回来,该结构不是"可 blitable"的。 换句话说,本机布局与内部托管布局不同。 ByValArray使这不可避免。

相当多的洗衣清单,我希望我都得到了。 使结构尺寸相同是战斗的95%。

我已经重新创建了C++部分,因为我遇到了与WFSGetInfo及其相关结构相关的错误,我正在用一些随机数据填充字段(关于char[3]字段的说明:我用 3 个随机大写字母填充它(。在MSDN的启发和SO的许多其他问题的帮助下,我确定了代码中的问题列表;解决这些问题后,我能够从C#中获取正确的数据。请注意,我使用的是VStudio2015

几个地面笔记:

  • 对我来说BNA_API__declspec(dllexport)
  • 函数声明放置在

    #if defined(__cplusplus)
    extern "C" {
    #endif
    // Function declaration comes here
    #if defined(__cplusplus)
    }
    #endif
    

    阻止,以避免C++名称重整。有关更多详细信息,请查看 [MSDN]:修饰名称

问题:

  1. 调用约定不匹配(在我的情况下,这引发了异常(:默认情况下,C(C++( 使用__cdecl,而C#封送拆收器使用__stdcall.这不能很好地工作,它在推送ing/popping 参数时会损坏堆栈。为了解决此问题,您只能在一个地方更改默认值:
    • C++BNA_API int__stdcallBanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType);- 这是我选择的方法
    • C#DllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)]- 这也可以
  2. C#中的某些类型与C中的对应类型不同(更宽(。当数据对齐时,如果发生这种不匹配,则在第一个之后的所有内容都会被搞砸(当试图将其取出时(。因此,在struct sp_notetypeC#定义中,您应该具有:publicuintcim_ulValues;。检查 [MSDN]:封送处理参数以获取完整列表
  3. (可能是前一个的变体(结构的Charset- 在C中,使用char(8 位宽(-,将其从Charset.Auto更改为Charset.Ansi.检查 [SO]:C# 调用 C DLL,传递字符 * 作为参数不正确