DllImport 与 ComImport

DllImport vs ComImport

我正在努力思考平台调用服务、组件对象模型等概念,但我发现很难理解什么是不同的职责以及不同的职责所在。我一直在研究包含以下内容的代码:

[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
internal static extern object SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, ref Guid riid);

[ComImport]
[Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItem

据我了解,前者导入一个创建 shell 项的函数,后者导入创建它们所需的类型(接口)。

我不明白的是为什么一个使用 DllImport 而另一个使用 ComImport。它们各自的文档中没有说明使用哪种方法,文档中也没有提供 COM 对象的 GUID。我唯一的猜测是,区分因素是前者是一个函数而后者是一个接口。

DllImportComImport无关。

DllImport 在 .NET 中本质上定义了一个函数原型,以便稍后在本机 DLL 中调用函数。这种方法调用一般称为P/InvokePlatform Invoke的缩写)

ComImport doesn't really import anything. It's a manual way for defining COM interfaces in c# and is typically used 用于自定义/IUnknown 接口或与自动化不兼容的类型。

My only guess is that the differentiating factor is that the former is a function and the latter an interface.

不,ComImport 也可以应用于 类。

编辑: 我想我明白你现在想要什么了,见下文

最大的不同是什么?

好的,最终我们有两种可能调用本机代码的方法(假设目前我们的 COM 示例是用本机代码编写的)。为什么有两种不同的方式?

一个很大的区别是这些方法(或功能)是如何向世界公开或宣传的。

典型的 .dll 文件 Windows export 它们在 EXPORTS table 中的功能在 .dll 文件中。您可以通过在 Microsoft Dependency Viewer 中打开 DLL 来查看正在导出哪些函数。

这包括:

  • PeekMessageUser32.dll
  • DeleteFileKernel32.dll
  • 我们的好朋友SHCreateItemFromParsingNameshell32.dll

现在,为了调用上面的内容,您必须使用我们已经讨论过的 [DllImport]

COM 不同。COM 类 和方法 未在 EXPORTs table 中列出,所以它不可能 [DllImport] / p-调用它们。 COM 严重依赖 Windows 注册表; COM 类型库或后期绑定来完成工作。

COM 可以在 .EXE 或 .DLL 中实现(只是为了稍微混淆一下)。在依赖项查看器中打开 COM .dll 仅列出所有 COM 服务器必须实现的 COM registration/unregistration 函数。

例如查看 mapishell.dll COM 服务器(MS Office 的一部分),即使我们知道存在功能,我们也看不到任何内容(除了注册)被导出。

更多

如果您想了解有关 COM 工作原理的更多信息,请查看 my other answer here 了解更多信息。

IShellItem 是一个 COM 接口。一般而言,COM 与调用外部代码的 [DllImport] 方式相比效果不佳。 COM 更高级,一个接口声明了一整套您可以调用的方法,就像您在 C# 语言中使用 interface 关键字所做的那样。

在实践中,您总是需要一个具体的 class 来实现接口,无论是在 C# 中还是在 COM 中。在 COM 术语中称为 coclass,这样的 classes 完全隐藏在视图之外。通常用 C++ 编写,这种语言通常对互操作的支持很差,但 COM 提供了使该 C++ 代码可从几乎任何语言调用的协议。隐藏 class 实现是关键因素,完全隐藏讨厌的小 C++ 细节。像构造函数、内存管理、继承、对象布局、异常、从一种语言移植到另一种语言的特性。

您总是需要使用工厂函数来创建 class 对象。一种通用的方法就是您正在谈论的方法, CoCreateInstance() 辅助函数非常常用。您只需要提供 CLSID,即 coclass 的 guid。然后 COM 运行时负责查找实现 coclass 的 EXE 或 DLL,加载它并获取 IClassFactory 接口以创建对象。必须注册 COM 服务器是至关重要的,注册表中的键告诉它哪个可执行文件负责该工作。

还有第二种方法,即由库显式导出的工厂函数,如 SHCreateItemFromParsingName()。它避免了我在上一段中提到的所有问题。 DirectX 不常见,但也不罕见,它是另一个使用工厂函数(如 D3D11CreateDevice())的库的好例子。微软何时更喜欢工厂函数而不是 coclass 并不是很明显,除了可能不鼓励从脚本语言使用这样的库。

如果接口由 coclass 实现,那么您可以简单地使用 new IShellItem() 来创建对象。请注意创建接口实例的奇怪之处,这在 C# 中是毫无意义的。但有意用于 COM 客户端代码。 C# 编译器在后台生成代码以启动对象工厂管道。

但是由于 SHCreateItemFromParsingName 工厂函数是从 DLL 导出的正常函数,就像其他任何函数一样,您现在需要使用 [DllImport] 来声明它。