从C#调用非托管dll的困难

3

我很费劲地想弄清楚为什么我无法从我的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参数。

感谢任何建议!


1
recipients[0] = INCLUDE_ONLYUSERIDS; -- 这不会变成 recipients[0] = 1;,这与 recipients[0] = '1'; 不同 - C# 的等效语句是 recipients[0] = (char)1;。 - jeffora
recipients[4] = Char.MinValue; 的目的是什么? - James King
在运行正常的代码中,这一行strcat(strcpy(&recipients[1], rID), "\n"):生成的字符串是什么样子的?即'\n'被复制到了哪里?在512个字节之后吗?还是它足够聪明以截断未使用的recipients[]部分?离我上次使用C++已经有一段时间了,请谅解。 - James King
James B - 我试图绝对确保我的字符串是以空字符结尾的,你在后面的评论中猜对了。 - David Montgomery
2个回答

2
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

这是ASCII码表示“System.Char []”。因此,您需要使用编码器将该char[]转换为所需的字符串。至于为什么第一个字符串无法正常工作......我猜测您的DLL实际上正在寻找值“1”,而不是ASCII字符“1”(49)。在有效的代码中,您说:
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会通过MarshalAs属性将.NET Unicode字符串转换为请求的ASCII字符串进行编组,因为他已经指定了字符串类型为ASCII:http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype.aspx - jeffora
我原以为PInvoke会自动添加一个终止的空字符,但是当我测试数据时,除非你显式地添加一个,否则在被调用的方法中没有尾随的零... 我猜这就是为什么OP添加了一个的原因。 - James King
Jeffora和James成功了——DLL正在寻找1,而不是'1'!我是否提到过我有多么喜欢这个网站?这是自DejaNews首次推出以来,在线资源提高开发人员生产力的最大进步。 - David Montgomery
SO已经帮助我摆脱了无数次的脑力困境......无论我何时发布问题,白天或晚上:)感谢您的投票! - James King
1
只是一个小提示,char[].ToString() 如您所述返回类型名称的字符串表示形式('System.Char[]')。但是,这并不意味着您需要使用编码类将其转换为字符串,您只需要使用正确的过程即可,例如 string s = new string(someCharArray); marshaller 会处理 Unicode 和 ASCII。 - jeffora
显示剩余4条评论

0

你尝试更改调用约定了吗? 在 C++ 中,您声明带有 Pascal 调用约定的函数,在 C# 中则使用 stdcall。 调用约定必须匹配。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接