【问题标题】:Creating C# to C++ bridge: Why do I get a AccessViolationException when calling DLL?创建 C# 到 C++ 的桥:为什么在调用 DLL 时会出现 AccessViolationException?
【发布时间】:2011-03-09 02:24:47
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c# c++ dll dllimport


    【解决方案1】:

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

    【讨论】:

    • 最初我也认为这是与访问字符串内存有关的问题。但是,第一个函数 HelloFromDll() 完美无缺。然而,虽然字符串内存可能仍然是一个问题,但我认为更大的问题是@PrashantGupta 所描述的。
    • 如前所述,它仅适用于 XP。您的代码在 Vista 和 Win7 上爆炸。不确定进程互操作发生了什么,这听起来像是另一个问题。
    • 我使用的是 Windows 7,它实际上并没有爆炸。
    • 好的,我建议您将开发机器与您的软件一起提供。
    【解决方案2】:

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

    ProcessA (3rd party App) --> 加载 X.DLL --> 初始化插件 --> 做其他事情。

    ProcessB (Your C# App) --> 加载 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 中并转储到磁盘,然后在您方便的时候进行探索:-)

    【讨论】:

    • 是的,我认为您已经确定了问题所在。我怎样才能让不同的进程相互交谈?此外,如果可能,我会尽快让 ProcessA 充当服务器,而 C# 应用程序直接充当客户端。
    【解决方案3】:

    一般情况下,返回的 char* 需要作为 IntPtr 返回:

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多