【问题标题】:DllImport vs ComImportDllImport 与 ComImport
【发布时间】:2017-10-15 12:06:04
【问题描述】:

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

[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。我唯一的猜测是差异化因素是前者是一个函数,而后者是一个接口。

【问题讨论】:

  • ComImport 在这种情况下是可选的。你可以删除它。它在 COM coclass 上非常有用(你想在上面调用 C# 的 new 的 .NET 类)

标签: c# c++ winapi com pinvoke


【解决方案1】:

IShellItem 是一个 COM 接口。 COM 通常不能与调用外部代码的 [DllImport] 方式进行比较。 COM 更漂亮,一个接口声明了一整套您可以调用的方法,就像您在 C# 语言中使用 interface 关键字所做的那样。

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

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

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

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

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

【讨论】:

  • 如何知道某物是否是 COM (IShellItem) (SHCreateItemFromParsingName) 以及如何知道何时使用 CoCreateInstance() 以及何时使用工厂函数?我指出的文档没有说明它们应该如何实现,也没有说明 COM 接口的 GUID。
  • “接口”和“功能”这两个词是很好的提示。 COM 编程变得晦涩难懂,因此这些提示不一定是好的线索。新一代的程序员从不故意编写任何 COM 客户端代码,也不知道它的全部内容。它仍然很热而且很重,但是 UWP 完全基于 COM。但是包裹得很重,语言投影使它不可见。用漂亮的 C# 风格的界面将可怕的部分包装在库中是非常普遍的。使 COM 成为现代代码的汇编语言编程技能。
【解决方案2】:

DllImportComImport无关。

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

ComImportdoesn't really import anything。这是在 c# 中定义 COM 接口的手动方式,typically used 用于自定义/IUnknown 接口或自动化不兼容类型。

我唯一的猜测是区别因素是前者是一个函数,而后者是一个接口。

不,ComImport 也可以应用于类。

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

最大的区别是什么?

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

一个很大的不同是这些方法(或函数)是如何向世界公开或宣传的。

Windows 上的典型 .dll 文件导出它们的函数到 .dll 文件的 EXPORTS 表中。您可以通过在 Microsoft Dependency Viewer 中打开 DLL 来查看正在导出的函数。

这包括以下内容:

  • User32.dll 中的PeekMessage
  • Kernel32.dll
  • 中的DeleteFile
  • 我们的好朋友SHCreateItemFromParsingNameshell32.dll

现在要调用上述函数,您必须使用[DllImport],正如我们所讨论的那样。

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

COM 可以在 .EXE 或 .DLL 中实现(只是稍微混淆一下)。在依赖项查看器中打开 COM .dll 只会列出所有 COM 服务器必须实现的 COM 注册/注销功能。

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

更多

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

【讨论】:

  • 能否使用 ComImport 而不是 DllImport 来“导入” SHCreateItemFromParsingName 函数?我不明白这两件事的本质区别。
  • 所以如果它是一个函数,你只需 DllImport 并调用它,如果它是一个抽象类(就 C# 而言是接口),你需要 COM 来操作它吗?
  • @user8056359 没有
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
  • 2014-10-30
  • 2011-12-10
  • 2020-02-19
相关资源
最近更新 更多