Search code examples
c#c++winapicompinvoke

DllImport vs ComImport


I'm trying to wrap my head around the concepts of Platform Invocation Services, Component Object Model etc, but I find it hard to understand what is what and where different responsibilities lie. I've been studying code which contains the following:

[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

As I understand it, the former imports a function which creates shell items and the latter imports the type (interface) needed to create them.

What I don't understand is why one uses DllImport and the other ComImport. There is nothing in their respective documentation which states which method to use nor does the documentation provide the GUID for COM objects. My only guess is that the differentiating factor is that the former is a function and the latter an interface.


Solution

  • IShellItem is a COM interface. COM in general does not compare that well to the [DllImport] way of calling external code. COM is much fancier, an interface declares an entire set of methods you can call, much as you'd do in the C# language with the interface keyword.

    In practice you always need a concrete class that implements the interface, both in C# and in COM. Called a coclass in COM lingo, such classes are completely hidden from view. Often written in C++, a language that normally supports interop very poorly, but COM provides the protocol to make that C++ code callable from just about any language. Hiding the class implementation is the crucial ingredient, completely hiding the nasty little C++ details. Like constructors, memory management, inheritance, object layout, exceptions, features that port so poorly from one language to another.

    You always need to a use a factory function to create the class object. A universal way is the one you are talking about, the CoCreateInstance() helper function is very commonly used. All you need to supply is the CLSID, the guid of the coclass. The COM runtime then takes care of finding the EXE or DLL that implements the coclass, loading it and obtaining the IClassFactory interface to get the object created. Having to register the COM server is crucial, keys in the registry tell it what executable file takes care of that job.

    And then there is the secondary way, a factory function that is explicitly exported by the library, like SHCreateItemFromParsingName(). It avoids all the goo that I mentioned in the previous paragraph. Not as common, but not unusual, DirectX is another good example of a library that uses factory functions, like D3D11CreateDevice(). It is not very obvious exactly when Microsoft prefers a factory function over a coclass, other than perhaps discouraging using such a library from a scripting language.

    If the interface would have been implemented by a coclass then you could have simply used new IShellItem() to create the object. Note the wonkiness of creating an instance of an interface, something that never makes sense in C#. But intentional for COM client code. The C# compiler generates the code under the hood to get the object factory plumbing going.

    But since the SHCreateItemFromParsingName factory function is a normal exported function from a DLL, like any other, you now need to use [DllImport] to declare it.