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.
Suppose, we need to print the structure with its fields corresponding value. We can do this with the help of package fmt which implements formatted I/O with functions analogous to C's printf and scanf.
Array elements are accessed using the Subscript variable, Similarly Structure members are accessed using dot [.] operator. Structure written inside another structure is called as nesting of two structures. Nested Structures are allowed in C Programming Language.
The 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:
C++: auto len = sizeof(SP_CIMNOTETYPELIST); // 196 bytes
C# : var len = Marshal.SizeOf(typeof(sp_BankNoteTypeList)); // 296 bytes
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:
[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 okay. Rerun the test and you should now get 196 bytes in C# as well. Annotating the changes:
CharSet = CharSet.Ansi
char
, an 8 bit type. CharSet.Auto would have been the right choice if the native structure used WCHAR.public uint cim_ulValues;
ULONG
at 32 bits in both 32-bit and 64-bit programs. That makes uint
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 assumes BOOL
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
[Out]out sp_BankNoteTypeList list
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.
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 about char[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
#if defined(__cplusplus)
extern "C" {
#endif
// Function declaration comes here
#if defined(__cplusplus)
}
#endif
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 from Charset.Auto
to Charset.Ansi
. Check [SO]: C# calling C DLL, pass char * as parameter not correct
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With