From 2a236b31a65614f5318cad794a5f38f4e566674e Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 6 Sep 2018 10:15:21 +0200 Subject: [PATCH] Add support for collectible types to SOS (#19842) * Add support for collectible types to SOS Collectible types indirectly reference managed LoaderAllocator via pointer to native AssemblyLoaderAllocator stored in their MethodTable. GC uses this relation when scanning object graph to determine which objects are rooted and which ones are not. The gcroot command in SOS doesn't understand this relation and so it is unable to find all roots for LoaderAllocator. This change fixes it. * PR feedback Make the failure to get the collectible info non-fatal to make it compatible with older runtimes. --- src/ToolBox/SOS/Strike/eeheap.cpp | 43 ++++++++++-- src/ToolBox/SOS/Strike/gcroot.cpp | 13 +++- src/ToolBox/SOS/Strike/sos.cpp | 137 +++++++++++++++++++++++++------------ src/ToolBox/SOS/Strike/sos.h | 2 + src/ToolBox/SOS/Strike/util.h | 10 ++- src/debug/daccess/daccess.cpp | 4 ++ src/debug/daccess/dacimpl.h | 6 +- src/debug/daccess/request.cpp | 27 ++++++++ src/inc/dacprivate.h | 20 ++++++ src/inc/sospriv.idl | 10 +++ src/pal/prebuilt/idl/sospriv_i.cpp | 4 ++ src/pal/prebuilt/inc/sospriv.h | 80 ++++++++++++++++++++++ 12 files changed, 303 insertions(+), 53 deletions(-) diff --git a/src/ToolBox/SOS/Strike/eeheap.cpp b/src/ToolBox/SOS/Strike/eeheap.cpp index 5a5680f..c28c964 100644 --- a/src/ToolBox/SOS/Strike/eeheap.cpp +++ b/src/ToolBox/SOS/Strike/eeheap.cpp @@ -868,8 +868,7 @@ DWORD GetNumComponents(TADDR obj) return Value; } -BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, - DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers) +static MethodTableInfo* GetMethodTableInfo(DWORD_PTR dwAddrMethTable) { // Remove lower bits in case we are in mark phase dwAddrMethTable = dwAddrMethTable & ~3; @@ -880,12 +879,34 @@ BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, // from the target DacpMethodTableData dmtd; // see code:ClrDataAccess::RequestMethodTableData for details - if (dmtd.Request(g_sos,dwAddrMethTable) != S_OK) - return FALSE; + if (dmtd.Request(g_sos, dwAddrMethTable) != S_OK) + return NULL; + info->BaseSize = dmtd.BaseSize; info->ComponentSize = dmtd.ComponentSize; info->bContainsPointers = dmtd.bContainsPointers; + + // The following request doesn't work on older runtimes. For those, the + // objects would just look like non-collectible, which is acceptable. + DacpMethodTableCollectibleData dmtcd; + if (SUCCEEDED(dmtcd.Request(g_sos, dwAddrMethTable))) + { + info->bCollectible = dmtcd.bCollectible; + info->LoaderAllocatorObjectHandle = TO_TADDR(dmtcd.LoaderAllocatorObjectHandle); + } + } + + return info; +} + +BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, + DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers) +{ + MethodTableInfo* info = GetMethodTableInfo(dwAddrMethTable); + if (info == NULL) + { + return FALSE; } bContainsPointers = info->bContainsPointers; @@ -911,6 +932,20 @@ BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, return TRUE; } +BOOL GetCollectibleDataEfficient(DWORD_PTR dwAddrMethTable, BOOL& bCollectible, TADDR& loaderAllocatorObjectHandle) +{ + MethodTableInfo* info = GetMethodTableInfo(dwAddrMethTable); + if (info == NULL) + { + return FALSE; + } + + bCollectible = info->bCollectible; + loaderAllocatorObjectHandle = info->LoaderAllocatorObjectHandle; + + return TRUE; +} + // This function expects stat to be valid, and ready to get statistics. void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort) { diff --git a/src/ToolBox/SOS/Strike/gcroot.cpp b/src/ToolBox/SOS/Strike/gcroot.cpp index e426220..cd13719 100644 --- a/src/ToolBox/SOS/Strike/gcroot.cpp +++ b/src/ToolBox/SOS/Strike/gcroot.cpp @@ -1009,7 +1009,7 @@ GCRootImpl::RootNode *GCRootImpl::GetGCRefs(RootNode *path, RootNode *node) // Only calculate the size if we need it. size_t objSize = 0; - if (mSize || node->MTInfo->ContainsPointers) + if (mSize || node->MTInfo->ContainsPointers || node->MTInfo->Collectible) { objSize = GetSizeOfObject(obj, node->MTInfo); @@ -1027,7 +1027,7 @@ GCRootImpl::RootNode *GCRootImpl::GetGCRefs(RootNode *path, RootNode *node) } // Early out: If the object doesn't contain any pointers, return. - if (!node->MTInfo->ContainsPointers) + if (!node->MTInfo->ContainsPointers && !node->MTInfo->Collectible) return NULL; // Make sure we have the object's data in the cache. @@ -1139,6 +1139,15 @@ GCRootImpl::MTInfo *GCRootImpl::GetMTInfo(TADDR mt) curr->ComponentSize = (size_t)dmtd.ComponentSize; curr->ContainsPointers = dmtd.bContainsPointers ? true : false; + // The following request doesn't work on older runtimes. For those, the + // objects would just look like non-collectible, which is acceptable. + DacpMethodTableCollectibleData dmtcd; + if (SUCCEEDED(dmtcd.Request(g_sos, mt))) + { + curr->Collectible = dmtcd.bCollectible ? true : false; + curr->LoaderAllocatorObjectHandle = TO_TADDR(dmtcd.LoaderAllocatorObjectHandle); + } + // If this method table contains pointers, fill out and cache the GCDesc. if (curr->ContainsPointers) { diff --git a/src/ToolBox/SOS/Strike/sos.cpp b/src/ToolBox/SOS/Strike/sos.cpp index 64ee4b9..5ff77a9 100644 --- a/src/ToolBox/SOS/Strike/sos.cpp +++ b/src/ToolBox/SOS/Strike/sos.cpp @@ -180,6 +180,15 @@ namespace sos info->BaseSize = mMTData->BaseSize; info->ComponentSize = mMTData->ComponentSize; info->bContainsPointers = mMTData->bContainsPointers; + + // The following request doesn't work on older runtimes. For those, the + // objects would just look like non-collectible, which is acceptable. + DacpMethodTableCollectibleData mtcd; + if (SUCCEEDED(mtcd.Request(g_sos, GetMT()))) + { + info->bCollectible = mtcd.bCollectible; + info->LoaderAllocatorObjectHandle = TO_TADDR(mtcd.LoaderAllocatorObjectHandle); + } } if (mSize == (size_t)~0) @@ -380,14 +389,14 @@ namespace sos RefIterator::RefIterator(TADDR obj, LinearReadCache *cache) - : mCache(cache), mGCDesc(0), mArrayOfVC(false), mDone(false), mBuffer(0), mCurrSeries(0), + : mCache(cache), mGCDesc(0), mArrayOfVC(false), mDone(false), mBuffer(0), mCurrSeries(0), mLoaderAllocatorObjectHandle(0), i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) { Init(); } RefIterator::RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache) - : mCache(cache), mGCDesc(desc), mArrayOfVC(arrayOfVC), mDone(false), mBuffer(0), mCurrSeries(0), + : mCache(cache), mGCDesc(desc), mArrayOfVC(arrayOfVC), mDone(false), mBuffer(0), mCurrSeries(0), mLoaderAllocatorObjectHandle(0), i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) { Init(); @@ -403,6 +412,13 @@ namespace sos { if (mDone) Throw("Attempt to move past the end of the iterator."); + + if (mCurr == mLoaderAllocatorObjectHandle) + { + // The mLoaderAllocatorObjectHandle is always the last reference returned + mDone = true; + return *this; + } if (!mArrayOfVC) { @@ -440,6 +456,14 @@ namespace sos mDone = true; } + if (mDone && mLoaderAllocatorObjectHandle != NULL) + { + // The iteration over all regular object references is done, but there is one more + // reference for collectible types - the LoaderAllocator for GC + mCurr = mLoaderAllocatorObjectHandle; + mDone = false; + } + return *this; } @@ -457,66 +481,89 @@ namespace sos { TADDR mt = ReadPointer(mObject); BOOL bContainsPointers = FALSE; - + BOOL bCollectible = FALSE; + TADDR loaderAllocatorObjectHandle; + if (!GetSizeEfficient(mObject, mt, FALSE, mObjSize, bContainsPointers)) Throw("Failed to get size of object."); - - if (!bContainsPointers) + + if (!GetCollectibleDataEfficient(mt, bCollectible, loaderAllocatorObjectHandle)) + Throw("Failed to get collectible info of object."); + + if (!bContainsPointers && !bCollectible) { mDone = true; return; } - - if (!mGCDesc) + + if (bContainsPointers) { - int entries = 0; - - if (FAILED(MOVE(entries, mt-sizeof(TADDR)))) - Throw("Failed to request number of entries."); - - // array of vc? - if (entries < 0) + if (!mGCDesc) { - entries = -entries; - mArrayOfVC = true; + int entries = 0; + + if (FAILED(MOVE(entries, mt-sizeof(TADDR)))) + Throw("Failed to request number of entries."); + + // array of vc? + if (entries < 0) + { + entries = -entries; + mArrayOfVC = true; + } + else + { + mArrayOfVC = false; + } + + size_t slots = 1 + entries * sizeof(CGCDescSeries)/sizeof(TADDR); + + ArrayHolder buffer = new TADDR[slots]; + + ULONG fetched = 0; + CLRDATA_ADDRESS address = TO_CDADDR(mt - slots*sizeof(TADDR)); + if (FAILED(g_ExtData->ReadVirtual(address, buffer, (ULONG)(slots*sizeof(TADDR)), &fetched))) + Throw("Failed to request GCDesc."); + + mBuffer = buffer.Detach(); + mGCDesc = (CGCDesc*)(mBuffer + slots); + } + + mCurrSeries = mGCDesc->GetHighestSeries(); + + if (!mArrayOfVC) + { + mCurr = mObject + mCurrSeries->GetSeriesOffset(); + mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; } else { - mArrayOfVC = false; + i = 0; + mCurr = mObject + mCurrSeries->startoffset; + mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); + mCount = (int)mGCDesc->GetNumSeries(); } - - size_t slots = 1 + entries * sizeof(CGCDescSeries)/sizeof(TADDR); - - ArrayHolder buffer = new TADDR[slots]; - - ULONG fetched = 0; - CLRDATA_ADDRESS address = TO_CDADDR(mt - slots*sizeof(TADDR)); - if (FAILED(g_ExtData->ReadVirtual(address, buffer, (ULONG)(slots*sizeof(TADDR)), &fetched))) - Throw("Failed to request GCDesc."); - - mBuffer = buffer.Detach(); - mGCDesc = (CGCDesc*)(mBuffer + slots); + + if (mCurr == mStop) + operator++(); + else if (mCurr >= mObject + mObjSize - plug_skew) + mDone = true; } - - mCurrSeries = mGCDesc->GetHighestSeries(); - - if (!mArrayOfVC) + else { - mCurr = mObject + mCurrSeries->GetSeriesOffset(); - mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; + mDone = true; } - else + + if (bCollectible) { - i = 0; - mCurr = mObject + mCurrSeries->startoffset; - mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); - mCount = (int)mGCDesc->GetNumSeries(); + mLoaderAllocatorObjectHandle = loaderAllocatorObjectHandle; + if (mDone) + { + // There are no object references, but there is still a reference for + // collectible types - the LoaderAllocator for GC + mCurr = mLoaderAllocatorObjectHandle; + } } - - if (mCurr == mStop) - operator++(); - else if (mCurr >= mObject + mObjSize - plug_skew) - mDone = true; } diff --git a/src/ToolBox/SOS/Strike/sos.h b/src/ToolBox/SOS/Strike/sos.h index 3778235..ff5b53a 100644 --- a/src/ToolBox/SOS/Strike/sos.h +++ b/src/ToolBox/SOS/Strike/sos.h @@ -501,6 +501,8 @@ namespace sos TADDR *mBuffer; CGCDescSeries *mCurrSeries; + TADDR mLoaderAllocatorObjectHandle; + int i, mCount; TADDR mCurr, mStop, mObject; diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index 7851654..ebad2e4 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -1660,9 +1660,11 @@ struct MethodTableInfo DWORD BaseSize; // Caching BaseSize and ComponentSize for a MethodTable DWORD ComponentSize; // here has HUGE perf benefits in heap traversals. BOOL bContainsPointers; + BOOL bCollectible; DWORD_PTR* GCInfoBuffer; // Start of memory of GC info CGCDesc* GCInfo; // Just past GC info (which is how it is stored) bool ArrayOfVC; + TADDR LoaderAllocatorObjectHandle; }; class MethodTableCache @@ -1680,9 +1682,11 @@ protected: info.BaseSize = 0; info.ComponentSize = 0; info.bContainsPointers = false; + info.bCollectible = false; info.GCInfo = NULL; info.ArrayOfVC = false; info.GCInfoBuffer = NULL; + info.LoaderAllocatorObjectHandle = NULL; } }; Node *head; @@ -1948,6 +1952,8 @@ size_t NextOSPageAddress (size_t addr); BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers); +BOOL GetCollectibleDataEfficient(DWORD_PTR dwAddrMethTable, BOOL& bCollectible, TADDR& loaderAllocatorObjectHandle); + // ObjSize now uses the methodtable cache for its work too. size_t ObjectSize (DWORD_PTR obj, BOOL fIsLargeObject=FALSE); size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject=FALSE); @@ -2856,8 +2862,10 @@ private: TADDR *Buffer; CGCDesc *GCDesc; + TADDR LoaderAllocatorObjectHandle; bool ArrayOfVC; bool ContainsPointers; + bool Collectible; size_t BaseSize; size_t ComponentSize; @@ -2874,7 +2882,7 @@ private: MTInfo() : MethodTable(0), TypeName(0), Buffer(0), GCDesc(0), - ArrayOfVC(false), ContainsPointers(false), BaseSize(0), ComponentSize(0) + ArrayOfVC(false), ContainsPointers(false), Collectible(false), BaseSize(0), ComponentSize(0) { } diff --git a/src/debug/daccess/daccess.cpp b/src/debug/daccess/daccess.cpp index 1ce452b..e7bef87 100644 --- a/src/debug/daccess/daccess.cpp +++ b/src/debug/daccess/daccess.cpp @@ -3278,6 +3278,10 @@ ClrDataAccess::QueryInterface(THIS_ { ifaceRet = static_cast(this); } + else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface6))) + { + ifaceRet = static_cast(this); + } else { *iface = NULL; diff --git a/src/debug/daccess/dacimpl.h b/src/debug/daccess/dacimpl.h index 6147de3..e05575c 100644 --- a/src/debug/daccess/dacimpl.h +++ b/src/debug/daccess/dacimpl.h @@ -862,7 +862,8 @@ class ClrDataAccess public ISOSDacInterface2, public ISOSDacInterface3, public ISOSDacInterface4, - public ISOSDacInterface5 + public ISOSDacInterface5, + public ISOSDacInterface6 { public: ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget=0); @@ -1208,6 +1209,9 @@ public: // ISOSDacInterface5 virtual HRESULT STDMETHODCALLTYPE GetTieredVersions(CLRDATA_ADDRESS methodDesc, int rejitId, struct DacpTieredVersionData *nativeCodeAddrs, int cNativeCodeAddrs, int *pcNativeCodeAddrs); + // ISOSDacInterface6 + virtual HRESULT STDMETHODCALLTYPE GetMethodTableCollectibleData(CLRDATA_ADDRESS mt, struct DacpMethodTableCollectibleData *data); + // // ClrDataAccess. // diff --git a/src/debug/daccess/request.cpp b/src/debug/daccess/request.cpp index 12864e7..3a497b1 100644 --- a/src/debug/daccess/request.cpp +++ b/src/debug/daccess/request.cpp @@ -1955,6 +1955,33 @@ ClrDataAccess::GetMethodTableFieldData(CLRDATA_ADDRESS mt, struct DacpMethodTabl } HRESULT +ClrDataAccess::GetMethodTableCollectibleData(CLRDATA_ADDRESS mt, struct DacpMethodTableCollectibleData *data) +{ + if (mt == 0 || data == NULL) + return E_INVALIDARG; + + SOSDacEnter(); + + MethodTable* pMT = PTR_MethodTable(TO_TADDR(mt)); + BOOL bIsFree = FALSE; + if (!pMT || !DacValidateMethodTable(pMT, bIsFree)) + { + hr = E_INVALIDARG; + } + else + { + data->bCollectible = pMT->Collectible(); + if (data->bCollectible) + { + data->LoaderAllocatorObjectHandle = pMT->GetLoaderAllocatorObjectHandle(); + } + } + + SOSDacLeave(); + return hr; +} + +HRESULT ClrDataAccess::GetMethodTableTransparencyData(CLRDATA_ADDRESS mt, struct DacpMethodTableTransparencyData *pTransparencyData) { if (mt == 0 || pTransparencyData == NULL) diff --git a/src/inc/dacprivate.h b/src/inc/dacprivate.h index 2f74826..47e79a1 100644 --- a/src/inc/dacprivate.h +++ b/src/inc/dacprivate.h @@ -177,6 +177,25 @@ struct MSLAYOUT DacpMethodTableFieldData : ZeroInit } }; +struct MSLAYOUT DacpMethodTableCollectibleData : ZeroInit +{ + CLRDATA_ADDRESS LoaderAllocatorObjectHandle; + BOOL bCollectible; + + HRESULT Request(ISOSDacInterface *sos, CLRDATA_ADDRESS addr) + { + HRESULT hr; + ISOSDacInterface6 *pSOS6 = NULL; + if (SUCCEEDED(hr = sos->QueryInterface(__uuidof(ISOSDacInterface6), (void**)&pSOS6))) + { + hr = pSOS6->GetMethodTableCollectibleData(addr, this); + pSOS6->Release(); + } + + return hr; + } +}; + struct MSLAYOUT DacpMethodTableTransparencyData : ZeroInit { BOOL bHasCriticalTransparentInfo; @@ -1043,5 +1062,6 @@ static_assert(sizeof(DacpGetModuleAddress) == 0x8, "Dacp structs cannot be modif static_assert(sizeof(DacpFrameData) == 0x8, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpJitCodeHeapInfo) == 0x18, "Dacp structs cannot be modified due to backwards compatibility."); static_assert(sizeof(DacpExceptionObjectData) == 0x38, "Dacp structs cannot be modified due to backwards compatibility."); +static_assert(sizeof(DacpMethodTableCollectibleData) == 0x10, "Dacp structs cannot be modified due to backwards compatibility."); #endif // _DACPRIVATE_H_ diff --git a/src/inc/sospriv.idl b/src/inc/sospriv.idl index 5b71821..5896426 100644 --- a/src/inc/sospriv.idl +++ b/src/inc/sospriv.idl @@ -367,3 +367,13 @@ interface ISOSDacInterface5 : IUnknown { HRESULT GetTieredVersions(CLRDATA_ADDRESS methodDesc, int rejitId, struct DacpTieredVersionData *nativeCodeAddrs, int cNativeCodeAddrs, int *pcNativeCodeAddrs); }; + +[ + object, + local, + uuid(11206399-4B66-4EDB-98EA-85654E59AD45) +] +interface ISOSDacInterface6 : IUnknown +{ + HRESULT GetMethodTableFieldData(CLRDATA_ADDRESS mt, struct DacpMethodTableFieldData *data); +}; diff --git a/src/pal/prebuilt/idl/sospriv_i.cpp b/src/pal/prebuilt/idl/sospriv_i.cpp index 7c0cd69..290b9b7 100644 --- a/src/pal/prebuilt/idl/sospriv_i.cpp +++ b/src/pal/prebuilt/idl/sospriv_i.cpp @@ -91,6 +91,10 @@ MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface4,0x74B9D34C,0xA612,0x4B07,0x93,0xDD,0 MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface5,0x127d6abe,0x6c86,0x4e48,0x8e,0x7b,0x22,0x07,0x81,0xc5,0x81,0x01); + +MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface6,0x11206399,0x4b66,0x4edb,0x98,0xea,0x85,0x65,0x4e,0x59,0xad,0x45); + + #undef MIDL_DEFINE_GUID #ifdef __cplusplus diff --git a/src/pal/prebuilt/inc/sospriv.h b/src/pal/prebuilt/inc/sospriv.h index 3b810cc..8896720 100644 --- a/src/pal/prebuilt/inc/sospriv.h +++ b/src/pal/prebuilt/inc/sospriv.h @@ -2272,6 +2272,86 @@ EXTERN_C const IID IID_ISOSDacInterface5; #endif /* __ISOSDacInterface5_INTERFACE_DEFINED__ */ + +#ifndef __ISOSDacInterface6_INTERFACE_DEFINED__ +#define __ISOSDacInterface6_INTERFACE_DEFINED__ + + /* interface ISOSDacInterface6 */ + /* [uuid][local][object] */ + + + EXTERN_C const IID IID_ISOSDacInterface6; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("11206399-4B66-4EDB-98EA-85654E59AD45") + ISOSDacInterface6 : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE GetMethodTableCollectibleData( + CLRDATA_ADDRESS mt, + struct DacpMethodTableCollectibleData *data) = 0; + }; + + +#else /* C style interface */ + + typedef struct ISOSDacInterface6Vtbl + { + BEGIN_INTERFACE + + HRESULT(STDMETHODCALLTYPE *QueryInterface)( + ISOSDacInterface5 * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG(STDMETHODCALLTYPE *AddRef)( + ISOSDacInterface5 * This); + + ULONG(STDMETHODCALLTYPE *Release)( + ISOSDacInterface5 * This); + + HRESULT(STDMETHODCALLTYPE *GetMethodTableCollectibleData)( + CLRDATA_ADDRESS mt, + struct DacpMethodTableCollectibleData *data); + + END_INTERFACE + } ISOSDacInterface6Vtbl; + + interface ISOSDacInterface6 + { + CONST_VTBL struct ISOSDacInterface6Vtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define ISOSDacInterface6_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define ISOSDacInterface6_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define ISOSDacInterface6_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define ISOSDacInterface6_GetMethodTableCollectibleData(This,mt,data) \ + ( (This)->lpVtbl -> GetMethodTableCollectibleData(This,mt,data) ) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + +#endif /* __ISOSDacInterface6_INTERFACE_DEFINED__ */ + /* Additional Prototypes for ALL interfaces */ /* end of Additional Prototypes */ -- 2.7.4