Now there's a title to make your pulse race.
This post is ostensibly another in the how-to-write-a-Windows-Desktop-Search-protocol-handler series, but it's really just what-Matt-does-to-write-a-COM-object. I've done it for just about every COM object I've written; I have a feeling I'll be referring back to it.
And yes, that title does say ATL. It's C++ time.
I'm not going into the C++/.net COM object argument just now. I will at some point - I've got plenty to say about it - but for this, I'm using C++. Not everything is a .net shaped nail.
Of course, these tips make certain assumptions. I'm talking about COM objects that are in-process and that only implement other people's interfaces (think shell extensions, addins for Office, Visual Studio or Windows Desktop Search. Pretty much any kind of addins). Some of this won't work for situations such as scriptable components, custom objects for a custom application and so on. Your mileage may vary.
Right then. Fire up Visual Studio and create a new ATL project. We want a dll and we don't want it attributed. I'm a bit of a Luddite with regards to C++ attributes. I just know it's a code generation thing, and I'm old-skool enough to write this stuff long-hand, thanks. (That said, I'm reading the link above, and it does look rather interesting. Might have to have a play with this...)
I'm going to assume the project was called "dllname", and I'm going to leave it as an exercise to the reader to substitute "dllname" for the right name for the rest of this post.
1. Remove proxy stub project
I haven't once used this. I'm not implementing my own interfaces, so I don't need to provide any custom marshalling. Right click on the PS project and delete it.
2. Get rid of the typelib
Again, I'm not implementing my own interfaces, so I don't need to describe them to anyone. If I don't remove the typelib, ATL will register an essentially empty typelib in the registry for me, and will store the typelib as a resource in my dll. Both of these are unnecessary bloat. Trivial, yes, but easily prevented (we should still try to be as streamline as possible, even in this day of cheap memory and storage).
Firstly, open up the dllname.cpp file, and modify DllRegisterServer. Simply add a FALSE to the arguments, so that the typelib isn't registered:
HRESULT hr = _AtlModule.DllRegisterServer(FALSE);
Do a similar thing for DllUnregisterServer:
HRESULT hr = _AtlModule.DllUnregisterServer(FALSE);
Now we need to remove the typelib from the resources. Open dllname.rc, and in the resource view, right click on the dllname.rc node (not the dllname project node!). Select "Resource Includes". You're going to get a dialog with two big edit boxes. In the bottom one will be the line:
1 TYPELIB "dllname.tlb"
Don't delete the .idl file from the project! This does create the type library (.tlb) but the ATL wizards need this file.
3. Don't register an AppId
Again, never needed this. Open the dllname.rgs file and remove the AppId section. Easy peasy.
4. Enable QueryInterface debugging
This one's a corker. Saved the best til last. Once you've done this, every time a client calls one of your objects' QueryInterface methods, you'll get a string written via OutputDebugString with the class name, the name of the interface requested (from the registry) and a "failed" marker if the interface isn't supported.
This is an absolutely essential debugging aid when working with COM. Quite often the documentation will tell you what all the interfaces are, what each one does, but not which combination of interfaces to implement on a given object. Or, it only lists the interfaces for that particular API domain, and don't list the standard COM interfaces required, such as IPersistFile, etc.
Edit you stdafx.h and define _ATL_DEBUG_QI before including any atl header files.
Note that there's a bug in AtlDumpIID (the function that does all the magic) in atlbase.h. If the interface name isn't found in the registry, the code is supposed to output the raw IID, but the logic is wrong, and nothing gets output. It's fixed in Visual Studio 2005 and later, so if you have anything earlier, check out Craig Skibo's post on the problem. He offers a solution that you just have to copy into the file. It's a little verbose (I'd have just set a flag on success and checked that at the end of the file rather than have all those goto's...)
A top tip in conjunction with this is to use sysinternal's DebugView to capture all those messages to OutputDebugString, even when you're not in the debugger. Oh, and if you don't know what a raw IID actually refers to, don't forget Google. Or install the Platform SDK and Windows Desktop Search.
5. Cleanup object rgs scripts
Now, this one is for when you create a new object, rather than the first steps of a project like the rest. So, go on. Create an object. Now open it's .rgs file. The first thing we can get rid of is the typelib reference. We're not registering that typelib any more, so we can get rid of that. And I always get rid of "Programmable", and if you're not using ProgIds, get rid of those too.
And that's it. Just a couple of simple changes that I make every time I start a COM object.
PS. Whatever development you're doing (C++/.net/asp.net/whatever) you really, really want to use Microsoft's symbol server.