question

AlexisFouquet-1686 avatar image
0 Votes"
AlexisFouquet-1686 asked ·

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

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

dotnet-csharpc++
· 1
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


You probably already know that if you do not want to have concerns about manual memory management, you can let the system to do it using “managed C++”, i.e. C++/CLR, where you can easily access libraries made in C#. However, apparently there is a reason to use COM and native C++ in your case.

0 Votes 0 ·

1 Answer

AlexisFouquet-1686 avatar image
0 Votes"
AlexisFouquet-1686 answered ·

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

·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.