У меня есть COM-объект, написанный на управляемом коде (C ++ / CLI). Я использую этот объект в стандарте C ++.
Как заставить деструктор моего COM-объекта вызываться сразу после освобождения COM-объекта? Если это невозможно, могу ли я, чтобы Release () вызывал метод Dispose () (не MyDispose () - GBG) для моего COM-объекта (управляемого DotNet - GBG)?
RE: Принудительное освобождение ресурсов, связанных с COM-сервером DotNet, когда клиент использует неуправляемый код. Эти ресурсы могут быть организованы для освобождения, когда сборщик мусора собирает элемент, но это не так. детерминированные и такие ресурсы, как файловые потоки, не могут высвобождаться часами или днями для больших систем памяти, где сборка мусора происходит редко.
Это распространенная проблема с COM Callable Wrappers (CCW), которая может быть замечена другим несколько связанным потоком по адресу: Возможно ли перехватить (или знать о) COM-подсчет ссылок на объекты CLR, выставленные COM . в этом случае, как и в любом случае, когда кто-то пишет свой собственный COM-клиент, независимо от того, находится ли он под управляемым или неуправляемым кодом, это легко решить, просто вызвав метод IDisposable.Dispose (), как это было сделано там. Однако этот метод не будет работать, скажем, для класса COM-кодеков DotNet, чьим клиентом может быть сама операционная система, и которому не нужно знать, что COM-сервер неуправляем или не управляется (DotNet).
Можно реализовать шаблон IDisposable.Dispose () на COM-сервере DotNet согласно ссылке MSDN: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx,, но это не принесет пользы, потому что метод Dispose () никогда не будет вызван CCW. В идеале реализация CCW в mscoree.dll должна действительно проверять и вызывать метод IDisposable.Dispose (), если он реализован как часть выпуска CCW и / или финализации / деструктора. Я не уверен, почему Microsoft не сделала этого, поскольку, имея полный доступ к информации о сборке, они могли легко определить, поддерживает ли класс DotNet COM класс IDisposable, и просто вызвать Dispose () в окончательном выпуске, если это произойдет, и так как это будет в пределах CCW, можно избежать всех сложностей с обработкой подсчета ссылок из-за дополнительной ссылки на интерфейс.
Я не вижу, как это «сломало бы» любой существующий код, поскольку любой клиент, который знает IDisposable, мог бы все еще вызывать Dispose (), который, если реализован в соответствии с вышеуказанным шаблоном, только эффективно что-либо делает при первом вызове. Microsoft может беспокоиться о том, что класс Disposed, хотя до сих пор существуют управляемые ссылки на него, которые не будут знать о его удалении до тех пор, пока не начнут возникать исключения, возникающие при попытке использовать уже удаленные ресурсы, но это потенциальная проблема для любого неправильного использования. использование интерфейса IDisposable даже с только клиентами DotNet: если существует несколько ссылок на один и тот же экземпляр объекта, и любой из них вызывает Dispose (), другие обнаружат, что попытка использовать требуемые удаленные ресурсы вызывает исключения. В таких случаях всегда следует устанавливать защитные элементы с использованием логического значения (согласно шаблону IDisposable) или ссылаться на объект только через общую оболочку.
Поскольку Microsoft не выполнила необходимые несколько строк кода при реализации CCW в mscoree.dll, я написал оболочку для mscoree.dll, которая добавляет эту дополнительную функциональность. Это немного сложно, поскольку для управления созданием моей обертки вокруг любого экземпляра любого COM-класса DotNet мне нужно также обернуть интерфейс IClassFactory и объединить экземпляр CCW в моем классе-обертке "CCW_Wrapper". Эта оболочка также поддерживает дополнительные уровни агрегации из еще одного внешнего класса. Код также подсчитывает ссылки на экземпляры классов в используемой реализации mscoree.dll, чтобы иметь возможность вызывать FreeLibrary для mscoree.dll, когда нет ссылок (и снова загружать LoadLibrary, если потребуется позже). Код также должен быть многопоточным, что требуется для COM в Windows 7. Мой код C ++ выглядит следующим образом:
РЕД., 22 декабря 2010 г .: исключен один ненужный параметр конструктора COM_Wrapper:
#include <windows.h>
HMODULE g_WrappedDLLInstance = NULL;
ULONG g_ObjectInstanceRefCnt = 0;
//the following is the C++ definition of the IDisposable interface
//using the GUID as per the managed definition, which never changes across
//DotNet versions as it represents a hash of the definition and its
//namespace, none of which can change by definition.
MIDL_INTERFACE("805D7A98-D4AF-3F0F-967F-E5CF45312D2C")
IDisposable : public IDispatch {
public:
virtual VOID STDMETHODCALLTYPE Dispose() = 0;
};
class CCW_Wrapper : public IUnknown {
public:
// constructor and destructor
CCW_Wrapper(
__in IClassFactory *pClassFactory,
__in IUnknown *pUnkOuter) :
iWrappedIUnknown(nullptr),
iOuterIUnknown(pUnkOuter),
iWrappedIDisposable(nullptr),
ready(FALSE),
refcnt(0) {
InterlockedIncrement(&g_ObjectInstanceRefCnt);
if (!this->iOuterIUnknown)
this->iOuterIUnknown = static_cast<IUnknown*>(this);
pClassFactory->CreateInstance(
this->iOuterIUnknown,
IID_IUnknown,
(LPVOID*)&this->iWrappedIUnknown);
if (this->iWrappedIUnknown) {
if (SUCCEEDED(this->iWrappedIUnknown->QueryInterface(__uuidof(IDisposable), (LPVOID*)&this->iWrappedIDisposable)))
this->iOuterIUnknown->Release(); //to clear the reference count caused by the above.
}
this->ready = TRUE; //enable destruction of the object when release decrements to zero.
//OUTER IUNKNOWN OBJECTS MUST ALSO PROTECT THEIR DESTRUCTORS IN SIMILAR MANNERS!!!!!
}
~CCW_Wrapper() {
this->ready = FALSE; //protect from re-entering this destructor when object released to zero.
if (this->iWrappedIDisposable) {
//the whole reason for this project!!!!!!!!
this->iWrappedIDisposable->Dispose();
//the following may be redundant, but to be sure...
this->iOuterIUnknown->AddRef();
this->iWrappedIDisposable->Release();
}
if (this->iWrappedIUnknown)
this->iWrappedIUnknown->Release();
if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
//clear all global resources including the mutex, multithreading safe...
HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
if (m)
FreeLibrary(m);
}
}
// IUnknown Interface
STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
if (ppv) {
*ppv = nullptr;
if (riid == IID_IUnknown) {
*ppv = static_cast<IUnknown*>(this);
this->AddRef();
return S_OK;
}
else if (this->iWrappedIUnknown) {
return this->iWrappedIUnknown->QueryInterface(riid, ppv);
}
return E_NOINTERFACE;
}
return E_INVALIDARG;
}
STDMETHOD_(ULONG, AddRef)() {
return InterlockedIncrement(&this->refcnt);
}
STDMETHOD_(ULONG, Release)() {
if (InterlockedDecrement(&this->refcnt))
return this->refcnt;
if (this->ready) //if not being constructed or destructed...
delete this;
return 0;
}
private:
IUnknown *iOuterIUnknown;
IUnknown *iWrappedIUnknown;
IDisposable *iWrappedIDisposable;
BOOL ready;
ULONG refcnt;
};
class ClassFactoryWrapper : public IClassFactory {
public:
// constructor and destructor
ClassFactoryWrapper(IClassFactory *icf) : wrappedFactory(icf), refcnt(0), lockcnt(0) {
InterlockedIncrement(&g_ObjectInstanceRefCnt);
}
~ClassFactoryWrapper() {
if (wrappedFactory)
wrappedFactory->Release();
if (!InterlockedDecrement(&g_ObjectInstanceRefCnt)) {
//clear all global resources, multithreading safe...
HMODULE m = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)0);
if (m)
FreeLibrary(m);
}
}
// IUnknown Interface
STDMETHOD(QueryInterface)(REFIID riid, void **ppv) {
if (ppv) {
*ppv = nullptr;
if (riid == IID_IUnknown) {
*ppv = static_cast<IUnknown*>(this);
this->AddRef();
}
else if (riid == IID_IClassFactory) {
*ppv = static_cast<IClassFactory*>(this);
this->AddRef();
}
else {
return E_NOINTERFACE;
}
return S_OK;
}
return E_INVALIDARG;
}
STDMETHOD_(ULONG, AddRef)() {
return InterlockedIncrement(&this->refcnt);
}
STDMETHOD_(ULONG, Release)() {
if (InterlockedDecrement(&this->refcnt) || this->lockcnt)
return this->refcnt;
delete this;
return 0;
}
// IClassFactory Interface
STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppv) {
HRESULT result = E_INVALIDARG;
if (ppv) {
*ppv = nullptr;
if (pUnkOuter && (riid != IID_IUnknown))
return result;
CCW_Wrapper *oipm = new CCW_Wrapper(wrappedFactory, pUnkOuter);
if (!oipm)
return E_OUTOFMEMORY;
if (FAILED(result = oipm->QueryInterface(riid, ppv)))
delete oipm;
}
return result;
}
STDMETHOD(LockServer)(BOOL fLock) {
if (fLock)
InterlockedIncrement(&this->lockcnt);
else {
if (!InterlockedDecrement(&this->lockcnt) && !this->refcnt)
delete this;
}
return wrappedFactory->LockServer(fLock);
}
private:
IClassFactory *wrappedFactory;
ULONG refcnt;
ULONG lockcnt;
};
STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID FAR* ppv) {
HRESULT result = E_INVALIDARG;
if (ppv) {
*ppv = nullptr;
if ((riid != IID_IUnknown) && (riid != IID_IClassFactory))
return E_NOINTERFACE;
HMODULE hDLL = LoadLibrary(L"mscoree.dll");
if (!hDLL)
return E_UNEXPECTED;
typedef HRESULT (__stdcall *pDllGetClassObject) (__in REFCLSID, __in REFIID, __out LPVOID *);
pDllGetClassObject DllGetClassObject = (pDllGetClassObject)GetProcAddress(hDLL, "DllGetClassObject");
if (!DllGetClassObject) {
FreeLibrary(hDLL);
return E_UNEXPECTED;
}
IClassFactory *icf = nullptr;
if (FAILED(result = (DllGetClassObject)(rclsid, IID_IClassFactory, (LPVOID*)&icf))) {
FreeLibrary(hDLL);
return result;
}
ClassFactoryWrapper *cfw = new ClassFactoryWrapper(icf);
if (!cfw) {
icf->Release();
FreeLibrary(hDLL);
return E_OUTOFMEMORY;
}
//record the HMODULE instance in global variable for freeing later, multithreaded safe...
hDLL = (HMODULE)InterlockedExchangePointer((PVOID*)&g_WrappedDLLInstance, (LPVOID)hDLL);
if (hDLL)
FreeLibrary(hDLL);
if (FAILED(result = cfw->QueryInterface(IID_IClassFactory, ppv)))
delete cfw; //will automatically free library and the held class factory reference if necessary.
}
return result;
}
extern "C"
HRESULT __stdcall DllCanUnloadNow(void) {
if (g_ObjectInstanceRefCnt)
return S_FALSE;
return S_OK;
}
extern "C"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved ) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Файл '.def' также требуется для DLL следующим образом:
LIBRARY mscoreeCOM_DisposeWrapper
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
Чтобы использовать этот исходный код, скомпилируйте его в DLL и установите DLL в папку Windows SYSTEM, затем попросите программу установки или метод [COMRegisterFunction] на вашем COM-сервере DotNet изменить запись реестра класса для InprocServer32 из mscoree. .dll к имени этой оболочки (скажем, mscoreeWrapper.dll). Его можно скомпилировать в 32- и / или 64-разрядных системах, а для установки в 64-разрядных системах необходимо поместить 64-разрядную версию в системную папку, а 32-разрядную - в папку SysWOW64; Кроме того, как обычная регистрация CLSID, так и виртуализированные версии WOW6432 должны быть изменены в соответствии с записями InprocServer32. Некоторые приложения могут требовать, чтобы эта DLL-оболочка имела цифровую подпись, чтобы работать без проблем, что является совершенно другой темой. Если кто-то захочет, я сделаю ссылку на мои скомпилированные версии этих DLL, доступные здесь.
Как я уже сказал, в mscoree.dll должна быть включена необходимая техника в несколько строк (исключая требования к оболочке). Кто-нибудь знает, как связаться с кем-то из соответствующего отдела Microsoft, чтобы сделать это предложение?
EDITADD: Я отправил предложение для DotNet Framework в Microsoft Connect. Похоже, это лучший способ дать обратную связь Microsoft.
EDITADD2: При реализации обходного пути для этой проблемы я понял, почему MIcrosoft, скорее всего, не будет реализовывать это "Автоматический вызов Dispose, если поддерживается, когда счетчик ссылок CCW падает до нуля". При написании обходного пути я должен был получить указатель ссылки на интерфейс COM на управляемом объекте, чтобы передать его чисто неуправляемому методу COM, а затем должен был Release () с этим счетчиком ссылок, чтобы сильно не иметь CCW ссылаясь на объект и, следовательно, вызывая утечку памяти, никогда не имея ее доступной для сборки мусора. Я сделал это, потому что знаю, что уменьшение числа ссылок до нуля на управляемом объекте в настоящее время только заставляет CCW отбросить его сильную ссылку на объект , что делает его пригодным для сбора мусора, если нет других ссылок. Однако, если Microsoft внедрила исправление Auto Dispose, как я предлагал, или если этот код был на месте, оборачивая функциональность mscoree.dll, это вызвало бы Dispose () на управляемом объекте, когда он не был нужен. В этом конкретном случае я мог бы «защитить» виртуальный метод Dispose (bool dispose), чтобы предотвратить возникновение Dispose (), , но для любого существующего кода, использующего это поведение, с тем же допущением, включая реализацию Microsoft среды выполнения DotNet. Библиотеки, реализующие это «исправление» в CCW, сломали бы существующий код . Это исправление обертки все еще применяется к COM-серверам, которые каждый пишет самостоятельно и знает об этом побочном эффекте, поскольку они могут поставить «охранников» на место в Dispose ().
EDITADD 3: В дальнейшей работе с этим я вижу, что мое предложение Microsoft остается в силе, и проблем с «взломом» существующего кода можно избежать с помощью исправления, которое вызовет IDisposable.Dispose () в экземпляре объекта, реализующем управляемый COM-сервер, если интерфейс существует , только если новый настраиваемый атрибут, такой как [AutoComDispose (true)], значение по умолчанию которого равно false, применяется к классу управляемого COM-сервера . Таким образом, программист выбрал бы реализацию функциональности, а документация по новому атрибуту предостерегла бы его использование в отношении необходимости «охранять» метод Dispose (), например, с «искусственным подсчетом ссылок», когда есть возможность Метод Marshal.Release (), вызываемый явно в коде, используемом управляемым сервером, или неявно посредством вызовов таких методов, как Marshal.GetObjectForIUnknown (), которые в некоторых случаях могут вызывать QueryInterface и Release контрольной точки, если ComObject является управляемым объект.
Основная проблема с этим Ответом - сложность установки его для использования, как отмечено выше.