C# DllImport问题

4
我的问题比较笼统,因此我不需要确切的答案,但希望能给我一些方向,帮助我解决以下问题:
在我的工作场所,我主要使用C#进行编程。 我们与一家第三方公司合作,并获得了一个本机C++ dll文件,我们需要使用它。 由于我需要的C++方法没有以易于从C#引用的方式暴露出来,因此我将dll封装在另一个本机C++ Dll中。
现在我有两个本机C++ dll文件,其中一个封装了另一个。
我创建了一个小型C#控制台应用程序,调用我在C++中创建的方法。 我的方法签名如下:
[DllImport("HashMethodWrapper.dll")]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string CreateHash(
            string input,
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder output);

在我的控制台应用程序中,一切正常,我总是能够在结果中接收到我期望的字符串。但是当我将它移到我创建的 Web 服务或 Web 应用程序中(因为这就是我真正需要它的地方),我发现我接收到的字符串是垃圾,而且不一致。好像我只是得到了一些已经丢失的内存引用之类的东西,但这只是我的猜测......我不知道为什么会发生这种情况,因为在我的控制台应用程序中一切都正常。有没有人可以给我指点一下呢?谢谢!编辑:我认为可能与一些未固定的对象有关,所以我尝试在一个 fixed 语句中调用该方法,例如:
unsafe public static string CreateHashWrap(string pass)
{
    String bb;
    StringBuilder outPass = new StringBuilder();
    fixed (char* resultStr = CreateHash(pass, outPass))
    {
        bb = new String(resultStr);
    }
    return bb;
}

...但这对我来说仍然不够。这是固定对象的正确方法吗?

第2次编辑:在C++中,方法签名看起来像这样:

extern "C" __declspec(dllexport) char *CreateRsaHash(char *inputPass, char *hashPass);

第三次编辑: 我将该方法的签名更改为

extern "C" __declspec(dllexport) bool CreateRsaHash(char *inputPass, char *hashPass);

我需要翻译的内容是:and the return value im looking for is placed in the *hashPass参数。

现在,我创建了一个简单的控制台应用程序来测试它。当我将DllImport插入到我的主类中,并直接调用该方法时,一切都很顺利。但是,当我将DllImport移动并将该方法包装在不同的类中,并从控制台的'Main'方法调用该类时,我会得到一个StackOverflow异常!

有人有任何想法为什么会发生这种情况吗?


你能展示一下在C/C++中如何声明函数头吗?这会很有帮助的 :) - Simon Mourier
杀掉API的作者,显然存在内存泄漏问题... - leppie
为什么??? 你能解释一下为什么吗,以及为什么从签名中这么清楚吗?希望我们还能修复它! - gillyb
返回指向任何非常量“对象”的指针是一个确定的迹象......方法sig可能是“错误的”,但从外观上看,它不会返回一个常量值......也许它确实会,我不知道,但它刚刚用斧子刺瞎了我的眼睛... - leppie
那我该怎么修复这个问题呢?返回一个const char*更好吗? - gillyb
1
如果可能的话,但我不知道如何实现,因为我可以推断出返回值会因每次调用而改变。最好的方法是传递预分配的缓冲区(例如具有容量的StringBuilder),并指定每个传递的缓冲区的大小。否则,您可以使用固定的byte/char[]。 - leppie
4个回答

2

1

从稀疏的信息中很难知道具体情况,但是如果非要猜测一下的话,我认为您需要确保固定输出对象。此外,我可能会将输出参数更改为其他类型,因为StringBuilder 的工作方式相当奇怪。

我知道,如果您分配了一个对象,它会获得一个指针,但这并不意味着它不会移动。因此,如果您尝试将托管对象的指针传递到非托管环境中,则需要确保告诉 GC "固定" 内存,以避免其在您使用时被移动。

以下是我所说的固定版本的简单示例:

string input = "...";
StringBuilder output = new StringBuilder();
var handle = System.Runtime.InteropServices.GCHandle.Alloc(output, GCHandleType.Pinned);
try
{
    CreateHash(input, output);
}
finally
{
    handle.Free();
}

关于StringBuilder,我在某个地方读到过,如果值发生变化,我需要一个stringbuilder,因为“String”是不可变的,在这种情况下无法完成工作。这是真的吗? - gillyb
关于固定对象,我认为那可能是问题所在,所以我尝试进行了一些固定。我会把它添加到我的问题中,请问你能告诉我我所做的和你用GCHandle.Alloc显示给我的有什么区别吗? - gillyb
是的,事实上,我不使用StringBuilder参数作为输出,并且目前正在努力更改方法签名。我从该方法获取的真正输出是返回值。 - gillyb
1
@justin.m.chase:你的最后一句话是个人观点,不一定是最佳实践。我喜欢所有类型都用同样的方式突出显示,所以选择大写版本,而将所有关键字突出显示为不同颜色。 - cjk
我尝试按照您发布的方式固定对象,但是立即出现异常:“对象包含非基元或非可插拔数据”。这是什么问题? - gillyb
显示剩余2条评论

0
我会考虑将其封装在一个C#共享程序集/dll中,而不是一个c++ dll中,然后尝试让您的控制台应用程序与dll一起工作。无论如何,使用这种方式来封装外部依赖是一个好的实践。
否则,一些传统问题是32位与64位,共享库的加载路径。它真的只是一个字符串还是更复杂的东西?

0

我找到了解决问题的方法,现在感觉有点(如果不是真的!)愚蠢... :-|

我在C++中使用了LoadLibrary()方法来动态调用其他本地dll中的方法。问题在于我没有给这个方法任何路径,只是dll文件名。在.net中,它会在当前文件夹中搜索,但似乎在本机代码中不起作用。

我的编程实践中更大的问题显然是我没有完全覆盖本地C++ dll中的错误处理!

尽管我在这个页面上收到的所有答案都不是无用的...

一旦我发现我有目录路径的问题,我就遇到了关于尝试访问损坏内存等不同异常。然后我需要创建固定对象,并为我的StringBuilder对象声明一个大小。

感谢大家的帮助!!

:)


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