Nurzhan Aitbayev
Nurzhan Aitbayev

Reputation: 807

unable to invoke function of dll written in c++ from c#

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

Answers (1)

Dirk
Dirk

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

Related Questions