【问题标题】:Difficulty calling unmanaged dll from C#难以从 C# 调用非托管 dll
【发布时间】:2011-05-22 15:34:34
【问题描述】:

我睡眼惺忪地想弄清楚为什么我不能从我的 C# 应用程序中调用旧 C++ .dll 中的外部方法。

这是函数头:

int __export FAR PASCAL SimplePGPEncryptFile(
                                      HWND hWnd1,
                                      LPSTR InputFileName,
                                      LPSTR OutputFileName,
                                      BOOL SignIt,
                                      BOOL Wipe,
                                      BOOL Armor,
                                      BOOL TextMode,
                                      BOOL IDEAOnly,
                                      BOOL UseUntrustedKeys,
                                      LPSTR RecipientList,
                                      LPSTR SignerKeyID,
                                      int SignerBufferLen,
                                      LPSTR SignerPassphrase,
                                      int SignerPwdBufferLen,
                                      LPSTR IDEAPassphrase,
                                      int IDEAPwdBufferLen,
                                      LPSTR PublicKeyRingName,
                                      LPSTR PrivateKeyRingName);

这是我的 C# 声明:

        [DllImport("smplpgp_32.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern int SimplePGPEncryptFile(
        IntPtr hWnd1,
        [MarshalAs(UnmanagedType.LPStr)] string InputFileName,
        [MarshalAs(UnmanagedType.LPStr)] string OutputFileName,
        bool SignIt,
        bool Wipe,
        bool Armor,
        bool TextMode,
        bool IDEAOnly,
        bool UseUntrustedKeys,
        [MarshalAs(UnmanagedType.LPStr)] string RecipientList,
        [MarshalAs(UnmanagedType.LPStr)] string SignerKeyID,
        int SignerBufferLen,
        [MarshalAs(UnmanagedType.LPStr)] string SignerPassphrase,
        int SignerPwdBufferLen,
        [MarshalAs(UnmanagedType.LPStr)] string IDEAPassphrase,
        int IDEAPwdBufferLen,
        [MarshalAs(UnmanagedType.LPStr)] string PublicKeyRingName,
        [MarshalAs(UnmanagedType.LPStr)] string PrivateKeyRingName);

当我调用此方法时,出现以下两个错误之一(在标题中声明):

#define SIMPLEPGPENCRYPTFILE_RECIPIENTLISTDOESNOTENDWITHNEWLINE    408
#define SIMPLEPGPENCRYPTFILE_RECIPIENTLISTDOESNOTSTARTWITHGOODCODECHAR   409

这也被定义为头部中的常量:

#define INCLUDE_ONLYUSERIDS 1

这是已知可调用此函数的 C++ 代码:

    char recipients[512];
recipients[0] = INCLUDE_ONLYUSERIDS;
strcat(strcpy(&recipients[1], rID), "\n"); \\ rID is equal to "CA"
return 0 == SimplePGPEncryptFile(INI.m_hWnd,
    (char*)plain, (char*)cipher,
    0,
    0,
    1,
    0,
    0,
    1, // UseUntrustedKeys
    recipients,
    0, 0,
    0, 0,
    0, 0,
    PGPKM.pub, 0); //PGPKM.pub is declared earlier

将这个传递给“RecipientList”参数会给我“409”错误:

string recipientList = "1CA\n\0";

将这个传递给“RecipientList”参数会给我“408”错误:

char[] recipients = new char[512];
recipients[0] = '1';
recipients[1] = 'C';
recipients[2] = 'A';
recipients[3] = '\n'; // also tried '\r', then '\n'
recipients[4] = Char.MinValue;
string paramValue = recipients.ToString();

谁能发现我的明显疏忽?我觉得我已经拥有了解决这个问题所需的一切,但没有任何事情能按预期工作。

旁注:我在同一个 .dll 中成功调用了不同的函数。另外,我已经尝试使用 StringBuilder 来构造 RecipientList 参数。

感谢您的任何建议!

【问题讨论】:

  • 收件人[0] = INCLUDE_ONLYUSERIDS; -- 这不会变成收件人[0] = 1;这与收件人[0] = '1' 不同; - C# 等价物是收件人[0] = (char)1;
  • 收件人[4] = Char.MinValue;的目的是什么?
  • 在有效的代码中,行 strcat(strcpy(&recipients[1], rID), "\n"): 生成的字符串是什么样的,即 \n 是从哪里复制的到? 512字节之后?或者它是否足够聪明,可以截断未使用的收件人 [] 的任何部分?距离我的 C++ 时代已经有一段时间了,请原谅我
  • James B - 我试图绝对确保我的字符串是多终止的,你在以后的评论中猜对了。

标签: c# c++ dll pinvoke unmanaged


【解决方案1】:

TLDR:

string recipientList = (char) 1 + "CA\n\0";

根据 Jeffora 的评论进行了一些挖掘......他可能是对的,我的第一个建议可能不会给你任何帮助。我写了一些测试代码,这是我发现的:

当您使用此代码时:

string recipientList = "1CA\n\0";

在编组为 LPStr 后,函数内部最终得到的字节是:

49-67-65-10-0

这看起来像你想要的。

当您使用此代码时:

char[] recipients = new char[512];
recipients[0] = '1';
recipients[1] = 'C';
recipients[2] = 'A';
recipients[3] = '\n'; // also tried '\r', then '\n'
recipients[4] = Char.MinValue;
string paramValue = recipients.ToString();

编组为 LPStr 后在函数内部获得的字节是:

83-121-115-116-101-109-46-67-104-97-114-91-93

这是“System.Char[]”的 ASCII。因此,您肯定需要使用编码器将 char[] 转换为您想要的字符串。

至于为什么第一个字符串不起作用...我怀疑您的 DLL 确实在寻找值 '1',而不是 '1' (49) 的 ascii 字符。在有效的代码中,您说:

recipients[0] = INCLUDE_ONLYUSERIDS;

其中 INCLUDE_ONLYUSERIDS = 1

试试这个:

string recipientList = (char) 1 + "CA\n\0";


原始答案:
我认为问题在于 Char.ToString() 将 char 视为 unicode 字符,但您将其编组为 LPStr,而不是 LPWStr。 LPStr 正在寻找一串 ANSII 字符。

要解决此问题,请尝试使用字节数组。

byte[] recipients= new byte[512];
 ...
//  Pass this string to SimplePGPEncryptFile
string recipientsToPass = System.Text.ASCIIEncoding.ASCII.GetString(recipients);

【讨论】:

  • 相当肯定 Marshaller 负责将 .NET Unicode 字符串编组为请求的 ASCII 字符串,因为他已通过 MarshalAs 属性指定字符串类型为 ASCII:msdn.microsoft.com/en-us/library/…
  • 嗯,是的,你是对的,这可能不会给你买血腥的东西。我应该在午夜之后停止尝试回答问题:P
  • 但是编组器不也自动将 0 添加到字符串的末尾吗?我认为这就是他试图对收件人做的事情[4] = Char.MinValue;
  • 我收回它,他确实需要某种编码器,因为仅仅调用 Char.ToString() 是不行的。查看我修改后的答案
  • 只是一个小提示,char[].ToString() 正如您所注意到的,它返回类型名称('System.Char[]')的字符串表示形式。但是,这并不意味着您需要使用编码类将其转换为字符串,您只需要使用正确的过程,即 string s = new string(someCharArray);编组器负责处理 unicode 与 ascii。
【解决方案2】:

您是否尝试更改调用约定? 在 C++ 中,您使用 pascal 调用约定声明函数,在 C# 中声明为 stdcall。 调用约定必须匹配。

【讨论】:

    猜你喜欢
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 2013-01-07
    • 2011-02-06
    • 1970-01-01
    • 1970-01-01
    • 2010-11-02
    • 2010-12-17
    相关资源
    最近更新 更多