//*** qistub.cpp -- QI helpers (retail and debug) // DESCRIPTION // this file has the shared-source 'master' implementation. it is // #included in each DLL that uses it. // clients do something like: // #include "priv.h" // for types, ASSERT, DM_*, DF_*, etc. // #include "../lib/qistub.cpp" #include "qistub.h" #include #define DM_MISC2 0 // misc stuff (verbose) // hack-o-rama: shlwapi/qistub.cpp does #undef DEBUG but its PCH was // built DEBUG, so lots of bad stuff happens. work-around it here. #undef DBEXEC #ifdef DEBUG #define DBEXEC(flg, expr) ((flg) ? (expr) : 0) #else #define DBEXEC(flg, expr) /*NOTHING*/ #endif #ifdef DEBUG // { //*** CUniqueTab { // DESCRIPTION // key/data table insert and lookup, w/ interlock. class CUniqueTab { public: BOOL Init(); void * Add(int val); void * Find(int val, int delta); void Reset(void); // n.b. *not* protected CUniqueTab(int cbElt); virtual ~CUniqueTab(); protected: private: void _Lock(void) { EnterCriticalSection(&_hLock); } void _Unlock(void) { LeaveCriticalSection(&_hLock); } CRITICAL_SECTION _hLock; // key + (arbitrary) limit of 4 int's of client data #define CUT_CBELTMAX (SIZEOF(int) + 4 * SIZEOF(int)) int _cbElt; // size of an entry (key + data) // (arbitrary) limit to catch clients running amuck #define CUT_CVALMAX 256 // actually, a LIM not a MAX HDSA _hValTab; }; CUniqueTab::CUniqueTab(int cbElt) { ASSERT(cbElt >= SIZEOF(DWORD)); // need at least a key; data optional _cbElt = cbElt; _hValTab = DSA_Create(_cbElt, 4); return; } CUniqueTab::~CUniqueTab() { DSA_Destroy(_hValTab); DeleteCriticalSection(&_hLock); return; } BOOL CUniqueTab::Init() { return InitializeCriticalSectionAndSpinCount(&_hLock, 0); } struct cutent { int iKey; char bData[CUT_CBELTMAX - SIZEOF(int)]; }; struct cfinddata { int iKey; int dRange; void *pEntry; }; int _UTFindCallback(void *pEnt, void *pData) { #define INFUNC(base, p, range) ((base) <= (p) && (p) <= (base) + (range)) struct cfinddata *pcd = (struct cfinddata *)pData; if (INFUNC(*(int *)pEnt, pcd->iKey, pcd->dRange)) { pcd->pEntry = pEnt; return 0; } return 1; #undef INFUNC } //*** CUniqueTab::Add -- add entry if not already there // void * CUniqueTab::Add(int val) { struct cfinddata cd = { val, 0, NULL }; _Lock(); DSA_EnumCallback(_hValTab, _UTFindCallback, &cd); if (!cd.pEntry) { int i; // lazy,lazy,lazy: alloc max size and let DSA_AppendItem sort it out struct cutent elt = { val, 0 /*,0,...,0*/ }; TraceMsg(DM_MISC2, "cut.add: add %x", val); if (DSA_GetItemCount(_hValTab) <= CUT_CVALMAX) { i = DSA_AppendItem(_hValTab, &elt); cd.pEntry = DSA_GetItemPtr(_hValTab, i); } } _Unlock(); return cd.pEntry; } //*** CUniqueTab::Find -- find entry // void * CUniqueTab::Find(int val, int delta) { struct cfinddata cd = { val, delta, NULL }; DSA_EnumCallback(_hValTab, _UTFindCallback, &cd); if (cd.pEntry) { // TODO: add p->data[0] dump TraceMsg(DM_MISC2, "cut.find: found %x+%d", val, delta); } return cd.pEntry; } //*** _UTResetCallback -- helper for CUniqueTab::Reset int _UTResetCallback(void *pEnt, void *pData) { struct cutent *pce = (struct cutent *)pEnt; int cbEnt = *(int *)pData; // perf: could move the SIZEOF(int) into caller, but seems safer here memset(pce->bData, 0, cbEnt - SIZEOF(int)); return 1; } //*** Reset -- clear 'data' part of all entries // void CUniqueTab::Reset(void) { if (EVAL(_cbElt > SIZEOF(int))) { _Lock(); DSA_EnumCallback(_hValTab, _UTResetCallback, &_cbElt); _Unlock(); } return; } // } #endif // } //*** QueryInterface helpers { //*** FAST_IsEqualIID -- fast compare // (cast to 'LONG_PTR' so don't get overloaded ==) #define FAST_IsEqualIID(piid1, piid2) ((LONG_PTR)(piid1) == (LONG_PTR)(piid2)) #ifdef DEBUG // { //*** DBNoOp -- do nothing (but suppress compiler optimizations) // NOTES // this won't fool compiler when it gets smarter, oh well... void DBNoOp() { return; } void DBBrkpt() { DBNoOp(); return; } //*** DBBreakGUID -- debug hook (gets readable name, allows brkpt on IID) // DESCRIPTION // search for 'BRKPT' for various hooks. // patch 'DBQIiid' to brkpt on a specific iface // patch 'DBQIiSeq' to brkpt on Nth QI of specific iface // brkpt on interesting events noted below // NOTES // warning: returns ptr to *static* buffer! typedef enum { DBBRK_NIL = 0, DBBRK_ENTER = 0x01, DBBRK_TRACE = 0x02, DBBRK_S_XXX = 0x04, DBBRK_E_XXX = 0x08, DBBRK_BRKPT = 0x10, } DBBRK; DBBRK DBQIuTrace = DBBRK_NIL; // BRKPT patch to enable brkpt'ing GUID *DBQIiid = NULL; // BRKPT patch to brkpt on iface int DBQIiSeq = -1; // BRKPT patch to brkpt on Nth QI of DBQIiid long DBQIfReset = FALSE; // BRKPT patch to reset counters TCHAR *DBBreakGUID(const GUID *piid, DBBRK brkCmd) { static TCHAR szClass[GUIDSTR_MAX]; SHStringFromGUID(*piid, szClass, ARRAYSIZE(szClass)); // FEATURE: fold these 2 if's together if ((DBQIuTrace & brkCmd) && (DBQIiid == NULL || IsEqualIID(*piid, *DBQIiid))) { TraceMsg(DM_TRACE, "util: DBBreakGUID brkCmd=%x clsid=%s (%s)", brkCmd, szClass, Dbg_GetREFIIDName(*piid)); // BRKPT put brkpt here to brkpt on 'brkCmd' event DBBrkpt(); } if (DBQIiid != NULL && IsEqualIID(*piid, *DBQIiid)) { //TraceMsg(DM_TRACE, "util: DBBreakGUID clsid=%s (%s)", szClass, Dbg_GetREFIIDName(*piid)); if (brkCmd != DBBRK_TRACE) { // BRKPT put brkpt here to brkpt on 'DBQIiid' iface DBNoOp(); } } // BRKPT put your brkpt(s) here for various events switch (brkCmd) { case DBBRK_ENTER: // QI called w/ this iface DBNoOp(); break; case DBBRK_TRACE: // looped over this iface DBNoOp(); break; case DBBRK_S_XXX: // successful QI for this iface DBNoOp(); break; case DBBRK_E_XXX: // failed QI for this iface DBNoOp(); break; case DBBRK_BRKPT: // various brkpt events, see backtrace to figure out which one DBNoOp(); break; } return szClass; } #endif // } #ifdef DEBUG CUniqueTab *DBpQIFuncTab; STDAPI_(BOOL) DBIsQIFunc(int ret, int delta) { BOOL fRet = FALSE; if (DBpQIFuncTab) fRet = BOOLFROMPTR(DBpQIFuncTab->Find(ret, delta)); return fRet; } #endif // perf: shell split means FAST_IsEqIID often fails, so QI_EASY is off. #define QI_EASY 0 // w/ shell split, seems to be too rare #ifdef DEBUG // { int DBcQITot, DBcQIUnk, DBcQIErr, DBcQIEasy, DBcQIHard; LPCQITAB DBpqitStats; // BRKPT: patch to enable QITABENT profiling #define DBSTAT_CNT 20 int DBcStats[DBSTAT_CNT + 3]; // 0..n, overflow, IUnknown, E_FAIL #define DBSI_FAIL (-1) #define DBSI_IUNKNOWN (-2) #define DBSI_OVERFLOW (-3) #define DBSI_SPEC(i) (DBSTAT_CNT - 1 + (-(i))) //*** // DESCRIPTION // search for 'BRKPT' for various hooks. // patch 'DBpqitStats' to gather stats on that QITAB // then break into debugger (ctrl+C) and dumpa 'DBcStats l 24' // then sort high count guys to the front, and 0 count guys to the end // void DBQIStats(LPCQITAB pqitab, INT_PTR i) { if (pqitab != DBpqitStats) return; if (i >= DBSTAT_CNT) i = DBSI_OVERFLOW; if (i < 0) i = DBSTAT_CNT - 1 + (-i); DBcStats[i]++; return; } void DBDumpQIStats() { int i; TCHAR *p; TCHAR buf[256]; TraceMsg(TF_QISTUB, "qi stats: tot=%d unk=%d err=%d easy(%d)=%d hard=%d", DBcQITot, DBcQIUnk, DBcQIErr, QI_EASY, DBcQIEasy, DBcQIHard); if (DBpqitStats == NULL) return; p = buf; for (i = 0; i < DBSTAT_CNT; i++) { p += wnsprintf(p, ARRAYSIZE(buf) - (p-buf), TEXT(" %d"), DBcStats[i]); } StringCchPrintf(p, ARRAYSIZE(buf) - (p-buf), TEXT(" o=%d u=%d e=%d"), DBcStats[DBSI_SPEC(DBSI_OVERFLOW)], DBcStats[DBSI_SPEC(DBSI_IUNKNOWN)], DBcStats[DBSI_SPEC(DBSI_FAIL)]); TraceMsg(TF_QISTUB, "qi stats: %s", buf); return; } #endif // } //*** QISearch -- table-driven QI // ENTRY/EXIT // this IUnknown* of calling QI // pqit QI table of IID,cast_offset pairs // ppv the usual // hr the usual S_OK/E_NOINTERFACE, plus other E_* for errors // NOTES // perf: shell split means FAST_IsEqIID often fails, so QI_EASY is off. // perf: IUnknown v. rare, so goes last. // PERF: explicit 'E_NOIFACE' entry in qitab for common miss(es)? STDAPI_(void*) QIStub_CreateInstance(void* that, IUnknown* punk, REFIID riid); // qistub.cpp STDAPI QISearch(void* that, LPCQITAB pqitab, REFIID riid, LPVOID* ppv) { // do *not* move this!!! (must be 1st on frame) #ifdef DEBUG #if (_X86_) int var0; // *must* be 1st on frame #endif #endif LPCQITAB pqit; #ifdef DEBUG TCHAR *pst; DBEXEC(TRUE, DBcQITot++); #if ( _X86_) // QIStub only works for X86 if (IsFlagSet(g_dwDumpFlags, DF_DEBUGQI)) { if (DBpQIFuncTab == NULL) DBpQIFuncTab = new CUniqueTab(SIZEOF(DWORD)); // LONG_PTR? if (DBpQIFuncTab && DBpQIFuncTab->Init()) { int n; int fp = (int) (1 + (int *)&var0); struct DBstkback sbtab[1] = { 0 }; n = DBGetStackBack(&fp, sbtab, ARRAYSIZE(sbtab)); DBpQIFuncTab->Add(sbtab[n - 1].ret); } } #endif if (DBQIuTrace) pst = DBBreakGUID(&riid, DBBRK_ENTER); #endif if (ppv == NULL) return E_POINTER; #if QI_EASY // 1st try the fast way for (pqit = pqitab; pqit->piid != NULL; pqit++) { DBEXEC(DBQIuTrace, (pst = DBBreakGUID(pqit->piid, DBBRK_TRACE))); if (FAST_IsEqualIID(&riid, pqit->piid)) { DBEXEC(TRUE, DBcQIEasy++); goto Lhit; } } #endif // no luck, try the hard way for (pqit = pqitab; pqit->piid != NULL; pqit++) { DBEXEC(DBQIuTrace, (pst = DBBreakGUID(pqit->piid, DBBRK_TRACE))); if (IsEqualIID(riid, *(pqit->piid))) { DBEXEC(TRUE, DBcQIHard++); #if QI_EASY Lhit: #else // keep 'easy' stats anyway DBEXEC(FAST_IsEqualIID(&riid, pqit->piid), DBcQIEasy++); #endif #ifdef DEBUG DBEXEC(TRUE, DBQIStats(pqitab, pqit - pqitab)); #if ( _X86_) // QIStub only works for X86 if (IsFlagSet(g_dwDumpFlags, DF_DEBUGQI)) { IUnknown* punk = (IUnknown*)((LONG_PTR)that + pqit->dwOffset); *ppv = QIStub_CreateInstance(that, punk, riid); if (*ppv) { pst = DBBreakGUID(&riid, DBBRK_S_XXX); return S_OK; } } #endif #endif Lcast: IUnknown* punk = (IUnknown*)((LONG_PTR)that + pqit->dwOffset); DBEXEC(TRUE, (pst = DBBreakGUID(&riid, DBBRK_S_XXX))); punk->AddRef(); *ppv = punk; return S_OK; } } // no luck, try IUnknown (which is implicit in the table) // we try IUnknown last not 1st since stats show it's rare if (IsEqualIID(riid, IID_IUnknown)) { // just use 1st table entry pqit = pqitab; DBEXEC(TRUE, DBcQIUnk++); DBEXEC(TRUE, DBQIStats(pqitab, DBSI_IUNKNOWN)); goto Lcast; } DBEXEC(DBQIuTrace, (pst = DBBreakGUID(&riid, DBBRK_E_XXX))); DBEXEC(TRUE, DBcQIErr++); DBEXEC(TRUE, DBQIStats(pqitab, DBSI_FAIL)); *ppv = NULL; return E_NOINTERFACE; } // } #ifdef DEBUG // { #if ( _X86_) // { QIStub only works for X86 //*** QIStub helpers { class CQIStub { public: virtual void thunk0(); // FEATURE: should AddRef/Release up _iSeq? don't recommend it. virtual STDMETHODIMP_(ULONG) AddRef(void) { _cRef++; return _cRef; } virtual STDMETHODIMP_(ULONG) Release(void) { _cRef--; if (_cRef>0) return _cRef; delete this; return 0; } virtual void thunk3(); virtual void thunk4(); virtual void thunk5(); virtual void thunk6(); virtual void thunk7(); virtual void thunk8(); virtual void thunk9(); virtual void thunk10(); virtual void thunk11(); virtual void thunk12(); virtual void thunk13(); virtual void thunk14(); virtual void thunk15(); virtual void thunk16(); virtual void thunk17(); virtual void thunk18(); virtual void thunk19(); virtual void thunk20(); virtual void thunk21(); virtual void thunk22(); virtual void thunk23(); virtual void thunk24(); virtual void thunk25(); virtual void thunk26(); virtual void thunk27(); virtual void thunk28(); virtual void thunk29(); virtual void thunk30(); virtual void thunk31(); virtual void thunk32(); virtual void thunk33(); virtual void thunk34(); virtual void thunk35(); virtual void thunk36(); virtual void thunk37(); virtual void thunk38(); virtual void thunk39(); virtual void thunk40(); virtual void thunk41(); virtual void thunk42(); virtual void thunk43(); virtual void thunk44(); virtual void thunk45(); virtual void thunk46(); virtual void thunk47(); virtual void thunk48(); virtual void thunk49(); virtual void thunk50(); virtual void thunk51(); virtual void thunk52(); virtual void thunk53(); virtual void thunk54(); virtual void thunk55(); virtual void thunk56(); virtual void thunk57(); virtual void thunk58(); virtual void thunk59(); virtual void thunk60(); virtual void thunk61(); virtual void thunk62(); virtual void thunk63(); virtual void thunk64(); virtual void thunk65(); virtual void thunk66(); virtual void thunk67(); virtual void thunk68(); virtual void thunk69(); virtual void thunk70(); virtual void thunk71(); virtual void thunk72(); virtual void thunk73(); virtual void thunk74(); virtual void thunk75(); virtual void thunk76(); virtual void thunk77(); virtual void thunk78(); virtual void thunk79(); virtual void thunk80(); virtual void thunk81(); virtual void thunk82(); virtual void thunk83(); virtual void thunk84(); virtual void thunk85(); virtual void thunk86(); virtual void thunk87(); virtual void thunk88(); virtual void thunk89(); virtual void thunk90(); virtual void thunk91(); virtual void thunk92(); virtual void thunk93(); virtual void thunk94(); virtual void thunk95(); virtual void thunk96(); virtual void thunk97(); virtual void thunk98(); virtual void thunk99(); protected: CQIStub(void *that, IUnknown* punk, REFIID riid); friend void* QIStub_CreateInstance(void *that, IUnknown* punk, REFIID riid); friend BOOL __stdcall DBIsQIStub(void *that); friend void __stdcall DBDumpQIStub(void *that); friend TCHAR *DBGetQIStubSymbolic(void *that); private: ~CQIStub(); static void *_sar; // C (not C++) ptr to CQIStub::AddRef int _cRef; IUnknown* _punk; // vtable we hand off to void* _that; // "this" pointer of object we stub (for reference) IUnknown* _punkRef; // "punk" (for reference) REFIID _riid; // iid of interface (for reference) int _iSeq; // sequence # TCHAR _szName[GUIDSTR_MAX]; // legible name of interface (for reference) }; struct DBQISeq { GUID * pIid; int iSeq; }; //CASSERT(SIZEOF(GUID *) == SIZEOF(DWORD)); // CUniqueTab uses DWORD's // FEATURE: todo: _declspec(thread) CUniqueTab * DBpQISeqTab = NULL; extern "C" void *Dbg_GetREFIIDAtom(REFIID riid); // lib/dump.c (priv.h?) //*** // NOTES // there's actually a race condition here -- another thread can come in // and do seq++, then we do the reset, etc. -- but the assumption is that // the developer has set the flag in a scenario where this isn't an issue. void DBQIReset(void) { ASSERT(!DBQIfReset); // caller should do test-and-clear if (DBpQISeqTab) DBpQISeqTab->Reset(); return; } void *DBGetVtblEnt(void *that, int i); #define VFUNC_ADDREF 1 // AddRef is vtbl[1] void * CQIStub::_sar = NULL; CQIStub::CQIStub(void* that, IUnknown* punk, REFIID riid) : _cRef(1), _riid(riid) { _that = that; _punk = punk; if (_punk) _punk->AddRef(); _punkRef = _punk; // for reference, so don't AddRef it! // c++ won't let me get &CQIStub::AddRef as a 'real' ptr (!@#$), // so we need to get it the hard way, viz. new'ing an object which // we know inherits it. if (_sar == NULL) { _sar = DBGetVtblEnt((void *)this, VFUNC_ADDREF); ASSERT(_sar != NULL); } StringCchCopy(_szName, ARRAYSIZE(_szName), Dbg_GetREFIIDName(riid)); // generate sequence # if (DBpQISeqTab == NULL) DBpQISeqTab = new CUniqueTab(SIZEOF(struct DBQISeq)); if (DBpQISeqTab && DBpQISeqTab->Init()) { struct DBQISeq *pqiseq; if (InterlockedExchange(&DBQIfReset, FALSE)) DBQIReset(); pqiseq = (struct DBQISeq *) DBpQISeqTab->Add((DWORD) Dbg_GetREFIIDAtom(riid)); if (EVAL(pqiseq)) // (might fail on table overflow) _iSeq = pqiseq->iSeq++; } TraceMsg(TF_QISTUB, "ctor QIStub %s seq=%d (that=%x punk=%x) %x", _szName, _iSeq, _that, _punk, this); } CQIStub::~CQIStub() { TraceMsg(TF_QISTUB, "dtor QIStub %s (that=%x punk=%x) %x", _szName, _that, _punk, this); ATOMICRELEASE(_punk); } STDAPI_(void*) QIStub_CreateInstance(void* that, IUnknown* punk, REFIID riid) { CQIStub* pThis = new CQIStub(that, punk, riid); if (DBQIiSeq == pThis->_iSeq && IsEqualIID(riid, *DBQIiid)) { TCHAR *pst; // BRKPT put brkpt here to brkpt on seq#'th call to 'DBQIiid' iface pst = DBBreakGUID(&riid, DBBRK_BRKPT); } return(pThis); } //*** DBGetVtblEnt -- get vtable entry // NOTES // always uses 1st vtbl (so MI won't work...). void *DBGetVtblEnt(void *that, int i) { void **vptr; void *pfunc; __try { vptr = (void **) *(void **) that; pfunc = (vptr == 0) ? 0 : vptr[i]; } __except(EXCEPTION_EXECUTE_HANDLER) { // since we're called from the DebMemLeak, we're only *guessing* // that we have a vptr etc., so we might fault. TraceMsg(TF_ALWAYS, "gve: GPF"); pfunc = 0; } return pfunc; } //*** DBIsQIStub -- is 'this' a ptr to a 'CQIStub' object? // DESCRIPTION // we look at the vtbl and assume that if we have a ptr to CQIStub::AddRef, // then it's us. // NOTES // M00BUG we do a 'new' in here, which can cause pblms if we're in the middle // of intelli-leak and we end up doing a ReAlloc which moves the heap (raymondc // found such a case). // M00BUG in a release build (w/ identical COMDAT folding) we'll get false // hits since most/all AddRefs are identical and folded. if we ever need to // be more exact we can add a signature and key off that. // M00BUG hack hack we actually return a void *, just in case you want to // know the 'real' object. if that turns out to be useful, we should change // to return a void * instead of a BOOL. BOOL DBIsQIStub(void* that) { void *par; #if 0 if (_sar == NULL) TraceMsg(DM_TRACE, "qis: _sar == NULL"); #endif par = DBGetVtblEnt(that, VFUNC_ADDREF); #if 0 TraceMsg(TF_ALWAYS, "IsQIStub(%x): par=%x _sar=%x", that, _sar, par); #endif return (CQIStub::_sar == par && CQIStub::_sar != NULL) ? (BOOL)((CQIStub *)that)->_punk : 0; #undef VFUNC_ADDREF } TCHAR *DBGetQIStubSymbolic(void* that) { class CQIStub *pqis = (CQIStub *) that; return pqis->_szName; } //*** DBDumpQIStub -- pretty-print a 'CQIStub' // STDAPI_(void) DBDumpQIStub(void* that) { class CQIStub *pqis = (CQIStub *) that; TraceMsg(TF_ALWAYS, "\tqistub(%x): cRef=0x%x iSeq=%x iid=%s", that, pqis->_cRef, pqis->_iSeq, pqis->_szName); } // Memory layout of CQIStub is: // lpVtbl // offset 0 // _cRef // offset 4 // _punk // offset 8 // // "this" pointer stored in stack // // mov eax, ss:4[esp] ; get pThis // mov ecx, 8[eax] ; get real object (_punk) // mov eax, [ecx] ; load the real vtable (_punk->lpVtbl) // ; the above will fault if referenced after we're freed // mov ss:4[esp], ecx ; fix up stack object (_punk) // jmp dword ptr cs:(4*i)[eax] ; jump to the real function // #define QIStubThunk(i) \ void _declspec(naked) CQIStub::thunk##i() \ { \ _asm mov eax, ss:4[esp] \ _asm mov ecx, 8[eax] \ _asm mov eax, [ecx] \ _asm mov ss:4[esp], ecx \ _asm jmp dword ptr cs:(4*i)[eax] \ } QIStubThunk(0); QIStubThunk(3); QIStubThunk(4); QIStubThunk(5); QIStubThunk(6); QIStubThunk(7); QIStubThunk(8); QIStubThunk(9); QIStubThunk(10); QIStubThunk(11); QIStubThunk(12); QIStubThunk(13); QIStubThunk(14); QIStubThunk(15); QIStubThunk(16); QIStubThunk(17); QIStubThunk(18); QIStubThunk(19); QIStubThunk(20); QIStubThunk(21); QIStubThunk(22); QIStubThunk(23); QIStubThunk(24); QIStubThunk(25); QIStubThunk(26); QIStubThunk(27); QIStubThunk(28); QIStubThunk(29); QIStubThunk(30); QIStubThunk(31); QIStubThunk(32); QIStubThunk(33); QIStubThunk(34); QIStubThunk(35); QIStubThunk(36); QIStubThunk(37); QIStubThunk(38); QIStubThunk(39); QIStubThunk(40); QIStubThunk(41); QIStubThunk(42); QIStubThunk(43); QIStubThunk(44); QIStubThunk(45); QIStubThunk(46); QIStubThunk(47); QIStubThunk(48); QIStubThunk(49); QIStubThunk(50); QIStubThunk(51); QIStubThunk(52); QIStubThunk(53); QIStubThunk(54); QIStubThunk(55); QIStubThunk(56); QIStubThunk(57); QIStubThunk(58); QIStubThunk(59); QIStubThunk(60); QIStubThunk(61); QIStubThunk(62); QIStubThunk(63); QIStubThunk(64); QIStubThunk(65); QIStubThunk(66); QIStubThunk(67); QIStubThunk(68); QIStubThunk(69); QIStubThunk(70); QIStubThunk(71); QIStubThunk(72); QIStubThunk(73); QIStubThunk(74); QIStubThunk(75); QIStubThunk(76); QIStubThunk(77); QIStubThunk(78); QIStubThunk(79); QIStubThunk(80); QIStubThunk(81); QIStubThunk(82); QIStubThunk(83); QIStubThunk(84); QIStubThunk(85); QIStubThunk(86); QIStubThunk(87); QIStubThunk(88); QIStubThunk(89); QIStubThunk(90); QIStubThunk(91); QIStubThunk(92); QIStubThunk(93); QIStubThunk(94); QIStubThunk(95); QIStubThunk(96); QIStubThunk(97); QIStubThunk(98); QIStubThunk(99); // } #endif // } #endif // }