无法从c#调用用c ++编写的dll函数

时间:2014-09-02 07:03:31

标签: c# c++ dll pinvoke marshalling

我需要从C#console app调用一个用c ++编写的dll函数。我编写了编组并在C#中编写了相同的数据类型。但无论如何我得到错误。问题在哪里?

这是dll函数的代码

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

}

这是TCardInfoEx结构代码

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

这是我在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;      
};

这是我调用方法(函数)的代码

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

我收到以下错误: 托管调试助手&PInvokeStackImbalance'已在' \ bin \ Debug \ FCFake.vshost.exe'中检测到问题。

附加信息:呼叫PInvoke功能' Program :: GetCardInfoEx'堆栈不平衡。这很可能是因为托管PInvoke签名与非托管目标签名不匹配。检查PInvoke签名的调用约定和参数是否与目标非托管签名匹配。

如何解决问题?

UPD:我根据Dirk的回答编辑了TCardInfoEx结构。不幸的是,它没有帮助我

1 个答案:

答案 0 :(得分:5)

快速浏览一下,我会说问题是你尝试映射到C#字符串的C ++结构中的固定大小的char数组。

当你使用这样一个固定大小的数组时,它会嵌入到struct本身中,而不是使用指针,指针只会将指针添加到结构而不是它指向的数据。

您可以修改C#结构,以便.NET知道如何使用MarshalAs属性将其映射到本机世界:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
string  OtherCardInformation;

这适用于.NET字符串到C ++ char数组的所有映射。

您可能还必须使用

设置结构的字符编码
[StructLayout(CharSet = CharSet.Ansi)]
public struct TCardInfoEx

例如,如果您的C ++程序使用ANSI字符。


您已指定包值(#pragma pack(1))。这也必须使用

转换为.NET世界
[StructLayout(CharSet = CharSet.Ansi, Pack = 1)]
public struct TCardInfoEx

最后你的签名并不匹配:

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

在.NET中,结构是按值传递的,因此您必须通过引用传递它才能使签名匹配。

将C ++ char *映射到.NET类型始终依赖于上下文。有时这样的指针用作输入字符串,有时用于任意输入数据,当然它也可以只是一个字符输出参数。


并且(希望最终)您的呼叫约定不匹配。当您的函数使用DllImport时,StdCall的默认调用约定为__cdecl。因此,您必须将其添加到DllImport属性中

[DllImport(..., CallingConvetion = CallingConvention.Cdecl, ...)]

感谢@DavidHeffernan,这篇文章从其原始版本扩展了很多,其中我只讨论了固定数组大小的问题。