windows-nt/Source/XPSP1/NT/shell/inc/qistub.cpp
2020-09-26 16:20:57 +08:00

855 lines
22 KiB
C++

//*** 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"
#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:
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)
{
InitializeCriticalSection(&_hLock);
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;
}
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 += wsprintf(p, TEXT(" %d"), DBcStats[i]);
}
p += wsprintf(p, 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) {
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);
}
StrCpyN(_szName, Dbg_GetREFIIDName(riid), ARRAYSIZE(_szName));
// generate sequence #
if (DBpQISeqTab == NULL)
DBpQISeqTab = new CUniqueTab(SIZEOF(struct DBQISeq));
if (DBpQISeqTab) {
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 // }