Reputation: 807
I need to invoke a function that have dll written in c++ from C# console app. I made marshaling and wrote the same datatypes in C#. But anyway I get errors. Where is the problem?
Here is code of the dll's function
extern "C" {
SAMPLENATIVEDLL_API int GetCardInfoEx(INT64 Card, DWORD Restaurant, DWORD UnitNo, TCardInfoEx* Info, char* InpBuf, DWORD InpLen, WORD InpKind, char* OutBuf, DWORD OutLen, WORD OutKind) {
int res = getCardInfoEx(Card, Info, UnitNo);
return res;
}
}
Here is TCardInfoEx structure code
#pragma pack(1)
typedef struct TCardInfoEx {
WORD Size;
BYTE Deleted;
BYTE Seize;
BYTE StopDate;
BYTE Holy;
BYTE Manager;
BYTE Blocked;
CHAR WhyLock[256];
CHAR Holder[40];
INT64 UserID;
DWORD CardAccountNumber;
DWORD TypeOfDefaulter;
WORD Bonus;
WORD Discount;
INT64 Summa;
INT64 AvailableSumma;
INT64 SummaOnCardAccount2;
INT64 SummaOnCardAccount3;
INT64 SummaOnCardAccount4;
INT64 SummaOnCardAccount5;
INT64 SummaOnCardAccount6;
INT64 SummaOnCardAccount7;
INT64 SummaOnCardAccount8;
CHAR OtherCardInformation[256];
CHAR OtherCardInformation1[256];
CHAR OtherCardInformation2[256];
};
Here is I made struct in c#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TCardInfoEx
{
ushort Size;
byte Deleted;
byte Seize;
byte StopDate;
byte Holy;
byte Manager;
byte Blocked;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
String WhyLock;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
String Holder;
Int64 UserID;
uint CardAccountNumber;
uint TypeOfDefaulter;
ushort Bonus;
ushort Discount;
Int64 Summa;
Int64 AvailableSumma;
Int64 SummaOnCardAccount2;
Int64 SummaOnCardAccount3;
Int64 SummaOnCardAccount4;
Int64 SummaOnCardAccount5;
Int64 SummaOnCardAccount6;
Int64 SummaOnCardAccount7;
Int64 SummaOnCardAccount8;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
String OtherCardInformation;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
String OtherCardInformation1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
String OtherCardInformation2;
};
Here is code where I invoke the method (function)
class Program
{
[DllImport("SampleNativeDLL.dll", CharSet = CharSet.Unicode)]
public static extern int GetCardInfoEx(Int64 Card, uint Restaurant, uint UnitNo, TCardInfoEx Info, String InpBuf, uint InpLen, ushort InpKind, String OutBuf, uint OutLen, ushort OutKind);
static void Main(string[] args)
{
TCardInfoEx tc = new TCardInfoEx();
GetCardInfoEx(1248000259045, 1, 1, tc, "", 0, 1, "", 1, 1);
}
}
And I get the following error: Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in '\bin\Debug\FCFake.vshost.exe'.
Additional information: A call to PInvoke function 'Program::GetCardInfoEx' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
How to solve the issue?
UPD: I edited TCardInfoEx struct according to Dirk's answer. Unfortunatelly, it didn't help me
Upvotes: 0
Views: 294
Reputation: 10958
At a quick glance I'd say the problem is the fixed size char arrays in the C++ struct that you try to map to C# strings.
When you use such a fixed sized array it becomes embedded in the struct itself, as opposed of using a pointer, which would only add the pointer to the struct and not the data it points to.
You can modify the C# struct so .NET knows how map it to the native world using the MarshalAs
-attribute:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
string OtherCardInformation;
This applies for all mappings for .NET strings to C++ char arrays.
You might also have to set the character encoding for the struct using
[StructLayout(CharSet = CharSet.Ansi)]
public struct TCardInfoEx
for example, if your C++ program uses ANSI characters.
You have specified a pack value (#pragma pack(1)
). That also has to be translated into the .NET world using
[StructLayout(CharSet = CharSet.Ansi, Pack = 1)]
public struct TCardInfoEx
And finally your signatures don't really match:
int GetCardInfoEx(
INT64 Card, // long
DWORD Restaurant, // uint
DWORD UnitNo, // uint
TCardInfoEx* Info, // must be ref TCarfInfoEx
char* InpBuf, // could be string, could also be byte[]
DWORD InpLen, // uint
WORD InpKind, // ushort
char* OutBuf, // could be StringBuilder, or IntPtr
DWORD OutLen, // uint
WORD OutKind) // ushort
In .NET structs are passed by value, so you have to pass it by ref in order for the signatures to match.
Mapping a C++ char *
to a .NET type is always context-dependant. Sometimes such a pointer is used as an input string, sometimes for arbitrary input data, and of course it could also just be a single character output parameter.
And (hopefully finally) your calling conventions don't match. The default calling convention with DllImport
is StdCall
while your function uses __cdecl
. So you have to add that to your DllImport
attribute too
[DllImport(..., CallingConvetion = CallingConvention.Cdecl, ...)]
Thanks to @DavidHeffernan this post expanded quite a bit from its original version, in which I only covered the fixed array size problem.
Upvotes: 5