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

abc.h file

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 file)

 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; } 

Structure: -

 [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; 

Function Call: -

 [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"; } 

when I try to call them in C #, I get the garbage value in C #. While I am debugging them, I find the correct storage of values. Any help on how the values ​​should be conveyed would be very helpful. Thanks in advance.

+8
c ++ pointers structure xfs cen-xfs
source share
2 answers

C # declarations are on the right track, it's just that the details are subtlely wrong. A good starting point is this post , showing how to write some test code to make sure the structure declarations match each other. Doing this in this particular case:

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

Not close, you should get an exact match in order to hope for its proper sorting. The C # declaration for the internal structure should look like this:

  [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); 

The sp_BankNoteTypeList declaration is OK. Repeat the test, and now you should get 196 bytes in C #. Annotating Changes:

  • CharSet = CharSet.Ansi
    This is necessary so that the CHAR member is correctly marshal. This is a typedef for Windows char , an 8-bit type. CharSet.Auto would be the right choice if the native framework used WCHAR.
  • public uint cim_ulValues;
    Windows uses the LLP64 data model , which stores 32-bit ULONG in 32-bit and 64-bit programs. This makes uint correct C # equivalent instead of ulong.
  • private byte _cim_bConfigured;
    bool is a very complex type with poor standardization. This is 1 byte in C ++, 4 bytes in C, 2 bytes in COM communication, 1 byte in managed code. Marshaling by default assumes bool as the corresponding native type, as is done in winapi. Declaring a private as a byte using a generic property generator is one way to do this, and the one I approved here by applying the [MarshalAs (UnmanagedType.U1)] attribute to the field will be different.
  • CallingConvention = CallingConvention.Stdcall
    It's important that this is clear, another common choice here is CallingConvention.Cdecl. I can’t say from my own fragment which is correct, BNA_API is a macro, but you didn’t mention that PInvokeStackImbalance MDA complains about this, so Stdcall will most likely be right. Make sure you have not disabled it.
  • [Out]out sp_BankNoteTypeList list
    The [Out] attribute is needed here to convince the pinvoke Marxist that copying the structure back is required. This is pretty unintuitive, most programmers think that out enough. But this is detailed information about the C # language that the marshaller does not know about. Copying back must be explicitly requested, the structure is not "blittable". In other words, the native layout does not match the internal managed layout. ByValArray makes this inevitable.

Enough list of laundries, I hope I have them all. Obtaining the size of the structure is the same - 95% of the battle.

+7
source share

I recreated the C ++ part, since I got errors related to WFSGetInfo and related structures, I fill in the fields with some random data (note about the char[3] field char[3] : I fill it with 3 random uppercase letters). With inspiration from MSDN and many other questions from SO, I have identified a list of problems in the code; after fixing these problems, I was able to get the right data from C #. As a note, I am using VStudio2015.

A few ground notes:

  • BNA_API for me is __declspec(dllexport)
  • Function declaration is placed inside

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

    to avoid changing the name of C ++. See [MSDN]: Awarded Names for More Information.

Problems:

  • Call convention mismatch (this threw an exception in my case): by default, C (C ++) uses __cdecl , while the C # marshaler uses __stdcall . This does not work; it distorts the stack when clicking / outputting arguments. To fix this, you should change the default value in only one place :
    • C ++: BNA_API int __stdcall BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType); is the method that I chose
    • C #: DllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)] - this also works
  • Some types from C # differ (wider) than their correspondents from C. When the data is aligned, if such inconsistencies occur, everything that comes after 1 st is messed up (when trying to pull it out). So, in the definition of struct sp_notetype C # you should have: public uint cim_ulValues; . Check [MSDN]: Marshaling Arguments for a Complete List
  • (may be a variant of the previous one). Charset your structures - in C, char (width 8 bits) - - change it from Charset.Auto to Charset.Ansi . Check [SO]: C # calls the C DLL, pass char * as parameter incorrectly
+3
source share

All Articles