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 calling:-
[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 calling them in C# I get garbage value in C#. While I debug them I find proper storing the values. Any help as to how the values must be passed would be very useful.. Thanks in advance.
I've recreated the C++ part, since I got errors related to
WFSGetInfo
and its related structs, I'm populating fields with some random data (a note aboutchar[3]
field: I'm populating it with 3 random uppercase letters). With inspiration from MSDN and many other questions from SO, I've identified a list of problems in the code; after fixing those problems, I was able to get the right data out from C#. As a note, I'm using VStudio2015.Couple of ground notes:
BNA_API
for me is__declspec(dllexport)
The function declaration is placed inside a
block, to avoid C++ name mangling. For more details, check [MSDN]: Decorated Names
Problems:
__cdecl
while the C# marshaler uses__stdcall
. That doesn't work well, it corrupts the stack when pushing/popping arguments. In order to fix this you must change the default in one place only:BNA_API int
__stdcall
BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType);
- this is the method I choseDllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)]
- this works as wellstruct sp_notetype
's C# definition, you should have:public
uint
cim_ulValues;
. Check [MSDN]: Marshaling Arguments for the complete listCharset
of your structures - in C,char
(8bit wide) is used -, change it fromCharset.Auto
toCharset.Ansi
. Check [SO]: C# calling C DLL, pass char * as parameter not correctThe C# declarations are on the right track, it is just that the details are subtly wrong. A good starting point is this post, shows you how to write some test code to verify that the structure declarations are a good match. Doing so on this particular one:
Not close, you must get an exact match to have any hope of it marshalling correctly. The C# declaration for the inner struct should look like this:
The sp_BankNoteTypeList declaration is okay. Rerun the test and you should now get 196 bytes in C# as well. Annotating the changes:
CharSet = CharSet.Ansi
That one was necessary to get the CHAR member to marshal correctly. It is a Windows typedef for
char
, an 8 bit type. CharSet.Auto would have been the right choice if the native structure used WCHAR.public uint cim_ulValues;
Windows uses the LLP64 data model, that keeps a
ULONG
at 32 bits in both 32-bit and 64-bit programs. That makesuint
the correct C# equivalent instead of ulong.private byte _cim_bConfigured;
bool
is a very tricky type with poor standardization. It is 1 byte in C++, 4 bytes in C, 2 bytes in COM interop, 1 byte in managed code. The default marshaling assumesBOOL
as the matching native type, the way it is done in the winapi. Declaring it private as a byte with a public property getter is one way to do it and the one I favored here, applying the [MarshalAs(UnmanagedType.U1)] attribute to the field would be another.CallingConvention = CallingConvention.Stdcall
Pretty important to be explicit about this, the other common selection here is CallingConvention.Cdecl. I can't tell from the native snippet which one is correct, BNA_API is a macro, but you did not mention the PInvokeStackImbalance MDA complaining about it so Stdcall is somewhat likely to be correct. Do make sure you did not turn it off.
[Out]out sp_BankNoteTypeList list
The [Out] attribute is necessary here to convince the pinvoke marshaller that copying the structure back is required. This is pretty unintuitive, most programmer think that
out
is enough. But that is a C# language detail that the marshaller does not know about. Copying back has to be explicitly requested, the structure is not "blittable". Or in other words, the native layout is not the same as the internal managed layout. The ByValArray makes that inevitable.Quite a laundry-list, I hope I got them all. Getting the structure sizes the same is 95% of the battle.