【问题标题】:unable to invoke function of dll written in c++ from c#无法从 c# 调用用 c++ 编写的 dll 函数
【发布时间】:2014-09-02 07:03:30
【问题描述】:

我需要从 C# 控制台应用程序调用一个具有用 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 结构。不幸的是,它没有帮助我

【问题讨论】:

  • 尝试为您的字符串成员使用[MarshalAs(UnmanagedType.ByValTStr, SizeConst = ????)] 属性,或者不将它们编组为字符串,而是编组为字节数组。
  • 我不确定,但 String WhyLockString Holder 不匹配 char WhyLock[256]char Holder[40]。您认为 String 是如何知道 256 字节长的?!
  • 包装也错了。并且 p/invoke 需要通过 ref 传递结构。
  • 哦,调用约定还不匹配。
  • FWIW 打包导致布局效率低下。你为什么这样做?

标签: c# c++ dll pinvoke marshalling


【解决方案1】:

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

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

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

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

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

您可能还需要设置结构的字符编码使用

[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 中,结构是按值传递的,因此您必须通过 ref 传递它才能使签名匹配。

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


而且(希望最后)你的调用约定不匹配。 DllImport 的默认调用约定是 StdCall,而您的函数使用 __cdecl。因此,您也必须将其添加到您的 DllImport 属性中

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

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

【讨论】:

  • 谢谢,我采纳了你的建议,但没有帮助
  • @Nurzhan 那是因为其他错误。请阅读我的评论。
  • @DavidHeffernan 是的,我专注于我立即发现的一个错误。我会将 pack 值添加到结构布局中。
  • +1 您缺少调用约定。非托管代码使用 cdecl,托管默认为 stdcall。
  • @DavidHeffernan 这不取决于 SAMPLENATIVEDLL_API 的定义吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多