windows-nt/Source/XPSP1/NT/base/mvdm/wow16/ddeml/dmgdb.c
2020-09-26 16:20:57 +08:00

955 lines
23 KiB
C

/****************************** Module Header ******************************\
* Module Name: DMGDB.C
*
* DDE manager data handling routines
*
* Created: 12/14/88 Sanford Staab
*
* Copyright (c) 1988, 1989 Microsoft Corporation
\***************************************************************************/
#include "ddemlp.h"
/***************************** Private Function ****************************\
* PAPPINFO GetCurrentAppInfo()
*
* DESCRIPTION:
* This routine uses the pid of the current thread to locate the information
* pertaining to that thread. If not found, 0 is returned.
*
* This call fails if the DLL is in a callback state to prevent recursion.
* if fChkCallback is set.
*
* History: 1/1/89 Created sanfords
\***************************************************************************/
PAPPINFO GetCurrentAppInfo(
PAPPINFO paiStart)
{
register PAPPINFO pai;
HANDLE hTaskCurrent;
SEMENTER();
if (pAppInfoList == NULL) {
SEMLEAVE();
return(0);
}
pai = paiStart ? paiStart->next : pAppInfoList;
hTaskCurrent = GetCurrentTask();
while (pai) {
if (pai->hTask == hTaskCurrent) {
SEMLEAVE();
return(pai);
}
pai = pai->next;
}
SEMLEAVE();
return(0);
}
/***************************** Private Function ****************************\
* void UnlinkAppInfo(pai)
* PAPPINFO pai;
*
* DESCRIPTION:
* unlinks an pai safely. Does nothing if not linked.
*
* History: 1/1/89 Created sanfords
\***************************************************************************/
void UnlinkAppInfo(pai)
PAPPINFO pai;
{
PAPPINFO paiT;
AssertF(pai != NULL, "UnlinkAppInfo - NULL input");
SEMENTER();
if (pai == pAppInfoList) {
pAppInfoList = pai->next;
SEMLEAVE();
return;
}
paiT = pAppInfoList;
while (paiT && paiT->next != pai)
paiT = paiT->next;
if (paiT)
paiT->next = pai->next;
SEMLEAVE();
return;
}
/***************************** Private Functions ***************************\
* General List management functions.
*
* History:
* Created 12/15/88 sanfords
\***************************************************************************/
PLST CreateLst(hheap, cbItem)
HANDLE hheap;
WORD cbItem;
{
PLST pLst;
SEMENTER();
if (!(pLst = (PLST)FarAllocMem(hheap, sizeof(LST)))) {
SEMLEAVE();
return(NULL);
}
pLst->hheap = hheap;
pLst->cbItem = cbItem;
pLst->pItemFirst = (PLITEM)NULL;
SEMLEAVE();
return(pLst);
}
void DestroyLst(pLst)
PLST pLst;
{
if (pLst == NULL)
return;
SEMENTER();
while (pLst->pItemFirst)
RemoveLstItem(pLst, pLst->pItemFirst);
FarFreeMem((LPSTR)pLst);
SEMLEAVE();
}
void DestroyAdvLst(pLst)
PLST pLst;
{
if (pLst == NULL)
return;
SEMENTER();
while (pLst->pItemFirst) {
FreeHsz(((PADVLI)(pLst->pItemFirst))->aItem);
RemoveLstItem(pLst, pLst->pItemFirst);
}
FarFreeMem((LPSTR)pLst);
SEMLEAVE();
}
PLITEM FindLstItem(pLst, npfnCmp, piSearch)
PLST pLst;
NPFNCMP npfnCmp;
PLITEM piSearch;
{
PLITEM pi;
if (pLst == NULL)
return(NULL);
SEMENTER();
pi = pLst->pItemFirst;
while (pi) {
if ((*npfnCmp)((LPBYTE)pi + sizeof(LITEM), (LPBYTE)piSearch + sizeof(LITEM))) {
SEMLEAVE();
return(pi);
}
pi = pi->next;
}
SEMLEAVE();
return(pi);
}
/*
* Comparison functions for FindLstItem() and FindPileItem()
*/
BOOL CmpDWORD(pb1, pb2)
LPBYTE pb1;
LPBYTE pb2;
{
return(*(LPDWORD)pb1 == *(LPDWORD)pb2);
}
BOOL CmpWORD(pb1, pb2)
LPBYTE pb1;
LPBYTE pb2;
{
return(*(LPWORD)pb1 == *(LPWORD)pb2);
}
BOOL CmpHIWORD(pb1, pb2)
LPBYTE pb1;
LPBYTE pb2;
{
return(*(LPWORD)(pb1 + 2) == *(LPWORD)(pb2 + 2));
}
/***************************** Private Function ****************************\
* This routine creates a new list item for pLst and links it in according
* to the ILST_ constant in afCmd. Returns a pointer to the new item
* or NULL on failure.
*
* Note: This MUST be in the semaphore for use since the new list item
* is filled with garbage on return yet is linked in.
*
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
PLITEM NewLstItem(pLst, afCmd)
PLST pLst;
WORD afCmd;
{
PLITEM pi, piT;
if (pLst == NULL)
return(NULL);
SEMCHECKIN();
pi = (PLITEM)FarAllocMem(pLst->hheap, pLst->cbItem + sizeof(LITEM));
if (pi == NULL) {
AssertF(FALSE, "NewLstItem - memory failure");
return(NULL);
}
if (afCmd & ILST_NOLINK)
return(pi);
if (((piT = pLst->pItemFirst) == NULL) || (afCmd & ILST_FIRST)) {
pi->next = piT;
pLst->pItemFirst = pi;
} else { /* ILST_LAST assumed */
while (piT->next != NULL)
piT = piT->next;
piT->next = pi;
pi->next = NULL;
}
return(pi);
}
/***************************** Private Function ****************************\
* This routine unlinks and frees pi from pLst. If pi cannot be located
* within pLst, it is freed anyway.
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
BOOL RemoveLstItem(pLst, pi)
PLST pLst;
PLITEM pi;
{
PLITEM piT;
if (pLst == NULL || pi == NULL)
return(FALSE);
SEMCHECKIN();
if ((piT = pLst->pItemFirst) != NULL) {
if (pi == piT) {
pLst->pItemFirst = pi->next;
} else {
while (piT->next != pi && piT->next != NULL)
piT = piT->next;
if (piT->next != NULL)
piT->next = pi->next; /* unlink */
}
} else {
AssertF(FALSE, "Improper list item removal");
return(FALSE);
}
FarFreeMem((LPSTR)pi);
return(TRUE);
}
/*
* ------------- Specific list routines -------------
*/
/***************************** Private Function ****************************\
* hwnd-hsz list functions
*
* History: 1/20/89 Created sanfords
\***************************************************************************/
void AddHwndHszList(
ATOM a,
HWND hwnd,
PLST pLst)
{
PHWNDHSZLI phhi;
AssertF(pLst->cbItem == sizeof(HWNDHSZLI), "AddHwndHszList - Bad item size");
SEMENTER();
if (!a || (BOOL)HwndFromHsz(a, pLst)) {
SEMLEAVE();
return;
}
phhi = (PHWNDHSZLI)NewLstItem(pLst, ILST_FIRST);
phhi->hwnd = hwnd;
phhi->a = a;
IncHszCount(a); // structure copy
SEMLEAVE();
}
void DestroyHwndHszList(pLst)
PLST pLst;
{
if (pLst == NULL)
return;
AssertF(pLst->cbItem == sizeof(HWNDHSZLI), "DestroyHwndHszList - Bad item size");
SEMENTER();
while (pLst->pItemFirst) {
FreeHsz(((PHWNDHSZLI)pLst->pItemFirst)->a);
RemoveLstItem(pLst, pLst->pItemFirst);
}
FarFreeMem((LPSTR)pLst);
SEMLEAVE();
}
HWND HwndFromHsz(
ATOM a,
PLST pLst)
{
HWNDHSZLI hhli;
PHWNDHSZLI phhli;
hhli.a = a;
if (!(phhli = (PHWNDHSZLI)FindLstItem(pLst, CmpWORD, (PLITEM)&hhli)))
return(NULL);
return(phhli->hwnd);
}
/***************************** Private Function ****************************\
* DESCRIPTION:
* Advise list helper functions.
*
* History: 1/20/89 Created sanfords
\***************************************************************************/
/*
* This will match an exact hsz/fmt pair with a 0 format or 0 item or 0 hwnd
* being wild.
*/
BOOL CmpAdv(
LPBYTE pb1, // entry being compared
LPBYTE pb2) // search for
{
PADVLI pali1 = (PADVLI)(pb1 - sizeof(LITEM));
PADVLI pali2 = (PADVLI)(pb2 - sizeof(LITEM));
if (pali2->aTopic == 0 || pali1->aTopic == pali2->aTopic) {
if (pali2->hwnd == 0 || pali1->hwnd == pali2->hwnd) {
if (pali2->aItem == 0 || pali1->aItem == pali2->aItem ) {
if (pali2->wFmt == 0 || pali1->wFmt == pali2->wFmt) {
return(TRUE);
}
}
}
}
return(FALSE);
}
WORD CountAdvReqLeft(
PADVLI pali)
{
ADVLI aliKey;
register WORD cLoops = 0;
SEMENTER();
aliKey = *pali;
aliKey.hwnd = 0; // all hwnds
pali = (PADVLI)aliKey.next;
while (pali) {
if (CmpAdv(((LPBYTE)pali) + sizeof(LITEM),
((LPBYTE)&aliKey) + sizeof(LITEM))) {
cLoops++;
}
pali = (PADVLI)pali->next;
}
SEMLEAVE();
return(cLoops);
}
BOOL AddAdvList(
PLST pLst,
HWND hwnd,
ATOM aTopic,
ATOM aItem,
WORD fsStatus,
WORD wFmt)
{
PADVLI pali;
AssertF(pLst->cbItem == sizeof(ADVLI), "AddAdvList - bad item size");
if (!aItem)
return(TRUE);
SEMENTER();
if (!(pali = FindAdvList(pLst, hwnd, aTopic, aItem, wFmt))) {
IncHszCount(aItem); // structure copy
pali = (PADVLI)NewLstItem(pLst, ILST_FIRST);
}
AssertF((BOOL)(DWORD)pali, "AddAdvList - NewLstItem() failed")
if (pali != NULL) {
pali->aItem = aItem;
pali->aTopic = aTopic;
pali->wFmt = wFmt;
pali->fsStatus = fsStatus;
pali->hwnd = hwnd;
}
SEMLEAVE();
return((BOOL)(DWORD)pali);
}
/*
* This will delete the matching Advise loop entry. If wFmt is 0, all
* entries with the same hszItem are deleted.
* Returns fNotEmptyAfterDelete.
*/
BOOL DeleteAdvList(
PLST pLst,
HWND hwnd,
ATOM aTopic,
ATOM aItem,
WORD wFmt)
{
PADVLI pali;
AssertF(pLst->cbItem == sizeof(ADVLI), "DeleteAdvList - bad item size");
SEMENTER();
while (pali = (PADVLI)FindAdvList(pLst, hwnd, aTopic, aItem, wFmt)) {
FreeHsz(pali->aItem);
RemoveLstItem(pLst, (PLITEM)pali);
}
SEMLEAVE();
return((BOOL)(DWORD)pLst->pItemFirst);
}
/***************************** Private Function ****************************\
* This routine searches the advise list for and entry in hszItem. It returns
* pAdvli only if the item is found.
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
PADVLI FindAdvList(
PLST pLst,
HWND hwnd,
ATOM aTopic,
ATOM aItem,
WORD wFmt)
{
ADVLI advli;
AssertF(pLst->cbItem == sizeof(ADVLI), "FindAdvList - bad item size");
advli.aItem = aItem;
advli.aTopic = aTopic;
advli.wFmt = wFmt;
advli.hwnd = hwnd;
return((PADVLI)FindLstItem(pLst, CmpAdv, (PLITEM)&advli));
}
/***************************** Private Function ****************************\
* This routine searches for the next entry for hszItem. It returns
* pAdvli only if the item is found. aTopic and hwnd should NOT be 0.
*
* History:
* Created 11/15/89 Sanfords
\***************************************************************************/
PADVLI FindNextAdv(
PADVLI padvli,
HWND hwnd,
ATOM aTopic,
ATOM aItem)
{
SEMENTER();
while ((padvli = (PADVLI)padvli->next) != NULL) {
if (hwnd == 0 || hwnd == padvli->hwnd) {
if (aTopic == 0 || aTopic == padvli->aTopic) {
if (aItem == 0 || padvli->aItem == aItem) {
break;
}
}
}
}
SEMLEAVE();
return(padvli);
}
/***************************** Private Function ****************************\
* This routine removes all list items associated with hwnd.
*
* History:
* Created 4/17/91 Sanfords
\***************************************************************************/
VOID CleanupAdvList(
HWND hwnd,
PCLIENTINFO pci)
{
PADVLI pali, paliNext;
PLST plst;
if (pci->ci.fs & ST_CLIENT) {
plst = pci->pClientAdvList;
} else {
plst = pci->ci.pai->pServerAdvList;
}
AssertF(plst->cbItem == sizeof(ADVLI), "CleanupAdvList - bad item size");
SEMENTER();
for (pali = (PADVLI)plst->pItemFirst; pali; pali = paliNext) {
paliNext = (PADVLI)pali->next;
if (pali->hwnd == hwnd) {
MONLINK(pci->ci.pai, FALSE, pali->fsStatus & DDE_FDEFERUPD,
(HSZ)pci->ci.aServerApp, (HSZ)pci->ci.aTopic,
(HSZ)pali->aItem, pali->wFmt,
(pci->ci.fs & ST_CLIENT) ? FALSE : TRUE,
(pci->ci.fs & ST_CLIENT) ?
pci->ci.hConvPartner : MAKEHCONV(hwnd),
(pci->ci.fs & ST_CLIENT) ?
MAKEHCONV(hwnd) : pci->ci.hConvPartner);
FreeHsz(pali->aItem);
RemoveLstItem(plst, (PLITEM)pali);
}
}
SEMLEAVE();
}
/***************************** Pile Functions ********************************\
*
* A pile is a list where each item is an array of subitems. This allows
* a more memory efficient method of handling unordered lists.
*
\*****************************************************************************/
PPILE CreatePile(hheap, cbItem, cItemsPerBlock)
HANDLE hheap;
WORD cbItem;
WORD cItemsPerBlock;
{
PPILE ppile;
if (!(ppile = (PPILE)FarAllocMem(hheap, sizeof(PILE)))) {
SEMLEAVE();
return(NULL);
}
ppile->pBlockFirst = NULL;
ppile->hheap = hheap;
ppile->cbBlock = cbItem * cItemsPerBlock + sizeof(PILEB);
ppile->cSubItemsMax = cItemsPerBlock;
ppile->cbSubItem = cbItem;
return(ppile);
}
PPILE DestroyPile(pPile)
PPILE pPile;
{
if (pPile == NULL)
return(NULL);
SEMENTER();
while (pPile->pBlockFirst)
RemoveLstItem((PLST)pPile, (PLITEM)pPile->pBlockFirst);
FarFreeMem((LPSTR)pPile);
SEMLEAVE();
return(NULL);
}
WORD QPileItemCount(pPile)
PPILE pPile;
{
register WORD c;
PPILEB pBlock;
if (pPile == NULL)
return(0);
SEMENTER();
pBlock = pPile->pBlockFirst;
c = 0;
while (pBlock) {
c += pBlock->cItems;
pBlock = pBlock->next;
}
SEMLEAVE();
return(c);
}
/***************************** Private Function ****************************\
* Locate and return the pointer to the pile subitem who's key fields match
* pbSearch using npfnCmp to compare the fields. If pbSearch == NULL, or
* npfnCmp == NULL, the first subitem is returned.
*
* afCmd may be:
* FPI_DELETE - delete the located item
* In this case, the returned pointer is not valid.
*
* pppb points to where to store a pointer to the block which contained
* the located item.
*
* if pppb == NULL, it is ignored.
*
* NULL is returned if pbSearch was not found or if the list was empty.
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
LPBYTE FindPileItem(pPile, npfnCmp, pbSearch, afCmd)
PPILE pPile;
NPFNCMP npfnCmp;
LPBYTE pbSearch;
WORD afCmd;
{
LPBYTE psi; // subitem pointer.
PPILEB pBlockCur; // current block pointer.
register WORD i;
if (pPile == NULL)
return(NULL);
SEMENTER();
pBlockCur = pPile->pBlockFirst;
/*
* while this block is not the end...
*/
while (pBlockCur) {
for (psi = (LPBYTE)pBlockCur + sizeof(PILEB), i = 0;
i < pBlockCur->cItems;
psi += pPile->cbSubItem, i++) {
if (pbSearch == NULL || npfnCmp == NULL || (*npfnCmp)(psi, pbSearch)) {
if (afCmd & FPI_DELETE) {
/*
* remove entire block if this was the last subitem in it.
*/
if (--pBlockCur->cItems == 0) {
RemoveLstItem((PLST)pPile, (PLITEM)pBlockCur);
} else {
/*
* copy last subitem in the block over the removed item.
*/
hmemcpy(psi, (LPBYTE)pBlockCur + sizeof(PILEB) +
pPile->cbSubItem * pBlockCur->cItems,
pPile->cbSubItem);
}
}
return(psi); // found
}
}
pBlockCur = (PPILEB)pBlockCur->next;
}
SEMLEAVE();
return(NULL); // not found.
}
/***************************** Private Function ****************************\
* Places a copy of the subitem pointed to by pb into the first available
* spot in the pile pPile. If npfnCmp != NULL, the pile is first searched
* for a pb match. If found, pb replaces the located data.
*
* Returns:
* API_FOUND if already there
* API_ERROR if an error happened
* API_ADDED if not found and added
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
WORD AddPileItem(pPile, pb, npfnCmp)
PPILE pPile;
LPBYTE pb;
BOOL (*npfnCmp)(LPBYTE pbb, LPBYTE pbSearch);
{
LPBYTE pbDst;
PPILEB ppb;
if (pPile == NULL)
return(FALSE);
SEMENTER();
if (npfnCmp != NULL && (pbDst = FindPileItem(pPile, npfnCmp, pb, 0)) !=
NULL) {
hmemcpy(pbDst, pb, pPile->cbSubItem);
SEMLEAVE();
return(API_FOUND);
}
ppb = pPile->pBlockFirst;
/*
* locate a block with room
*/
while ((ppb != NULL) && ppb->cItems == pPile->cSubItemsMax) {
ppb = (PPILEB)ppb->next;
}
/*
* If all full or no blocks, make a new one, link it on the bottom.
*/
if (ppb == NULL) {
ppb = (PPILEB)NewLstItem((PLST)pPile, ILST_LAST);
if (ppb == NULL) {
SEMLEAVE();
return(API_ERROR);
}
ppb->cItems = 0;
}
/*
* add the subitem
*/
hmemcpy((LPBYTE)ppb + sizeof(PILEB) + pPile->cbSubItem * ppb->cItems++,
pb, pPile->cbSubItem);
SEMLEAVE();
return(API_ADDED);
}
/***************************** Private Function ****************************\
* Fills pb with a copy of the top item's data and removes it from the pile.
* returns FALSE if the pile was empty.
*
* History:
* Created 9/12/89 Sanfords
\***************************************************************************/
BOOL PopPileSubitem(pPile, pb)
PPILE pPile;
LPBYTE pb;
{
PPILEB ppb;
LPBYTE pSrc;
if ((pPile == NULL) || ((ppb = pPile->pBlockFirst) == NULL))
return(FALSE);
SEMENTER();
pSrc = (LPBYTE)pPile->pBlockFirst + sizeof(PILEB);
hmemcpy(pb, pSrc, pPile->cbSubItem);
/*
* remove entire block if this was the last subitem in it.
*/
if (pPile->pBlockFirst->cItems == 1) {
RemoveLstItem((PLST)pPile, (PLITEM)pPile->pBlockFirst);
} else {
/*
* move last item in block to replace copied subitem and decrement
* subitem count.
*/
hmemcpy(pSrc, pSrc + pPile->cbSubItem * --pPile->pBlockFirst->cItems,
pPile->cbSubItem);
}
SEMLEAVE();
return(TRUE);
}
#if 0
/***************************** Semaphore Functions *************************\
* SEMENTER() and SEMLEAVE() are macros.
*
* History: 1/1/89 Created sanfords
\***************************************************************************/
void SemInit()
{
LPBYTE pSem;
SHORT c;
pSem = (LPBYTE) & FSRSemDmg;
c = 0;
while (c++ < sizeof(DOSFSRSEM)) {
*pSem++ = 0;
}
FSRSemDmg.cb = sizeof(DOSFSRSEM);
}
void SemCheckIn()
{
PIDINFO pi;
BOOL fin;
DosGetPID(&pi);
fin = (FSRSemDmg.cUsage > 0) && (FSRSemDmg.pid == pi.pid) && ((FSRSemDmg.tid ==
pi.tid) || (FSRSemDmg.tid == -1));
/*
* !!! NOTE: during exitlists processing, semaphore TIDs are set to -1
*/
AssertF(fin, "SemCheckIn - Out of Semaphore");
if (!fin)
SEMENTER();
}
void SemCheckOut()
{
PIDINFO pi;
BOOL fOut;
DosGetPID(&pi);
fOut = FSRSemDmg.cUsage == 0 || FSRSemDmg.pid != pi.pid || FSRSemDmg.tid !=
pi.tid;
AssertF(fOut, "SemCheckOut - In Semaphore");
if (!fOut)
while (FSRSemDmg.cUsage)
SEMLEAVE();
}
void SemEnter()
{
DosFSRamSemRequest(&FSRSemDmg, SEM_INDEFINITE_WAIT);
}
void SemLeave()
{
DosFSRamSemClear(&FSRSemDmg);
}
#endif // 0
BOOL CopyHugeBlock(pSrc, pDst, cb)
LPBYTE pSrc;
LPBYTE pDst;
DWORD cb;
{
DWORD cFirst;
/*
* |____________| |___________| |____________| |____________|
* ^src ^
*
* |____________| |___________| |____________| |____________|
* ^dst ^
*/
/*
* The following check determines whether the copy can be done
* in one short copy operation. Checks whether the byte count
* is small enough to span the bytes to the right of the greater
* of pSrc and pDst.
*/
cFirst = (DWORD)min(~LOWORD((DWORD)pSrc), ~LOWORD((DWORD)pDst)) + 1L;
/* cFirst is # of bytes to end of seg, for buffer w/ biggest offset */
if (cb < cFirst) {
hmemcpy(pDst, pSrc, (WORD)cb);
return(TRUE);
}
goto copyit; /* if not, jump into while loop */
/*
* Now at least one of the pointers is on a segment boundry.
*/
while (cb) {
cFirst = min(0x10000 - (LOWORD((DWORD)pSrc) | LOWORD((DWORD)pDst)), (LONG)cb);
copyit:
if (HIWORD(cFirst)) {
/*
* special case where pSrc and pDst both are on segment
* bounds. Copy half at a time. First half first.
*/
/*
* |___________| |____________| |____________|
* ^src ^
*
* |___________| |____________| |____________|
* ^dst ^
*/
cFirst >>= 1; /* half the span */
hmemcpy(pDst, pSrc, (WORD)cFirst);
pSrc += cFirst; /* inc ptrs */
pDst += cFirst;
cb -= cFirst; /* dec bytecount */
}
hmemcpy(pDst, pSrc, (WORD)cFirst);
pSrc = HugeOffset(pSrc, cFirst);
pDst = HugeOffset(pDst, cFirst);
cb -= cFirst;
/*
* |____________| |___________| |____________| |____________|
* ^src ^
*
* |____________| |___________| |____________| |____________|
* ^dst ^
*/
}
return(TRUE);
}
/***************************************************************************\
* Kills windows but avoids invalid window rips in debugger.
\***************************************************************************/
BOOL DmgDestroyWindow(hwnd)
HWND hwnd;
{
if (IsWindow(hwnd))
return(DestroyWindow(hwnd));
return(TRUE);
}
BOOL ValidateHConv(
HCONV hConv)
{
return(IsWindow((HWND)hConv) &&
GetWindowWord((HWND)hConv, GWW_CHECKVAL) == HIWORD(hConv));
}
#ifdef DEBUG
void _loadds fAssert(
BOOL f,
LPSTR pszComment,
WORD line,
LPSTR szfile,
BOOL fWarning)
{
char szT[90];
if (!f) {
wsprintf(szT, "\n\rAssertion failure: %s:%d %s\n\r",
szfile, line, pszComment);
OutputDebugString((LPSTR)szT);
if (!fWarning)
DEBUGBREAK();
}
}
#endif /* DEBUG */