创建C#到C ++桥:为什么在调用DLL时会出现AccessViolationException?

时间:2011-03-09 02:24:48

标签: c# c++ dll dllimport

我正在探索在第三方应用程序的DLL插件和C#应用程序之间建立桥梁的想法。我正在编写插件DLL和C#应用程序。该插件将被加载到第三方应用程序中,然后我想使用C#中的插件来间接从第三方应用程序获取数据。

我能够从C#中成功调用DLL中的导出函数。例如:

C ++ DLL:

extern "C" __declspec(dllexport) char * HelloFromDll()
{
    char *result;
    result = "Hello from my DLL";
    return result;
}

C#:

using System.Runtime.InteropServices;

[DllImport(@"MyDll.dll")]
private static extern string HelloFromDll();

然后我可以从C#调用此DLL函数并在UI中显示该字符串。但是,只要我创建一个从我的第三方应用程序调用函数的导出函数,我就会得到一个AccessViolationException。例如,

extern "C" __declspec(dllexport) char * GetData()
{
    char *result;
    result = 3rdPartyLibrary::SomeFunction();
    return result;
}

通过一些测试,一旦我调用第三方功能,似乎就会发生错误。我该如何解决这个问题?

3 个答案:

答案 0 :(得分:3)

此功能在C程序中也很难使用。从函数返回字符串是一种支持不足的方案。存在内存管理问题,目前尚不清楚谁拥有该字符串。在大多数情况下,调用者应该获得字符串的所有权并在使用后释放它。这对你的函数来说效果不好,程序会崩溃,因为你返回了一个字符串文字。

.NET pinvoke marshaller也需要解决这个问题。由于额外的问题,它无法使用C代码使用的分配器。它将调用CoTaskMemFree(COM分配器)。这导致XP上出现无法识别的内存泄漏,导致Vista和Win7崩溃。

只是不要写这样的C代码。始终让调用者传递字符串的缓冲区。现在没有猜测谁拥有记忆。像这样:

extern "C" __declspec(dllexport) void HelloFromDll(char* buffer, int bufferSize)
{
    strcpy_s(result, bufferSize, "Hello from my DLL");
}

使用这样的C#代码:

[DllImport("foo.dll", CharSet = CharSet.Ansi)]
private static extern void HelloFromDll(StringBuilder buffer, int bufferSize);
...
var sb = new StringBuilder(666);
HelloFromDll(sb, sb.Capacity);
string result = sb.ToString();

答案 1 :(得分:2)

从您的问题看来,情况就是这样:

ProcessA(第三方应用) - >加载X.DLL - >初始化插件 - >做其他事情。

ProcessB(您的C#应用​​程序) - >加载X.DLL - >调用GetData();

ProcessA中加载的X.DLL是否有任何与ProcessB中加载的X.DLL对话的机制?

如果没有,那么这种方法是有缺陷的。您的代码可能会崩溃,因为“3rdPartyLibrary”类尚未在您的C#应用​​程序中初始化,因为它是完全不同的DLL副本。

为了提取这些数据,您需要一个X.DLL定义的查询接口,它可以跨进程交换,也可以是套接字?

然后ProcessB与此接口对话并提取数据。如果使用套接字,则X.DLL将实现服务器和客户端代码,其中GetData()将使用此机制(可能是套接字)并查询数据并将其返回。

所以:ProcessA中的X.DLL应该像服务器一样。 并且:ProcessB中的X.DLL(或写一个Y.DLL)应该像客户端一样从ProcessA获取此信息。

顺便说一句,如果查询只需要完成一次,只需要在X.DLL中硬编码并转储到磁盘,然后以你的方便探索: - )

答案 2 :(得分:1)

通常,返回的char *需要作为IntPtr返回:

[DllImport(@"MyDll.dll")]
private static IntPtr HelloFromDll();

然后,您需要将该IntPtr转换为字符串:

string retVal=Marshal.PtrToStringAnsi(HelloFromDll());

P / Invoke中的字符串有点困难。我的一般经验法则是:

  • 输入char * parameter = c#string
  • 返回char * = IntPtr(使用PtrToStringAnsi)
  • 输出char * parameter = c#StringBuilder - 并确保预分配足够大 之前(即=新的StringBuilder( size ))调用函数。