【问题标题】:Why is the "ref" keyword needed when making a call to a function in a DLL?为什么在调用 DLL 中的函数时需要“ref”关键字?
【发布时间】:2019-08-21 22:19:18
【问题描述】:

我试图弄清楚为什么我需要使 DLLImport 函数的参数为​​“ref int XXX”,而不仅仅是“int XXX”。对于后者,这会导致“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”问题......但是当我在参数中添加“ref”时......它工作得很好。例如:

    [DllImport("someDLL.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern int SomeDLL_GetDevices(ref int devices);

^以上作品

    [DllImport("someDLL.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern int SomeDLL_GetDevices(int devices);

^ 上述崩溃和烧毁

我不是来自 C# 的 C++ 调用及其编组方面的导出,所以如果有人能解释为什么一个有效而一个无效,我将不胜感激。

【问题讨论】:

  • C/C++ 中的函数签名是什么? general 中不需要 ref 关键字,但您尝试调用的特定函数需要它。
  • 我猜本机函数需要一个 int 指针,而不是一个 int。如果您将 devices 的值作为 int 传递,则解释为指针的值不太可能恰好指向内存中的有效位置。
  • DWORD SomeDLL_GetDevices (WORD *devices)
  • 我猜如果它只是一个 WORD 它将是 int,但 WORD * 是 int 指针,它是 ref int。 Tbh 我错过了 *... 没看到...

标签: c#


【解决方案1】:

ref int 表示“被调用者期望一个指向 32 位整数的指针,而编组器期望它是一个托管指针”。

指向整数和整数的托管指针是两个完全不同的东西。一个是指针大小的——通常是 64 位——一个绝对是 32 位。一个是值,一个是描述从哪里获取包含值的变量。它们不可互换,您需要正确处理。

如果您不清楚编组层使用的约定来解决这个问题,请在编写更多编组代码之前弄清楚它。 您正在与一个不具备 C# 提供给您的类型安全或内存安全的系统进行交互,因此您需要确保保留运行时的不变量。通常,运行时会确保维护运行时不变量,但您正在描述如何与不在运行时中的代码进行交互,因此请确保它是正确的。

尝试一些东西直到它起作用并不是一个好方法。如果您继续这样做,您可能会引入崩溃、内存泄漏和更细微的错误(即安全漏洞)。阅读文档、书籍或视频,或让同事向您解释以便您了解其工作原理。然后,您将能够自信地编写 p/invoke 代码,而不是猜测和希望。

【讨论】:

  • 谢谢埃里克。采取的建议。 Tbh 我很困惑,因为我错过了 C++ 签名说 WORD * 而不是 WORD 的地方。我把它读成 WORD 这就是为什么我在想为什么把 ref 放在那里......但我可以看到它是一个 WORD 指针,即一个 int 指针。我将复习我的原始类型指针“位数”知识,因为我对此很不满意。谢谢:D
  • 是的,而不是 ref long 期望指向 64 位整数的指针,即 long 否?
  • @slickchick2:正确。
  • @slickchick2: 如果它是一个指向变量的托管指针,那么运行时就知道它的一切:它是否可移动,它是在堆栈上还是在堆上,什么变量的类型是,等等。如果它是非托管指针,则运行时知道它是指针。如果是 IntPtr,那只是一个数字;运行时不能假设关于它的任何事情。这可以改变运行时在将值封送到非托管代码时采取的操作。
  • Eric 提到弄乱签名会导致细微的失败。 Here 是我自己微妙失败的一个说明性例子。我编组的数组似乎在 32 位机器上运行良好,但是当代码在 64 位机器上运行时,只有数组的第一个元素可以访问。
猜你喜欢
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 2012-05-01
  • 2012-03-22
  • 1970-01-01
  • 1970-01-01
  • 2011-02-21
  • 1970-01-01
相关资源
最近更新 更多