【问题标题】:Direct memory access of C++ DLL in C#C#中C++ DLL的直接内存访问
【发布时间】:2013-12-10 12:28:10
【问题描述】:

我已经在 stackoverflow 上搜索过这个问题,但没有找到这个确切的问题。特别是,当 C# 端管理内存时,我发现了大量关于检索 C++ 字符串引用 (char**) 的问题,而不是相反。

情况如下。我有一个我自己编写的 DLL(在 VC++ 2012 上编写)。它从文件中加载和解析数据,然后让使用 DLL 的任何人以多种方式访问​​这些数据(出于性能原因,通过实际的直接内存访问)。任何使用此 DLL 的 C++ 程序显然都没有问题。 C# 看起来确实如此......

一点上下文:

DLL 提供接受 char**(char* 数组)参数的导出函数,其元素被设置到 DLL 中的内存位置,然后在调用后由 DLL 的用户进一步处理。

示例可能如下所示(示例性实现):

int myFunction(char* dataArray[], int row)
{
    for (int i= 0; i < _something; ++i)
    {
        dataArray[i] = _some_char_ptr
    }
}

这就是 C++ 中被调用者的实现可能的样子:

char** data = new char*[__num_fields];

myFunction(data, __some_row);

for (int i= 0; i < __something; ++i)
{
    cout << data[i] << endl;
}

特殊 - 因此与我发现的所有问题不同 - 关于这一点,被调用者分配了一个 char* 列表,该列表在调用点之后指向 DLL 本身分配的内存位置。

现在,我必须说,我只使用 C#,因为您可以立即获得非常简单的 GUI。我正在使用的 C# GUI 工具用于对 DLL 加载的数据进行完整性测试。我对 C# 一点也不深。到目前为止,我所尝试的根本没有成功,而且我知道由于 C# 没有任何类型的指针概念,可能无法解决这个问题。我在 Java 中使用 JNA 加载 DLL 时发现的类似问题导致我不再使用 Java 进行此类测试。

现在我尝试了一些 C# 的东西,包括。上下文:

public static class DLLTest
{
    // delegate object for the function:
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate int MyFunction_Type(out string[] dataArray, int row);

    public static MyFunction_Type MyFunction { get; private set; }

    public static void Load()
    {
        IntPtr dllAddress = DllUtilities.LoadLibrary(@"__dll_path");
        IntPtr procAddress = DllUtilities.GetProcAddress(dllAddress, "myFunction");
        MyFunction = (MyFunction_Type)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(MyFunction_Type));
    }
}

然后我像这样调用函数:

string[] data = new string[__num_fields];
DLLTest.MyFunction(out data, __row)

使用普通字符串、整数、布尔值之类的类似代码的代码就像魅力一样。只是这些字符串数组一直困扰着我:-)

注意:我无法更改 DLL 的工作方式,因为我是为我的老板编写的,他在 C++ 和 Delphi 程序中都很好地使用了它。它运行良好,我们不打算更改 DLL 本身的任何内容,因为它确实很好。

非常感谢任何帮助或澄清,非常感谢,问候

【问题讨论】:

  • CallingConvention.StdCall 为什么?
  • 这是来自互联网的复制粘贴行。我使用'extern "C"' 导出DLL 的函数,它利用stdcall 调用约定,从而禁用名称修饰。这条确切的线也适用于所有其他有效的 DLL 函数。
  • extern "C" 禁用 C++ 修改。它没有设置调用约定。
  • 在 x64 上 Std 和 C 是相同的约定,但在 x86 上它们是不同的,因此 C# 必须匹配 C++ 声明
  • 这是非常危险的代码,你的 C++ 代码很容易破坏 GC 堆,因为它不知道传递的数组到底有多长。它需要知道 __num_fields 才能安全地执行此操作,但它不需要。还有一个重要的内存管理问题,这些字符串需要再次释放,而 C# 代码无法做到这一点。不要这样做。

标签: c# c++ dll pinvoke


【解决方案1】:
out string[] dataArray

char** 参数的错误翻译。应该是:

IntPtr[] dataArray

调用 C# 代码然后在调用函数之前分配数组。就像 C++ 代码一样。

IntPtr[] data = new IntPtr[__num_fields];
int retval = DLLTest.MyFunction(data, __row);

然后,您可以使用Marshal 类访问内存,以读取IntPtr 指针后面的非托管内存。

调用约定看起来有点奇怪。如所写,C++ 函数是cdecl。正如 Hans 所说,DLL 还必须有某种机制来知道传入的数组有多长。

【讨论】:

  • DLL 中的每个导出函数都有这个前缀:extern "C" __declspec(dllexport)。为了便于阅读,我没有把它放在这里。
  • @poljpocket 没关系。除非你明确写成__stdcall,否则函数仍然是__cdecl
  • 您在询问 DLL 如何提供指向其内存的指针。这意味着 DLL 必须以IntPtr 的形式为您提供这些指针。从非托管内存读取的函数可以在Marshal 类中找到。他们都接受IntPtr
  • 顺便说一下,您可能会考虑将 Delphi VCL 用于您的 GUI,因为您已经拥有从 Delphi 访问 DLL 的代码。使用指针可能比 C# 更容易,并且在 GUI 构建方面非常有效。另一方面,如果你不喜欢 Pascal,那么它有多好可能都无所谓!
  • 完美!非常感谢!我刚刚从 DLL 中成功获取了我的列表!
猜你喜欢
  • 2013-03-31
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
  • 2010-10-13
  • 2018-01-21
  • 2010-11-26
  • 2019-08-12
  • 1970-01-01
相关资源
最近更新 更多