|
|
//*** 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 <strsafe.h>
#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 // }
|