After importing a TLB, how do I convert from one type of _com_ptr_t to another?

Raymond Chen

The Microsoft C++ compiler has this weird feature called #import. You give it a type library (TLB) file, and it converts the type library into a C++ header file that exposes the types in the type library in a form of bunch of smart pointer types.

The documentation on how to uses these autogenerated smart pointers is kind of sparse. One thing it neglects to mention is how to convert one kind of _com_ptr_t to another. Suppose you have an IWidgetPtr (representing an IWidget) and you want to get the IToggleSwitchPtr interface from it.

Well, we see that there is a QueryInterface method, but it appears to produce raw pointers, not fancy _com_ptr_t pointers.

Aha, but we can ask for the raw pointer version of a _com_ptr_t: Use the & operator.

void FlipToggleSwitch(IWidgetPtr widget)
{
    IToggleSwitchPtr switch;
    HRESULT hr = widget.QueryInterface(__uuidof(switch), &switch);
    if (FAILED(hr)) _com_raise_error(hr);
    switch.Flip();
}

The parameters to the QueryInterface can be simplified (and made less error-prone) with the help of the IID_PPV_ARGS macro:

HRESULT hr = widget.QueryInterface(IID_PPV_ARGS(&switch));

There’s another way, which is even easier, but also even more invisible: Use the conversion constructor.

void FlipToggleSwitch(IWidgetPtr widget)
{
    IToggleSwitchPtr switch = widget; // conversion constructor!
    if (!switch) _com_raise_error(E_NOINTERFACE);
    switch.Flip();
}

The conversion constructor for _com_ptr_t lets you construct any type of _com_ptr_t from any other type of _com_ptr_t. It does so by using QueryInterface to get from one interface to the other. If the source is empty (wraps a nullptr) or if the query fails with E_NO­INTERFACE, then the constructed object is empty. If the query fails with any other error, then a _com_error exception is thrown.¹

This conversion operator is not marked explicit, so it can be implicitly invoked. This makes it super-convenient, but also super-eager to stick its nose in places it might not belong.

void SetInvertedPolarity(IWidgetPtr widget);

void PrepareGadget(IGadgetPtr gadget)
{
    SetInvertedPolarity(gadget); // compiles!
}

We are passing the wrong kind of smart pointer to Set­Inverted­Polarity, but it compiles anyway! The compiler automatically converts the IGadgetPtr to an IWidgetPtr in order to pass it to Set­Inverted­Polarity. This is great if gadgets are deluxe versions of widgets, and you expect the conversion to succeed. This is not so great if you didn’t mean to treat the gadget as a widget, and the attempt to call Set­Inverted­Polarity was really a mistake. Depending on how the Set­Inverted­Polarity function works, it may throw an exception or even crash on a null pointer if you give it a null widget. The super-eager conversion constructor led you into a trap.

The inner workings of the _com_ptr_t can be found in the header file comip.h.

¹ If you set a custom COM error handler, then the custom error handler is called.

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Henry Skoglund 0

    That the docs are “kind of sparse” is being generous.
    Actually I think that the #import keyword has got a kind of bad wrap because the attributes for it are not well understood but they can be very helpful. For example I remember using “inject_statement” to insert #pragma pack()s for TLBs containing structs that was being used both from VB6 and VC6. Since the two languages packed structs differently the inject_statement: attribute was a lifesaver when #importing those TLBs from VB6 into VC6.

  • philiplu 2

    #import was my doing, on the C++ compiler front-end team, a long time ago, probably around 1997. I’m a little surprised it’s still supported, and not at all surprised it hasn’t seen much attention since then. explicit certainly wasn’t a keyword back then.

    It was, as you say, a weird little feature, aimed to help with VB6/VC6 integration as Henry Skoglund notes. Delighted to see some mention of it, 25+ years later.

  • Adam Rosenfield 2

    1) Me on reading the title: “How does one import a Translation Lookaside Buffer? Oh, that’s a different TLB.”

    2) The #import feature here is also not to be confused with a different #import feature in the Objective-C language, which works like #include but with guaranteed include-once semantics (equivalent of #pragma once or use of header guards).

Feedback usabilla icon