February 5th, 2020

The various patterns for passing C-style arrays across the Windows Runtime ABI boundary

The Windows Runtime supports C-style arrays. These are contiguous blocks of memory that consist of multiple consecutive instances of the same type. (This is not to be confused with a Windows Runtime vector, which is an interface that resembles an array but which does not require any particular storage format.)

Arrays are kind of weird, because they aren’t “objects” (there is no identity), but they aren’t scalars either (they are variable-sized). And there are multiple patterns for passing these arrays across the ABI boundary, depending on who is allocating the memory, whether the size is known to the caller, and whether the memory is being passed into or out of the function.

  • PassArray: The caller passes a read-only array, and the implementation reads from it.
  • FillArray: The caller passes a write-only array, and the implementation fills it with data.
  • ReceiveArray: The implementation allocates a block of memory for the array and the caller receives a pointer to that block of memory, as well as the number of elements in the array.

Here’s a table, since people tend to like tables.

  PassArray FillArray ReceiveArray
Parameter Return value
Allocated by Caller Caller Callee Callee
Size Caller decides Caller decides Callee decides Callee decides
Freed by Caller Caller Caller Caller
Allocator Caller decides Caller decides COM allocator COM allocator
Policy Read-only Write-only Write-only Write-only
IDL void M(T[] value); void M(
  ref T[] value);
void M(
  out T[] value);
T[] M();
ABI HRESULT M(
  UINT32 size,
  _In_reads_(size)
  T* value);
HRESULT M(
  UINT32 size,
  _Out_writes_all_(
    size) T* value);
HRESULT M(
  _Out_ UINT32* size,
  _Outptr_result_buffer_all_(
    *size) T** value);
C++/WinRT void M(
 array_view<T const>
 value);
void M(
 array_view<T> value);
void M(
  com_array<T>&
  value);
com_array<T> M();
C++/CX void M(
  const Array<T>^
  value);
void M(
  WriteOnlyArray<T>^
  value);
void M(
  Array<T>&
  value);
Array<T>^ M();
C# void M(T[] value); void M(T[] value); void M(
  out T[] value);
T[] M();
VB Sub M(value As T[]) Sub M(value As T[]) Sub M(ByRef value
  As T[])
Function M()
  As T[]
JS function M(value
  : TypedArray)
function M(value
  : TypedArray)
function M()
 : TypedArray
Function M()
 : TypedArray

I gave the JavaScript prototypes in TypeScript notation so I could annotate the data types. The case of an out parameter in JavaScript is a bit more complicated than it looks. I’ll save that topic for another day.

Note that in the case of PassArray, the formal parameter at the ABI level is not declared const T*, even though the buffer is read-only.

Update: “Freed by” and “Allocator” rows added later.

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.

7 comments

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

  • R Pond

    The VB in this chart is slightly incorrect – it will work only with parentheses, not with square brackets, to denote the array elements.
    So, Sub M(value As T[]) should be Sub M(value As T()). Or Sub M(value() As T) – the () can go either after the variable name or after the type, but not both.

  • Henke37

    Wait, no option for a read and write array? That’s going to be awkward, especially for people who want to limit the amount of copying being done.

    • Raymond ChenMicrosoft employee Author

      You can pass a PassArray and a FillArray as separate parameters. No copying.

  • Simon Clarkstone

    Is “Function” at the end of the Javascript row supposed to have a capital F?

    You got the names “PassArray”, “FillArray”, and “ReceiveArray” from C++/CX, right? (e.g. they are used here: https://docs.microsoft.com/en-us/cpp/cppcx/array-and-writeonlyarray-c-cx?view=vs-2019 )

    Does the API promise only to write to the write-only arrays, and not even read what it has written?

    • Raymond ChenMicrosoft employee Author

      “I gave the JavaScript prototypes in TypeScript notation so I could annotate the data types.” And it’s the other way around: C++/CX got the names from the Windows Runtime. FillArrays are write-only. Not sure if they can be read-back before returning. (It shouldn’t matter, should it? Are you planning to pass device memory as a FillArray target?)

      • Kalle Niemitalo

        I could imagine a cross-process marshaler that temporarily maps most of the caller’s buffer to the callee process with write-only page permissions, and copies only any partial pages at the beginning and the end. Win32 does not have PAGE_WRITEONLY in Memory Protection Constants, though.

  • Kalle Niemitalo

    For the C++/CX parameter case:

    void M(Array<T>& value);

    error C3699: ‘&’: cannot use this indirection on type ‘Platform::Array’

    It needs a hat too:

    void M(Array<T>^& value);