Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplest way to move an array from C++ to C#, modify it, and pass it back to C++

Tags:

c++

c#

interop

com

I have a C# class library that contains methods that need to be used with an external application. Unfortunately this external application only supports external APIs in C/C++.

Now I've managed to get a very simple COM example working between a C++ dll and a C# DLL, but I am stuck on how I can move around array data.

This is what I've got so far, just as a little example I found on the web of communicating via COM:

DLL_EXPORT(void) runAddTest(int add1,long *result) {
    // Initialize COM.
    HRESULT hr = CoInitialize(NULL);

    // Create the interface pointer.
    IUnitModelPtr pIUnit(__uuidof(UnitModel));

    long lResult = 0;

    // Call the Add method.
    pIUnit->Add(5, 10, &lResult);

    *result = lResult;

    // Uninitialize COM.
    CoUninitialize();

}

This works fine to call the add method in my C# class. How can I modify this to take and return an array of doubles? (ill also need to do it with strings down the line).

I need to take an unmanaged array , pass this array to a C# class for some calculations, and then pass it back the results to the array reference specified in the original function call (unmanaged) C++.

I'll need to expose a function like this:


*calcin - reference to array of doubles

*calcOut - reference to array of doubles

numIN - value of size of input array

DLL_EXPORT(void) doCalc(double *calcIn, int numIn, double *calcOut)
{
      //pass the calcIn array to C# class for the calcuations

      //get the values back from my C# class

      //put the values from the C# class 
      //into the array ref specified by the *calcOut reference 


}

I think I can use a C++\CLI DLL for the external application so if this is easier than straight COM then i'll be willing to look at that.

Please be gentle as I am primarily a C# developer but have been thrown in the deep end of Interop and C++ .

like image 454
Alex Avatar asked Feb 22 '09 18:02

Alex


2 Answers

I experimented with this a while ago but have unfortunately forgotten how it all fitted together... for my purpose it turned out to be woefully slow so I cut out the C# and went back to all C++. When you say you're primarily a C# developer I hope you understand pointers because if you don't there's no way to be gentle.

Passing arrays basically came down to using CoTaskMemAlloc family of functions on the C++ side (http://msdn.microsoft.com/en-us/library/ms692727(VS.85).aspx) and the Marshal class on the C# side (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.aspx - which has methods like AllocCoTaskMem). For C# I ended up with a utility class:

public class serviceUtils
{
    unsafe public long stringToCoTaskPtr( ref str thestring )
    {
        return (long)Marshal.StringToCoTaskMemAnsi(thestring.theString).ToPointer();//TODO : what errors occur from here? handle them
    }

    unsafe public long bytesToCoTaskPtr( ref bytes thebytes, ref short byteCnt)
    {
        byteCnt = (short)thebytes.theArray.Length;
        IntPtr tmpptr = new IntPtr();
        tmpptr = Marshal.AllocCoTaskMem(byteCnt);
        Marshal.Copy(thebytes.theArray, 0, tmpptr, byteCnt);
        return (long)tmpptr.ToPointer();
    }

    public void freeCoTaskMemPtr(long ptr)
    {
        Marshal.FreeCoTaskMem(new IntPtr(ptr));//TODO : errors from here?
    }

    public string coTaskPtrToString(long theptr)
    {
        return Marshal.PtrToStringAnsi(new IntPtr(theptr));
    }

    public byte[] coTaskPtrToBytes(long theptr, short thelen)
    {
        byte[] tmpbytes = new byte[thelen];
        Marshal.Copy(new IntPtr(theptr), tmpbytes, 0, thelen);
        return tmpbytes;
    }
}

Just to throw some more code at you: this c++

#import "..\COMClient\bin\Debug\COMClient.tlb" named_guids raw_interfaces_only
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);   //Initialize all COM Components
COMClient::IComCalculatorPtr pCalc;
// CreateInstance parameters
HRESULT hRes = pCalc.CreateInstance(COMClient::CLSID_ComCalculator);
if (hRes == S_OK) {
    long size = 5;
    LPVOID ptr = CoTaskMemAlloc( size );
    if( ptr != NULL )
    {
        memcpy( ptr, "12345", size );
        short ans = 0;
        pCalc->changeBytes( (__int64*)&ptr, &size, &ans );
        CoTaskMemFree(ptr);
    }
}

CoUninitialize ();   //DeInitialize all COM Components

return 0;
}

called this c#

    public short changeBytes(ref long ptr, ref int arraysize)
    {
        try
        {
            IntPtr interopPtr = new IntPtr(ptr);                
            testservice.ByteArray bytes = new testservice.ByteArray();
            byte[] somebytes = new byte[arraysize];
            Marshal.Copy(interopPtr, somebytes, 0, arraysize);
            bytes.theArray = somebytes;

            CalculatorClient client = generateClient();
            client.takeArray(ref bytes);
            client.Close();
            if (arraysize < bytes.theArray.Length)
            {
                interopPtr = Marshal.ReAllocCoTaskMem(interopPtr, bytes.theArray.Length);//TODO : throws an exception if fails... deal with it
            }
            Marshal.Copy(bytes.theArray, 0, interopPtr, bytes.theArray.Length);
            ptr = interopPtr.ToInt64();

            arraysize = bytes.theArray.Length;

            //TODO : do we need to free IntPtr? check all code for memory leaks... check for successful allocation
        }
        catch(Exception e)
        {
            return 3;
        }

        return 2;
    }

Sorry, but I don't have the time to work all this out and explain it properly, hopefully this will give you pointers in the right direction, at the very least some things to google. Good Luck

PS : I got all the info to write this stuff off the net, so it is out there.

like image 169
Patrick Avatar answered Oct 19 '22 23:10

Patrick


I think I can use a C++\CLI DLL for the external application so if this is easier than straight COM then i'll be willing to look at that.

If you don't have much COM experience (and arrays are significantly not simple in COM) then C++/CLI wrapper around the 3rd party will likely be easier.

It will also only involve a single marshalling stage (native <-> managed) rather than the extra step the necessary COM Callable Wrapper you will need for managed <-> COM interfacing).

like image 44
Richard Avatar answered Oct 19 '22 22:10

Richard