【问题标题】:How to display the values from structures in C# from C++如何从 C++ 中显示 C# 中结构的值
【发布时间】:2017-12-15 13:37:50
【问题描述】:

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# 一无所知,但我向你的勇敢致敬,提供超过一半的声誉作为赏金 - 所以我投了赞成票。
  • 一些事情......从您共享的代码中,hResult 没有在任何地方声明(尽管忽略您的代码表明它应该是一个整数)。乍一看,这表明 DicErrorCode 正在接收无效的整数到字符串的转换,但很难确定您何时可能错误地使用了您的结构。你有没有可能写出你的代码试图做什么的文字表示?

标签: c++ pointers structure xfs cen-xfs


【解决方案1】:

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

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 typedef,8 位类型。如果本机结构使用 WCHAR,CharSet.Auto 将是正确的选择。
  • public uint cim_ulValues;
    Windows 使用LLP64 data model,它在 32 位和 64 位程序中都将ULONG 保持在 32 位。这使得 uint 成为正确的 C# 等效项,而不是 ulong。
  • private byte _cim_bConfigured;
    bool 是一种非常棘手的类型,标准化程度很差。它在 C++ 中为 1 个字节,在 C 中为 4 个字节,在 COM 互操作中为 2 个字节,在托管代码中为 1 个字节。默认封送处理假定BOOL 作为匹配的本机类型,它在winapi 中完成的方式。将其声明为带有公共属性 getter 的字节是一种方法,而我在这里偏爱的方法是将 [MarshalAs(UnmanagedType.U1)] 属性应用于该字段将是另一种方法。
  • CallingConvention = CallingConvention.Stdcall
    明确这一点非常重要,这里的另一个常见选择是 CallingConvention.Cdecl。我无法从本机 sn-p 判断哪个是正确的,BNA_API 是一个宏,但是您没有提到 PInvokeStackImbalance MDA 抱怨它,所以 Stdcall 可能是正确的。请确保您没有将其关闭。
  • [Out]out sp_BankNoteTypeList list
    这里需要 [Out] 属性来说服 pinvoke marshaller 需要将结构复制回来。这很不直观,大多数程序员认为out 就足够了。但这是编组器不知道的 C# 语言细节。必须明确要求复制回来,该结构不是“blittable”。或者换句话说,本机布局与内部托管布局不同。 ByValArray 使这不可避免。

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

【讨论】:

    【解决方案2】:

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

    几个基本音符:

    • BNA_API 对我来说是 __declspec(dllexport)
    • 函数声明放在a内

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

      块,以避免C++ 名称混乱。更多详情请查看[MSDN]: Decorated Names

    问题:

    1. 调用约定不匹配(这在我的情况下引发了异常):默认情况下 C(C++) 使用 __cdeclC# marshaler 使用__stdcall。这效果不好,它会在 pushing/popping 参数时破坏堆栈。为了解决这个问题,您必须在一个地方更改默认值:
      • C++BNA_API int __stdcall BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType); - 这是我选择的方法
      • C#DllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)] - 这个也可以
    2. C# 中的某些类型与 C 中的对应类型不同(更广泛)。当数据对齐时,如果发生这种不匹配,那么第 1st 之后的所有内容都会一团糟(试图将其取出时)。所以,在struct sp_notetypeC# 定义中,你应该有:public uint cim_ulValues;。查看[MSDN]: Marshaling Arguments查看完整列表
    3. (可能是前一个的变体)您的结构的Charset - 在C 中,使用char(8 位宽) - 将其从Charset.Auto 更改为@ 987654342@。检查[SO]: C# calling C DLL, pass char * as parameter not correct

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-18
      • 1970-01-01
      • 2011-04-18
      • 2013-04-09
      相关资源
      最近更新 更多