DllImport和ComImport的区别

4

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

[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。我唯一的猜测是,区别因素在于前者是一个函数,后者是一个接口。
链接: - SHCreateItemFromParsingName函数 - IShellItem接口

在这种情况下,ComImport是可选的。你可以将其删除。它在COM coclasses上非常有用(.NET类,您想要在其中调用C#的“new”)。 - Simon Mourier
2个回答

5

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

实际上,无论是在C#中还是在COM中,您总是需要一个实现接口的具体类,这在COM术语中被称为。这些类完全隐藏在视图之外。它们通常用C++编写,C++通常很难支持交互操作,但COM提供了协议,使得可以从任何语言中调用C++代码。隐藏类的实现是至关重要的,彻底隐藏了讨厌的C++细节。例如构造函数、内存管理、继承、对象布局、异常等功能,这些功能在不同语言之间的传递效果很差。

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

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

如果接口由coclass实现,则您可以简单地使用new IShellItem()来创建对象。请注意,在C#中创建接口实例的怪异方式,这在C#中永远没有意义。但对于COM客户端代码很有用。 C#编译器在幕后生成代码以启动对象工厂管道。

但由于SHCreateItemFromParsingName工厂函数是DLL的普通导出函数,就像其他任何函数一样,因此现在需要使用[DlImport]来声明它。


1
如何判断某个东西是COM(IShellItem)还是不是(SHCreateItemFromParsingName),以及如何知道何时使用CoCreateInstance()和何时使用工厂函数?我指向的文档没有说明它们应该如何实现,也没有COM接口的GUID。 - user8056359
1
“接口”和“函数”这些词是很好的提示。COM编程变得越来越模糊,因此这些提示不一定是好的线索。有一整代程序员从未有意识地编写过任何COM客户端代码,也不知道它是什么。然而,它仍然非常重要,UWP完全基于COM。但是,它被大量封装,语言投影使其不可见。用一个漂亮的C#风格的接口库包装可怕的部分是相当普遍的。使COM成为现代代码的汇编语言编程技能。 - Hans Passant

2

DllImportComImport无关。

DllImport在.NET中定义了一个函数原型,以便稍后调用本地DLL中的函数。这种方法调用通常称为P/Invoke(缩写为平台调用)。

ComImport并没有真正导入任何内容。它是一种手动定义c#中COM接口的方式,通常用于自定义/IUnknown接口或不兼容自动化类型。

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

好吧,不是这样的,ComImport也可以应用于类。

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

有什么大的区别?

好的,最终我们有两种潜在的调用本地代码的方式(暂时假设我们的COM示例是用本地代码编写的)。为什么会有两种不同的方式呢?

嗯,一个重要的区别在于这些方法(或函数)如何向世界公开或广告宣传。

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

enter image description here

这包括以下内容:
  • User32.dll中的PeekMessage
  • Kernel32.dll中的DeleteFile
  • 我们的好朋友,在shell32.dll中的SHCreateItemFromParsingName

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

COM不同于此。 COM类和方法未列在EXPORTs表中,因此无法进行[DllImport] / p-invoke。 COM在Windows注册表上有很大依赖;COM类型库或后期绑定可以完成工作。

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

例如,查看MS Office的一部分mapishell.dll COM服务器时,我们什么都没有看到(除了注册),尽管我们知道存在功能。

enter image description here

更多

如果您想了解COM的工作原理,请查看这里的我的其他答案获取更多信息。


SHCreateItemFromParsingName函数能否使用ComImport进行“导入”,而不是使用DllImport?我不理解这两者之间的本质区别。 - user8056359
所以如果它是一个函数,你只需要DllImport它并调用它,而如果它是一个抽象类(就C#而言,它是一个接口),你需要使用COM来操作它? - user8056359
@user8056359 不。 - user585968

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接