C-Style Array COM-Interop

You are developing an application with large arrays and you want the best of both worlds. You want to use the .NET Framework for the ease of creating managed applications but you also want to use Native Code so that you take advantage of hardware accelerators like GPGPUs (for which you would consider C++ AMP) or Auto-Vectorization

The managed application in this post performs vector addition and dot products on C-style arrays. It shows you how to pass C-style arrays to and from Native Code. There’s already a post on using C++ AMP from C# that uses P/Invoke. This post will show you how to do it through COM Interop with C++/ATL. 

The dot product and vector addition implementations are basic because there are already several posts on the Parallel Programming in Native Code blog to bring you up to speed with these new technologies for even faster execution. This post also assumes you are familiar with basic Visual Studio, C++ and C# development.

Let’s get started.

1. Start Visual Studio with elevated privileges. (It has to be elevated so you can register your COM object)

2. Create a new Visual C++ ATL Project. I called mine “NativeVectorOps”. (Since there’s existing documentation on how to create ATL projects as well, I won’t get into the details here)

3. Define the interface (this listing does not show the IDL attributes)

interfaceICVectorOps : IDispatch{

       HRESULT Add(DOUBLE a[], DOUBLE b[], int size, DOUBLE c[]);

       HRESULT DotProduct(DOUBLE a[], DOUBLE b[], int size, DOUBLE* c);

};

4. Build the ATL project. This will also register your COM DLL. [Note: If you build this as a 64-bit binary, you’ll have to set the Target Environment to “Microsoft Windows 64-bit on x64 (/env x64)” on your ATL project]
clip_image001[4]

5. Open a Visual Studio Command Prompt and create the Interop Assembly with the following command line:
tlbimp NativeVectorOps.DLL

This creates a new interop assembly named NativeVectorOpsLib.DLL

6. Disassemble the Interop Assembly. From the Visual Studio command prompt, run
ildasm NativeVectorOpsLib.DLL /out:NativeVectorOpsLib.il

7. Now it’s time to fix the Intermediate Language (IL). By default, tlbimp will only create an interop assembly that passes arrays as a pointer. Not a big deal. You could certainly use this. I just want to use the C-style arrays because I prefer not to use pointer arithmetic.

Open NativeVectorOpsLib.il in your favorite text editor.

8. You’ll have to change each method twice. Once for the interface and once for the class. We’ll start by fixing the Add method:
Before:

  .method public hidebysig newslot abstract virtual

          instance void Add([in] float64& a,

                             [in] float64& b,

                             [in] int32 size,

                             [in][out] float64& c)

runtime managed internalcall

 

 

After:

  .method public hidebysig newslot abstract virtual

          instance void Add([in] float64 [] marshal([+2]) a,

                             [in] float64 [] marshal([+2]) b,

                             [in] int32 size,

                             [in][out] float64 [] marshal([+2]) c)

runtime managed internalcall

 

The “&” is the interop assembly default and it signifies the parameter was passed as a pointer. The [] marshal([+2]) says it should be passed as a C-Style array. The marshal “+2” part is a zero-based index which tells the interop layer which method parameter contains the size of the arrays. In this case, +2 turns out to be the size parameter.

9. Now we’ll change the DotProduct method. Remember – you’ll have to do it twice.

Before:

  .method public hidebysig newslot virtual

          instance float64 DotProduct([in] float64& a,

                                       [in] float64& b,

                                       [in] int32 size)

runtime managed internalcall

After:

  .method public hidebysig newslot abstract virtual

          instance float64 DotProduct([in] float64[] marshal([+2]) a,

                                       [in] float64[] marshal([+2]) b,

                               [in] int32 size)

runtime managed internalcall

Again, we’re just replacing float64&with float64[] marshal(+2).

10. Save NativeVectorOpsLib.il

11. Now it’s time to reassemble the interop assembly. From the Visual Studio command prompt run the following command:
ilasm /dll NativeVectorOpsLib.il

This recreates the NativeVectorOpsLib.DLL assembly.

12. Create a managed project like a simple C# console application. I called mine VectorClient.

13. Decorate the Main method with the STAThreadattribute.

14. Add a reference to the NativeVectorOpsLib.DLL assembly.

15. Here’s the code:

 

    [STAThread]

    staticvoid Main(string[] args)

    {

        // Define a couple of arbitrary arrays

        double[] a = { 1.0, 1.0, 1.0 };

        double[] b = { 2.0, 3.0, 4.0 };

 

        // Instantiate the Native COM Object

        NativeVectorOpsLib.CVectorOps vectors = new CVectorOps();

 

        // Calculate and print the dot product

        double dotProduct = 0.0;

        dotProduct = vectors.DotProduct(a, b, a.Length);

        Console.WriteLine("{0} . {1} = {2}", FormatArray(a), FormatArray(b), dotProduct);

 

        // Calculate and print the vector addition

        double[] c = newdouble[a.Length];

        vectors.Add(a, b, a.Length, c);

        Console.Write("{0} + {1} = {2}", FormatArray(a), FormatArray(b), FormatArray(c));

    }

    publicstatic string FormatArray(double[] array)

    {

        StringBuilder sb = new StringBuilder();

        sb.Append("{ ");

        for (int i = 0; i< array.Length; i++)

            sb.Append(string.Format("{0} ", array[i]));

        sb.Append("}");

 

        return sb.ToString();

    }

 

And that’s what it takes to do C-style arrays with COM interop. That wasn’t too bad, was it?