Memory manipulations [C++ calling a C# dll (COM)]

Alexis Fouquet 1 Reputation point
2021-03-19T07:14:26.75+00:00

Hi all,

I am writing a C# DLL that I call from C++ using COM. It works well but though I try to follow the memory manipulation rules for this kind of usage, I am scared of not doing things the right way and have something like a memory leak.

My exposed C# functions all take structures as parameter, and each member of the structures are allocated and initialized by the C++ client. These members can be of 4 sorts :

  • Numerical values : they are just initialized, nothing more
  • LPSTR strings : they are allocated with CoTaskMemAlloc, and freed with CoTaskMemFree
  • BSTR strings : they are allocated with SysAllocString, and freed with SysFreeString
  • SAFEARRAY arrays : they are allocated with SafeArrayCreateEx, and freed with SafeArrayDestroy. They are arrays of structures, and each "sub-structure" can include BSTR, and even SAFEARRAYS of structures... In this case, I don't call the free functions for these "sub-members" as the top SafeArrayDestroy does it itself as far as I know.

Then I have two kinds of usages :

  1. "Sending" data. The exposed C# functions take a structure as input parameter. All the members are allocated by the client, then the function is called, and finally the free functions are called (as explained before)
  2. "Receiving" data. The exposed C# functions take a structure as output parameter. In this case, the C++ client doesn't allocate the members of the structures as I assume the C# DLL does it itself. I only set the member to "NULL" value. But after the call of the function, I call the free functions.

So my question is simple : Do you see anything chocking in my way of doing things? Any bad usage? I have tried to follow all the rules that I was able to pick from the Microsoft documentation, but I have to admit that I am not confident with all this.

I will share some code as an answer to my post ;)

Many thanks,

Alexis

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,321 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,548 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Alexis Fouquet 1 Reputation point
    2021-03-19T07:16:44.467+00:00

    Ok so as I said, here is a simplifed version of my code ;)

    The C# code :

    Somewhere, these structures are defined :

    public struct ST_SUB_DATA
    {
        public double dSubValue;
        [MarshalAs(UnmanagedType.BStr)]
        public string tBSTRSubString;
    }
    
    public struct ST_DATA
    {
        public double dValue;
        public string tLPSTRString;
        [MarshalAs(UnmanagedType.BStr)]
        public string tBSTRString;
        public ST_SUB_DATA[] lstSubDataArray;
    }
    

    Then here is my COM interface :

    public interface MyManagedDLLInterface
    {
        void MySendFunction(ST_DATA a_stData);
        void MyReceiveFunction(out ST_DATA a_rstData);
    }
    
    public class MyManagedDLL : MyManagedDLLInterface
    {
        public void MySendFunction(ST_DATA a_stData)
        {
            // Do something using the a_stData content
        }
    
        public void MyReceiveFunction(out ST_DATA a_rstData)
        {
            a_rstData = new ST_DATA();
    
            a_rstData.dValue = 5.0;
            a_rstData.tLPSTRString = "lpstr value";
            a_rstData.tBSTRString = "bstr value";
    
            a_rstData.lstSubDataArray = new ST_SUB_DATA[2];            
            a_rstData.lstSubDataArray[0].dSubValue = 6.0;
            a_rstData.lstSubDataArray[0].tBSTRSubString = "bstr value sub 1";           
            a_rstData.lstSubDataArray[1].dSubValue = 7.0;
            a_rstData.lstSubDataArray[1].tBSTRSubString = "bstr value sub 2";
        }
    }
    

    The C++ code :

    For The C++, the structures are :

    struct __declspec(uuid("ed018e6a-a784-312a-9c04-3cd86c5d2b48"))
    ST_SUB_DATA
    {
        double dSubValue;
        BSTR tBSTRSubString;
    };
    
    struct __declspec(uuid("37491c45-d668-3cfa-bff4-080b5e136355"))
    ST_DATA
    {
        double dValue;
        LPSTR tLPSTRString;
        BSTR tBSTRString;
        SAFEARRAY * lstSubDataArray;
    };
    

    Then here is the C++ code (I only show the functions call, not the interface instanciation, etc)

    Send function :

    void MySendFunction()
    {
        ST_DATA stData = {};
    
        // Double value
        stData.dValue = 5.0;
    
        // LPSTR
        stData.tLPSTRString = (LPSTR)CoTaskMemAlloc(12);
        strcpy(stData.tLPSTRString, "lpstr value");
    
        // BSTR
        stData.tBSTRString = SysAllocString(CString("bstr value"));
    
        // SAFEARRAY
        IRecordInfoPtr clDataRecordInfo = NULL;
        GetRecordInfoFromGuids(__uuidof(MyManagedDLL::__MyManagedDLL), 1, 0, 0, __uuidof(MyManagedDLL::ST_DATA), &clDataRecordInfo);
        if (clDataRecordInfo != NULL)
        {
            SAFEARRAYBOUND clDataSafeArrayBound;
    
            memset(&clDataSafeArrayBound, 0, sizeof(clDataSafeArrayBound));
            clDataSafeArrayBound.cElements = 2;
            clDataSafeArrayBound.lLbound = 0;
    
            a_rstData.lstSubDataArray = SafeArrayCreateEx(VT_RECORD, 1, &clDataSafeArrayBound, (PVOID)clDataRecordInfo);
            if (a_rstData.lstSubDataArray != NULL)
            {
                void* pvDataTab = NULL;
                ST_DATA* pstDataTab = NULL;
    
                ::SafeArrayAccessData(a_rstData.lstSubDataArray, &pvDataTab);
                pstDataTab = reinterpret_cast<ST_DATA*>(pvDataTab);
    
                if (pstDataTab != NULL)
                {
                    pstDataTab[0].dSubValue = 6.0;
                    pstDataTab[0].tBSTRSubString = SysAllocString(CString("bstr value sub 1"));
    
                    pstDataTab[1].dSubValue = 7.0;
                    pstDataTab[1].tBSTRSubString = SysAllocString(CString("bstr value sub 2"));
                }
    
                ::SafeArrayUnaccessData(a_rstData.lstSubDataArray);
            }
        }
    
        // Call the C# function
        clMyManagedDLLInterface->MySendFunction(stData);
    
        // Free
        SysFreeString(stData.tBSTRString);
        CoTaskMemFree(stData.tLPSTRString);
        SafeArrayDestroy(a_rstData.lstSubDataArray);
    }
    

    Receive function :

    void MyReceiveFunction()
    {
        ST_DATA stData = {};
    
        // Initializations (the "NULL" sets are maybe useless...)
        stData.dValue = 0.0;
        stData.tLPSTRString = NULL;
        stData.tBSTRString = NULL;
        stData.lstSubDataArray = NULL;
    
        // Call the C# function
        clMyManagedDLLInterface->MyReceiveFunction(&stData);
    
        // Do something with the data
        // - To get the lpstr values in a char array, I just use a simple strcpy
        // - To get the bstr values in a char array, I use a _bstr_t intermediate variable (_bstr_t tBSTRString(stData.tBSTRString); strcpy(acCharArray, tBSTRString))
        // - To get the value from the safe array, I use SafeArrayAccessData and SafeArrayUnaccessData
    
        // Free
        SysFreeString(stData.tBSTRString);
        CoTaskMemFree(stData.tLPSTRString);
        SafeArrayDestroy(a_rstData.lstSubDataArray);
    }
    

    So, does anybody see any problem in this code?

    Many thanks for the help!

    Alexis

    0 comments No comments