November 30th, 2021

How can my C++/WinRT component pass a std::vector back to the caller?

The ReceiveArray pattern is the Windows Runtime pattern for how a function can return a C-style conformant array to its caller. In C++/WinRT, the projected version of the function is

// [out] parameter
void M(com_array<T>& value);

// return value
com_array<T> M();

A customer had a method that generated the result into a std::vector<int>. How do you return this to the caller? “There is no move constructor for com_array<int> that takes a std::vector<int>.”

That’s right, there is no constructor for com_array<int> that takes a std::vector<int>&&. If you think about it, there can’t possibly be one.

The com_array is required to use the COM task allocator to allocate its memory, because the memory is going to be passed back to the calling component, and the calling component is responsible for freeing the memory. This means that the memory allocator must be something language-agnostic, since the caller could be written in C# or Visual Basic or JavaScript or Rust or whatever.

On the other hand, std::vector uses the C++ free store to allocate memory.¹ This is an allocator specific to the C++ language. Actually, it’s even worse. It’s an allocator that is specific to a particular implementation of the C++ language. Code that uses one version of the compiler and runtime library cannot interoperate with code that uses a different version of the compiler and runtime library.

The allocators don’t agree, so you won’t be able to transfer ownership: The memory was allocated from some version of the C++ free store, but putting it in a com_array will result in the memory being freed by Co­Task­Mem­Free.

Okay, so you can’t move it into a com_array, but can you copy it?

Yes, and the com_array even has a special constructor for copying from a std::vector.

std::vector<int32_t> m_indices;

com_array<int32_t> GetIndices()
{
    return com_array<int32_t>(m_indices);
}

Starting in C++/WinRT version 2.0.200601.2, there’s a deduction guide that that deduces com_array<T> if you construct from a std::vector<T>, so you need only write

com_array<int32_t> GetIndices()
{
    return com_array(m_indices);
}

If you aren’t wedded to std::vector, you could generate the results directly into a winrt::com_array and return it, thereby avoiding a copy.

¹ You can override this by providing a custom allocator, say, by a custom allocator that obtains memory via Co­Task­Mem­Alloc. However, library types with custom allocators are going to create interop friction with other code that expects library types with the standard allocator.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

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

  • Henke37

    Specifically, providing a custom allocator to the stdlib template classes counts as an entirely different type. You might be able to add move semantics for constructing the com_array, but you lose the ability to move to normal std::vector templace instances that don’t use the custom allocator.