Reputation: 457
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.
Upvotes: 5
Views: 4212
Reputation:
DllImport
has nothing to do with ComImport
.
DllImport
defines essentially a function prototype in .NET in order to later call a function in a native DLL. This type of method invokation is generally called P/Invoke (short for Platform Invoke).
ComImport
doesn't really import anything. It's a manual way for defining COM interfaces in c# and is typically used for custom / IUnknown
interfaces or Automation-incompatible types.
My only guess is that the differentiating factor is that the former is a function and the latter an interface.
Well no, ComImport
can be applied to classes too.
EDIT: I think I see what you are after now, see below
OK so at the end of the day we have two ways to potentially call native code (assuming for the moment our COM example is written in native code). Why have two different ways?
Well one big difference is how such methods (or functions) are exposed or advertised to the world.
Typical .dll files on Windows export their functions in the EXPORTS
table in the .dll file. You can see what functions are being exported in a DLL by opening it in say Microsoft Dependency Viewer.
This includes things like:
PeekMessage
in User32.dllDeleteFile
in Kernel32.dll andSHCreateItemFromParsingName
in shell32.dllNow in order to call the above you must use [DllImport]
as we have discussed.
COM is different. COM classes and methods are not listed in a EXPORTs table so it is imposible to [DllImport]
/ p-invoke them. COM relies heavily on the Windows Registry; COM Type Libraries or late-binding to get the job done.
COM can either be implemented in .EXEs or .DLLs (just to confuse things slighty). Opening up a COM .dll in dependency viewer only lists the COM registration/unregistration functions that all COM servers must implement.
e.g. Looking at mapishell.dll COM server (a part of MS Office) we see nothing (apart from registration) being exported even though we know there is functionality present.
If you want to know more about how COM works, take a look at my other answer here for more information.
Upvotes: 2
Reputation: 942040
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.
Upvotes: 6