【问题标题】:How to convert a void* to a type that can be used in C#? Interoperability between C DLL and C#如何将 void* 转换为可在 C# 中使用的类型? C DLL 和 C# 之间的互操作性
【发布时间】:2025-12-01 11:05:01
【问题描述】:

我是一名 C/C++ 程序员,但我被要求更新一个用 C# 编写的程序以与设备通信。我的 C# 知识非常基础。

之前的版本完全是用C#写的,现在实际访问设备的API改成C了。我发现可以通过以下方式导入C函数API:

[DllImport("myapi.dll")]
public static extern int myfunct( 
                                 [MarshalAs(UnmanagedType.LPTStr)] string lpDeviceName,
                                 IntPtr hpDevice);

在 C 中这个函数原型是:

int myFunct( LPTStr lpDeviceName, HANDLE* hpDevice );

其中 HANDLE 定义为:

typedef void *HANDLE;

但是,此功能无法按预期工作。其实在C#代码中调用我应该声明什么样的类型并传递给C#方法呢?

感谢您的帮助,对于任何愚蠢的问题,我们深表歉意。

【问题讨论】:

  • 这是将 HANDLE 类型编组为 CLR 类型的正确方法。您的代码肯定有其他问题。你能告诉我们更多细节吗?
  • 我同意维尔的观点。显示您从哪里获得 hpDevice 您通过。一般来说,我希望它来自同一个库中的另一个函数。
  • 我试过这个 public IntPtr myHANDLE = new IntPtr();
  • 使用 IntPtr.Zero 作为默认值。不过,一般来说,您不会手动设置句柄 - 它们将由其他一些创建句柄的函数返回。除非这是设置句柄的函数,在这种情况下,将 ref IntPtr 作为类型传递。
  • 顺便说一句,我在测试 API 时让它在 C 上工作。我必须将它简单地声明为 HANDLE myHandle 并将其作为指针传递给函数,例如 myfunc(&myHandle)

标签: c# c++ dll interop void-pointers


【解决方案1】:

其实这是编组HANDLE *的错误方式。它会起作用,但在遇到异常时并不可靠。

您发布的函数看起来像一个对象创建函数(它将hpDevice 视为输出参数,并返回int 状态结果)。

编组它的正确方法取决于它正在创建的对象类型以及它是如何关闭的。假设HANDLE 是通过调用CloseHandle 关闭的(这对大多数但不是所有 HANDLE 对象都是如此),那么您可能可以使用从SafeHandleZeroOrMinusOneIsInvalid 继承的类型之一.例如,如果对象是注册表项,则使用SafeRegistryHandle;如果是文件,则使用SafeFileHandle

如果某个类型没有现有的安全句柄类型(但确实使用CloseHandle 关闭它),那么您必须定义自己的从SafeHandleZeroOrMinusOneIsInvalid 派生的安全句柄类型。如果它是某种不使用CloseHandle 关闭它的类型,那么您必须定义自己的安全句柄类型,该类型派生自SafeHandle

一旦确定了正确的SafeHandle派生类型,就可以在函数调用中使用它(以SafeFileHandle为例):

[DllImport("myapi.dll")]
public static extern int myFunct(
    [MarshalAs(UnmanagedType.LPTStr)] string lpDeviceName,
    out SafeFileHandle hpDevice);

【讨论】:

  • 我已经尝试过了,但它编译的结果不是好的。这个 HANDLE 用于连接到串行端口的设备,当我用 C 编写应用程序时,我刚刚将其声明为“HANDLE myHandle”并将其地址传递给函数,在 API 内部,这个 HANDLE 已初始化。我也尝试过使用 ref InPtr 没有任何运气。
  • 句柄是串行端口,还是用作“设备”的不透明句柄连接到串行端口? SafeFileHandle 只能在 HANDLE 是串行端口本身而不是不透明的设备句柄时使用。要检查的另一件事是calling convention 不匹配:C++ 默认为cdecl,C# 默认为stdcall
  • 我无法知道这个句柄是如何使用的,第三方 API 在调用这个函数时只是说他们“包装串行或 USB 连接”。正如我所说,我在尝试将其集成到 C# 项目之前进行了测试,我需要做的就是像 HANDLE h 一样声明一个句柄并将其地址传递给函数,它只是返回了正确的句柄而没有抱怨。
  • 嗯,C# 比这更安全。这就是为什么它不是那么简单。 :) 即使您不知道HANDLE 指的是什么,您也应该能够知道它是如何“关闭”的。你必须调用什么函数来关闭句柄?
  • DWORD WINAPI McpClose (HANDLE hDevice);
【解决方案2】:

您传递的是 IntPtr 而不是 ref IntPtr,定义应如下所示:

[DllImport("myapi.dll")]
public static extern int myfunct( 
    [MarshalAs(UnmanagedType.LPTStr)] string lpDeviceName,
    ref IntPtr hpDevice);

【讨论】:

  • 事实上你的解决方案是部分正确的,我在另一个论坛上了解到,最好传递一个 StringBuilder 而不是编组 LPTStr。
  • 这项工作只是使用了 Stringbuilder 而不是以这种方式编组的字符串。
最近更新 更多