繁体   English   中英

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

[英]Creating C# to C++ bridge: Why do I get a AccessViolationException when calling DLL?

我正在探索在第3方应用程序的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方函数后立即发生。 我怎样才能解决这个问题?

同样,在C程序中也很难使用此功能。 从函数返回字符串的情况很少得到支持。 存在内存管理问题,尚不清楚谁拥有该字符串。 在大多数情况下,要求调用者获得字符串的所有权并在使用后释放它。 这对于您的函数来说效果不佳,因为返回了字符串文字,程序将崩溃。

.NET pinvoke编组器也需要解决此问题。 额外的问题是它不能使用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();

从您的问题看来,这是场景:

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

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

ProcessA中加载的X.DLL是否具有与ProcessB中加载的X.DLL进行通信的机制?

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

为了提取这些数据,您需要一个由X.DLL定义的查询接口,该接口可以跨进程(可能是套接字)进行通信。

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

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

顺便说一句,如果只需要执行一次查询,只需将硬代码存储在X.DLL中并转储到磁盘,然后方便地进行探索:-)

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

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

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

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

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

  • 输入char *参数= c#字符串
  • 返回char * = IntPtr(使用PtrToStringAnsi)
  • 输出char *参数= c#StringBuilder-并确保在调用该函数之前将其预分配足够大的空间(即= new StringBuilder( size ))。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM