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

发布于 2024-10-21 12:38:53 字数 802 浏览 2 评论 0原文

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

我能够从 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 中显示该字符串。但是,一旦我创建一个从我的第 3 方应用程序调用函数的导出函数,我就会收到 AccessViolationException。例如,

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

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

I'm exploring the idea of building a bridge between a DLL plugin for a 3rd party app and a C# app. I'm writing both the plugin DLL and the C# application. The plugin will be loaded into the 3rd party app and then I want to use call the plugin from C# to indirectly get data from the 3rd party app.

I am able to successfully call an exported function from the DLL from C#. For example:

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();

I can then call this DLL function from C# and display the string in the UI. However, as soon as I create an export function that calls a function from my 3rd party app, I get an AccessViolationException. For example,

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

Through some testing, the error seems to occur as soon as I make a call to a 3rd party function. How can I fix this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

下雨或天晴 2024-10-28 12:38:53

从你的问题来看,情况似乎是这样的:

ProcessA (3rd party App) -->加载 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 中并转储到磁盘,然后根据您的方便进行探索:-)

From your question it seems that this is the scenario:

ProcessA (3rd party App) --> loads X.DLL --> initializes the plugin --> does other stuff.

ProcessB (Your C# App) --> loads X.DLL --> calls GetData();

Does X.DLL loaded in ProcessA have any mechanism to talk to X.DLL loaded in ProcessB?

if not then this approach is flawed. Your code probbably crashes because "3rdPartyLibrary" class hasn't been initialised in your C# app as it is completely different copy of the DLL.

For you to extract this data you need a query interface defined by X.DLL which can talk across processes, maybe sockets?

Then ProcessB talks to this interface and extracts the data. if using sockets, then your X.DLL would implement both server and client code, where your GetData() would use this mechanism (maybe sockets) and query the data and return it.

So : X.DLL in ProcessA should act like a server.
And: X.DLL (or write a Y.DLL) in ProcessB should act like a client and get this information from ProcessA.

btw, if the query is only needed to be done once, just hard code this is in X.DLL and dump to disk, and then explore at your convinience :-)

东京女 2024-10-28 12:38:53

这个函数在 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();

This function is very difficult to use in a C program as well. Returning strings from functions is a poorly supported scenario. There's a memory management problem, it isn't clear who owns the string. In most cases the caller is expected to take ownership of the string and free it after using it. That's not going to work out well for your function, the program will crash since you returned a string literal.

The .NET pinvoke marshaller needs to solve this problem as well. With the extra problem that it cannot use the allocator that's used by the C code. It is going to call CoTaskMemFree (the COM allocator). That causes an undiagnosable memory leak on XP, a crash on Vista and Win7.

Just don't write C code like this. Always let the caller pass the buffer for the string. Now there's no guessing who owns the memory. Like this:

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

With your C# code like this:

[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();
诗化ㄋ丶相逢 2024-10-28 12:38:53

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

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

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

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

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

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

Generally, a returned char* needs to be returned as an IntPtr:

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

Then, you'll need to convert that IntPtr into a string:

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

Strings are a bit difficult in P/Invoke. My general rule of thumb is:

  • Input char* parameter = c# string
  • Return char * = IntPtr (use PtrToStringAnsi)
  • Output char* parameter = c# StringBuilder - and be sure to pre-allocate it large enough
    before (ie = new StringBuilder(size)) calling the function.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文