2

I have a C++ MFC application which consumes C# COM wrapper. The issue is whenever I invoke a function inside wrapper, I am experiencing memory leak. Can anyone explain how to clean up the allocations that are made within the C# COM wrapper.

Below code blocks mimic what I was trying to do, can anyone provide me the references/rightway to pass the structure object/ clean up the memory allocation

C# wrapper exposed as COM

using System;
using System.Runtime.InteropServices;


namespace ManagedLib
{
    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct comstructure
    {
        
        public string[] m_strName;
         
        public UInt32[] m_nEventCategory;
    }


    [Guid("4BC57FAB-ABB8-4b93-A0BC-2FD3D5312CA8")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface ITest
    {
        
        comstructure  TestBool();

         
    }

    [Guid("A7A5C4C9-F4DA-4CD3-8D01-F7F42512ED04")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]    
    public class Test : ITest
    {
        public comstructure TestBool( )
        {
            comstructure testvar = new comstructure();

            testvar.m_strName = new string[100000];
            testvar.m_nEventCategory = new UInt32[100000];
            return testvar;
        }
                 
    }
}

C++ code


#include <iostream>
#include <afx.h>
#include <afxwin.h>         
#include <afxext.h>         
#include <afxdtctl.h>   
#include "windows.h"
#include "psapi.h"
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>          
#endif // _AFX_NO_AFXCMN_SUPPORT

#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS  
#include <atlbase.h>
#import "..\comlibrary\bin\Debug\comlibrary.tlb"
comlibrary::ITest* obj;
class mleak
{

public:


   void leakmemory()
   {
       comlibrary::comstructure v2;
       v2 = obj->TestBool();

       
   }
};
   int main()
   {


       CoInitializeEx(nullptr, COINIT_MULTITHREADED);
       CLSID clsid;
       HRESULT hResult = ::CLSIDFromProgID(L"ManagedLib.Test", &clsid);
       hResult = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
           __uuidof(comlibrary::ITest), (void**)&obj);
       std::cout << hResult;
       if (FAILED(hResult))
       {
           std::cout << "COM import failed!\n";
       }
        

       mleak m1;
       for (int i = 0; i < 600; i++)
       {
            
           m1.leakmemory();
           Sleep(100);
       }

       return 0;
   }

7
  • 1
    "I am experiencing memory leak" - How did you verify that there is a leak? Commented Dec 15, 2021 at 11:04
  • for every execution of m1.leakmemory(); memory holding up by my process is linearly increasing with the for loop @IInspectable Commented Dec 15, 2021 at 11:50
  • Since TestBool returns a object, I would expect that object needs to be manually disposed somehow. I'm no COM expert, but com objects are typically reference counted, so I would expect there to be a Release() method on the comstructure to decrease the refcount. Commented Dec 15, 2021 at 11:57
  • Exactly that's what I am searching for @JonasH, how to free the memory. Most of the examples that I found around are C# applications using C++ COM component , but I Couldn't find any relevant examples for my use case. Commented Dec 15, 2021 at 12:01
  • A linear increase in memory is not unusual when part of your code runs in a runtime environment that employs non-deterministic garbage collection, such as .NET's. Commented Dec 15, 2021 at 12:03

1 Answer 1

2

You should release memory if it's allocated and no-one else frees it, obviously. Here, the allocated memory is the .NET's string[] and uint[] which are represented as SAFEARRAY* in the native world.

But, long story short: you can't really use structs as return type for COM methods. It's causes not only copy semantics issues (who owns struct's field memory, etc.), but in general, it won't even work depending on struct size, etc. lots of trouble, COM methods should return 32/64 bit-sized variables (or void).

So you can fix this using COM objects instead of structs. For example:

[ComVisible(true)]
public interface IOther
{
    string[] Names { get; set; }
    uint[] EventCategories { get; set; }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Other : IOther
{
    public string[] Names { get; set; }
    public uint[] EventCategories { get; set; }
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITest
{
    Other TestOther();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Test : ITest
{
    public Other TestOther()
    {
        var other = new Other();
        other.Names = new string[100000];
        other.EventCategories = new UInt32[100000];
        return other;
    }
}

and in C++ side:

#include "windows.h"
#import "..\ManagedLib\bin\Debug\ManagedLib.tlb"

using namespace ManagedLib;

int main()
{
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    {
        ITestPtr test; // see https://stackoverflow.com/a/16382024/403671
        auto hr = test.CreateInstance(__uuidof(Test));
        if (SUCCEEDED(hr))
        {
            IOtherPtr other(test->TestOther());
            auto names = other->Names;

            // do what you want with safe array here
            // but in the end, make sure you destroy it
            SafeArrayDestroy(names);
        }
    }
    CoUninitialize();
    return 0;
}

Note: you can also use CComSafeArray to ease SAFEARRAY programming.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.