将对象从 C# COM 库传递到 C++ 应用程序时如何修复内存泄漏
How to fix memory leak when passing object from C# COM library to a C++ application
我有一个使用 C# COM 包装器的 C++ MFC 应用程序。问题是每当我在包装器中调用一个函数时,我都会遇到内存泄漏。任何人都可以解释如何清理在 C# COM 包装器中进行的分配。
下面的代码块模仿了我试图做的事情,谁能给我提供 references/rightway 来传递结构对象/清理内存分配
作为 COM 公开的 C# 包装器
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++代码
#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;
}
显然,如果内存已分配且没有其他人释放它,您应该释放它。在这里,分配的内存是 .NET 的 string[]
和 uint[]
,在本地世界中表示为 SAFEARRAY*
。
但是,长话短说:您不能真正将结构用作 COM 方法的 return 类型。它不仅会导致复制语义问题(谁拥有结构的字段内存等),而且通常,它甚至不会根据结构大小等工作。很多麻烦,COM 方法应该 return 32/64位大小的变量(或 void)。
所以您可以使用 COM 对象而不是结构来解决这个问题。例如:
[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;
}
}
在 C++ 方面:
#include "windows.h"
#import "..\ManagedLib\bin\Debug\ManagedLib.tlb"
using namespace ManagedLib;
int main()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
{
ITestPtr test; // see
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;
}
注意:您也可以使用 CComSafeArray
来简化 SAFEARRAY
编程。
我有一个使用 C# COM 包装器的 C++ MFC 应用程序。问题是每当我在包装器中调用一个函数时,我都会遇到内存泄漏。任何人都可以解释如何清理在 C# COM 包装器中进行的分配。
下面的代码块模仿了我试图做的事情,谁能给我提供 references/rightway 来传递结构对象/清理内存分配
作为 COM 公开的 C# 包装器
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++代码
#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;
}
显然,如果内存已分配且没有其他人释放它,您应该释放它。在这里,分配的内存是 .NET 的 string[]
和 uint[]
,在本地世界中表示为 SAFEARRAY*
。
但是,长话短说:您不能真正将结构用作 COM 方法的 return 类型。它不仅会导致复制语义问题(谁拥有结构的字段内存等),而且通常,它甚至不会根据结构大小等工作。很多麻烦,COM 方法应该 return 32/64位大小的变量(或 void)。
所以您可以使用 COM 对象而不是结构来解决这个问题。例如:
[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;
}
}
在 C++ 方面:
#include "windows.h"
#import "..\ManagedLib\bin\Debug\ManagedLib.tlb"
using namespace ManagedLib;
int main()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
{
ITestPtr test; // see
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;
}
注意:您也可以使用 CComSafeArray
来简化 SAFEARRAY
编程。