Steve's Blog

Random comments on .NET, debugging, and more.

COM/C++ Programming – the modern way

For a new project I’ve been working on I was forced into working with the world of C++ and COM for a sizable portion of it.  In my past life I’d previously worked on some large-ish projects involving COM (office interop in particular), but never on a fresh codebase from the ground up.

I’ve found that life in “modern COM” is significantly different than the “old school” COM I had worked with before.  A lot of this is probably very obvious to people working with COM/C++ for years, but for someone working in a mostly managed environment without much enterprise C++ experience, a lot of it wasn’t obvious.  Below are some observations.

Smart pointers are ridiculously good.

I know some people disagree with this (like Eric Lipert), but I can honestly say that my code using CComPtr<T> and equivalents has at least 95% less bugs than code using AddRef() and Release() everywhere.  Further, the code is significantly more readable, and I probably was 50% more productive.  Not having to reason about the correctness of a function (are all objects being released?) in almost all cases allows much more time for actually solving problems.

Further, using STL containers for dynamic memory allocation is significantly better than manually allocating storage.  Need a dynamic LPWSTR?  Use a vector<WCHAR> (although technically you can use a std::wstring and pass its buffer around, its not supported, and functions like length() behave differently than expected.  Need a BYTE[]?  Use a vector.  You get the point.

Lambda Expressions in C++11 are amazing.

It’s amazing, coming from C#, how much time and code lambda expressions can save you.  Lambda expressions get you two major things.

  1. Anonymous, inline functions
  2. Closures

Manually creating closure classes and functors, or constantly casting PVOID state objects in callbacks wastes time and makes code ugly.  Anonymous functions make using algorithms that take predicates or other functions much easier.  Converting code from callback + PVOID pairs to use functors and lambdas made my code cleaner and easier to read.  Being able to convert almost ANY instance method to a functor (or even normal function) with a couple lines of code is also a huge win.

The ATL is useful, but horribly documented.

I started my project not planning on using ATL, but after implementing IUnknown for the 10th time, decided this was a mistake.  Unfortunately, I don’t think there’s a single good guide online for making a simple COM object with ATL.  In addition, almost every tutorial I could find went something like “open up the <something> wizard and click next.”  I hate wizards when it comes to coding with a passion so I promptly ignored the tutorials.  Here’s my 3 step guide:

  1. Inherit CComObjectBase and your COM interface (or just IUnknown)
  2. Use BEGIN_COM_MAP, COM_INTERFACE_ENTRY, END_COM_MAP to implement your interface
  3. Create instances of your object with CComObject<YourClass>::CreateInstance.

That’s it!  Why is that not on the front page of the ATL docs on MSDN?  Note, you also need to define a CComModule somewhere in your library or you’ll get null reference exceptions in AddRef() on create.  Also, don’t forget ATL starts your refcount at 0, so you should AddRef immediately after CreateInstance.

IDL makes interface with .NET trivial

MIDL + TLBImp can round trip COM objects (and data structs) with almost 100% fidelity.  There are some gotchas though:

  • It seems like MIDL randomly will capitalize/lowercase members of structs and parameters.  I couldn’t figure out why, or the pattern, which is annoying.
  • TLBImp (because of limitations of type libraries) can’t handle variable sized OUT arrays, and instead define them as simple out variables in the managed interface.  The only way around this is to edit the IL (with ILdasm, ILasm), and add [] marshal([+1]) to the parameter.  The +1 tells the marshaller the index of the size parameter, 0 indexed left to right.  For example, you might end up with: [out] valuetype int[] marshal([+1]) pValues

Visual Studio 2012 C++ Unit Testing is really good

TDD is a great paradigm, but until 2012 there were few (no?) good, integrated solutions to unit test C++ code.  Visual Studio 2012 integrates C++ unit tests seamlessly into the IDE, just like managed unit tests.

Conclusion

The project that I started using these new technologies in is basically a rewrite of SPT for .NET 4.5 (I’ll be blogging about that soon).  It’s codebase is significantly cleaner and less buggy than the first version of SPT.  High code coverage with unit tests and smart pointers handling most COM objects contributed substantially to that.

blog comments powered by Disqus