A Few Options for Crossing Module Boundaries

It’s common to build complex software systems mixing components written in different languages.

For example, you may have a GUI written in C#, and some high-performance component written in C++, and you need to exchange data between these.

In such cases, there are several options. For example:

  1. COM: You can embed the C++ high-performance code in some COM component, exposing COM interfaces. The C# GUI subsystem talks to this high-performance component using COM interop.
  2. C-interface DLL: You can build a C-interface native DLL, “flattening” the C++ component interface using C functions. You can use PInvoke declarations on the C# side to communicate with the C++ component.
  3. C++/CLI: You can build a bridging layer between C++ and C# using C++/CLI.

Each one of these options have pros and cons.

For example, the C++/CLI approach is much easier than COM. However, C++/CLI is restricted to clients written in C# (and other .NET languages); instead COM components can be consumed by a broader audience.

The C-interface DLL option is also widely usable, as C is a great language for module boundaries, and many languages are able to “talk” with C interfaces. However, in this case you are flattening an object-oriented API to a C-style function-based interface (instead, both COM and C++/CLI maintain a more object-oriented nature).

Moreover, both COM and C++/CLI are Windows-specific technologies; on the other hand, a C interface resonates better with cross-platform code.

 

A Subtle Bug with PInvoke and Safe Arrays Storing Variant Bytes

When exchanging array data between different module boundaries using safe arrays, I tend to prefer (and suggest) safe arrays of direct types, like BYTEs, or BSTR strings, instead of safe array storing variants (that in turn contain BYTEs, or BSTRs, etc.).

However, there are some scripting clients that only understand safe arrays storing variants. So, if you want to support such clients, you have to pack the original array data items into variants, and build a safe array of variants.

If you have a COM interface method or C-interface function that produces a safe array of variants that contain BSTR strings, and you want to consume this array in C# code,  the following PInvoke seems to work fine:

[DllImport("NativeDll.dll", PreserveSig = false)]
pubic static extern void BuildVariantStringArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
  out string[] result);

So, if you have a safe array of variants that contain BYTEs, you may deduce that such a PInvoke declaration would work fine as well:

[DllImport("NativeDll.dll", PreserveSig = false)]
pubic static extern void BuildVariantByteArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
  out byte[] result);

I’ve just changed “string[]” to “byte[]” in the declaration of the “result” out parameter.

Unfortunately, this doesn’t work. What you get as a result in the output byte array is garbage.

The fix in this case of safe array of variant bytes is to use an object[] array in C#, which directly maps the original safe array of variants (as variants are marshaled to objects in C#):

[DllImport("NativeDll.dll", PreserveSig = false)]
pubic static extern void BuildVariantByteArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
  out object[] result);

And then manually convert from the returned object[] array to a byte[] array, for example using the C# Array.CopyTo method; e.g.:

// Get a safe array of variants (that contain bytes).
object[] data;
BuildVariantByteArray(out data);

// "Render" (copy) the previous object array 
// to a new byte array.
byte[] byteData = new byte[data.Length];
data.CopyTo(byteData, 0);

// Use byteData...

A variant is marshaled using object in C#. So a safe array of variants is marshaled using an object array in C#. In the case of safe arrays of variant bytes, the returned bytes are boxed in objects. Using Array.CopyTo, these bytes get unboxed and stuffed into a byte array.

The additional CopyTo step doesn’t seem necessary in the safe array of string variants, probably because strings are objects in C#.

Still, I think this aspect of the .NET/C# marshaler should be fixed, and if a PInvoke declaration clearly states byte[] on the C# side, the marshaler should automatically unbox the bytes from the safe array of variants.