/****************************** Module Header ******************************\
* Module Name: ddeutils.c
*
* Purpose: Conatains all the utility routines
*
* Created: 1990
*
* Copyright (c) 1990, 1991 Microsoft Corporation
*
* History:
* Raor, Srinik (../../1990) Designed and coded
*
\***************************************************************************/
#include "ole2int.h"
#include <dde.h>
#include "srvr.h"
#include "ddesrvr.h"
#include "ddedebug.h"
ASSERTDATA
#define WM_NCMOUSEFIRST WM_NCMOUSEMOVE
#define WM_NCMOUSELAST WM_NCMBUTTONDBLCLK
#define KB_64 65536
extern ATOM aTrue;
extern ATOM aFalse;
extern ATOM aStdCreateFromTemplate;
extern ATOM aStdCreate;
extern ATOM aStdOpen;
extern ATOM aStdEdit;
extern ATOM aStdShowItem;
extern ATOM aStdClose;
extern ATOM aStdExit;
extern ATOM aStdDoVerbItem;
//ScanBoolArg: scans the argument which is not included in
//the quotes. These args could be only TRUE or FALSE for
//the time being. !!!The scanning routines should be
//merged and it should be generalized.
INTERNAL_(LPSTR) ScanBoolArg
(
LPSTR lpstr,
BOOL FAR *lpflag
)
{
LPSTR lpbool;
ATOM aShow;
char ch;
lpbool = lpstr;
// !!! These routines does not take care of quoted quotes.
while((ch = *lpstr) && (!(ch == ')' || ch == ',')))
lpstr++;
if(ch == NULL)
return NULL;
*lpstr++ = NULL; // terminate the arg by null
// if terminated by paren, then check for end of command
// syntax.
// Check for the end of the command string.
if (ch == ')') {
if (*lpstr++ != ']')
return NULL;
if(*lpstr != NULL)
return NULL; //finally should be terminated by null.
}
aShow = GlobalFindAtomA (lpbool);
if (aShow == aTrue)
*lpflag = TRUE;
else {
if (aShow ==aFalse)
*lpflag = FALSE;
else
return NULL;;
}
return lpstr;
}
//+---------------------------------------------------------------------------
//
// Function: CreateUnicodeFromAnsi
//
// Synopsis: Creates a UNICODE string from an ANSI string
//
// Effects: Makes a new UNICODE string from the given ANSI string.
// The new UNICODE string is returned. Memory is allocated
// using PrivMemAlloc
//
// Arguments: [lpAnsi] -- Ansi version of string
//
// Requires:
//
// Returns: NULL if cannot create new string.
//
// Signals:
//
// Modifies:
//
// Algorithm:
//
// History: 6-07-94 kevinro Commented/cleaned
//
// Notes:
//
//----------------------------------------------------------------------------
LPOLESTR CreateUnicodeFromAnsi( LPCSTR lpAnsi)
{
WCHAR buf[MAX_PATH];
ULONG ccbuf;
LPOLESTR lpWideStr;
if ((ccbuf=MultiByteToWideChar(CP_ACP,0,lpAnsi,-1,buf,MAX_PATH))
== FALSE)
{
intrAssert(!"Unable to convert characters");
return NULL;
}
lpWideStr = (LPOLESTR) PrivMemAlloc(ccbuf * sizeof(WCHAR));
if (lpWideStr != NULL)
{
memcpy(lpWideStr,buf,ccbuf*sizeof(WCHAR));
}
return(lpWideStr);
}
//+---------------------------------------------------------------------------
//
// Function: CreateAnsiFromUnicode
//
// Synopsis: Creates an Ansi string from a UNICODE string
//
// Effects: Makes a new ANSI string from the given UNICODE string.
// The new string is returned. Memory is allocated
// using PrivMemAlloc
//
// Arguments: [lpUnicode] -- Unicode version of string
//
// Requires:
//
// Returns: NULL if cannot create new string.
//
// Signals:
//
// Modifies:
//
// Algorithm:
//
// History: 6-07-94 kevinro Commented/cleaned
//
// Notes:
//
//----------------------------------------------------------------------------
LPSTR CreateAnsiFromUnicode( LPCOLESTR lpUnicode)
{
char buf[MAX_PATH];
ULONG ccbuf;
LPSTR lpAnsiStr;
ccbuf = WideCharToMultiByte(CP_ACP,
0,
lpUnicode,
-1,
buf,
MAX_PATH,
NULL,
NULL);
if (ccbuf == FALSE)
{
intrAssert(!"Unable to convert characters");
return NULL;
}
lpAnsiStr = (LPSTR) PrivMemAlloc(ccbuf * sizeof(char));
if (lpAnsiStr != NULL)
{
memcpy(lpAnsiStr,buf,ccbuf);
}
return(lpAnsiStr);
}
//ScannumArg: Checks for the syntax of num arg in Execute and if
//the arg is syntactically correct, returns the ptr to the
//beginning of the next arg and also, returns the number
//Does not take care of the last num arg in the list.
INTERNAL_(LPSTR) ScanNumArg
(
LPSTR lpstr,
LPINT lpnum
)
{
WORD val = 0;
char ch;
while((ch = *lpstr++) && (ch != ',')) {
if (ch < '0' || ch >'9')
return NULL;
val += val * 10 + (ch - '0');
}
if(!ch)
return NULL;
*lpnum = val;
return lpstr;
}
//ScanArg: Checks for the syntax of arg in Execute and if
//the arg is syntactically correct, returns the ptr to the
//beginning of the next arg or to the end of the excute string.
INTERNAL_(LPSTR) ScanArg
(
LPSTR lpstr
)
{
// !!! These routines does not take care of quoted quotes.
// first char should be quote.
if (*(lpstr-1) != '\"')
return NULL;
while(*lpstr && *lpstr != '\"')
lpstr++;
if(*lpstr == NULL)
return NULL;
*lpstr++ = NULL; // terminate the arg by null
if(!(*lpstr == ',' || *lpstr == ')'))
return NULL;
if(*lpstr++ == ','){
if(*lpstr == '\"')
return ++lpstr;
// If it is not quote, leave the ptr on the first char
return lpstr;
}
// terminated by paren
// already skiped right paren
// Check for the end of the command string.
if (*lpstr++ != ']')
return NULL;
if(*lpstr != NULL)
return NULL; //finally should be terminated by null.
return lpstr;
}
// ScanCommand: scanns the command string for the syntax
// correctness. If syntactically correct, returns the ptr
// to the first arg or to the end of the string.
INTERNAL_(WORD) ScanCommand
(
LPSTR lpstr,
WORD wType,
LPSTR FAR * lplpnextcmd,
ATOM FAR * lpAtom
)
{
// !!! These routines does not take care of quoted quotes.
// and not taking care of blanks arround the operators
// !!! We are not allowing blanks after operators.
// Should be allright! since this is arestricted syntax.
char ch;
LPSTR lptemp = lpstr;
while(*lpstr && (!(*lpstr == '(' || *lpstr == ']')))
lpstr++;
if(*lpstr == NULL)
return NULL;
ch = *lpstr;
*lpstr++ = NULL; // set the end of command
*lpAtom = GlobalFindAtomA (lptemp);
if (!IsOleCommand (*lpAtom, wType))
return NON_OLE_COMMAND;
if (ch == '(') {
ch = *lpstr++;
if (ch == ')') {
if (*lpstr++ != ']')
return NULL;
}
else {
if (ch != '\"')
return NULL;
}
*lplpnextcmd = lpstr;
return OLE_COMMAND;
}
// terminated by ']'
if (*(*lplpnextcmd = lpstr)) // if no nul termination, then it is error.
return NULL;
return OLE_COMMAND;
}
//MakeDataAtom: Creates a data atom from the item string
//and the item data otions.
INTERNAL_(ATOM) MakeDataAtom
(
ATOM aItem,
int options
)
{
WCHAR buf[MAX_STR];
if (options == OLE_CHANGED)
return DuplicateAtom (aItem);
if (!aItem)
buf[0] = NULL;
else
GlobalGetAtomName (aItem, buf, MAX_STR);
if (options == OLE_CLOSED)
lstrcatW (buf, OLESTR("/Close"));
else {
if (options == OLE_SAVED)
lstrcatW (buf, OLESTR("/Save"));
else
AssertSz (0, "Bad option\n");
}
Puts ("MakeDataAtom "); Puts(buf); Putn();
if (buf[0])
return wGlobalAddAtom (buf);
else
return NULL;
}
//DuplicateAtom: Duplicates an atom
INTERNAL_(ATOM) DuplicateAtom
(
ATOM atom
)
{
WCHAR buf[MAX_STR];
if (!atom)
return NULL;
GlobalGetAtomName (atom, buf, MAX_STR);
return wGlobalAddAtom (buf);
}
// MakeGlobal: makes global out of strings.
// works only for << 64k
INTERNAL_(HANDLE) MakeGlobal
(
LPSTR lpstr
)
{
int len = 0;
HANDLE hdata = NULL;
LPSTR lpdata = NULL;
len = strlen (lpstr) + 1;
hdata = GlobalAlloc (GMEM_MOVEABLE | GMEM_DDESHARE, len);
if (hdata == NULL || (lpdata = (LPSTR) GlobalLock (hdata)) == NULL)
goto errRtn;
memcpy(lpdata, lpstr, (DWORD)len);
GlobalUnlock (hdata);
return hdata;
errRtn:
Assert (0);
if (lpdata)
GlobalUnlock (hdata);
if (hdata)
GlobalFree (hdata);
return NULL;
}
INTERNAL_(BOOL) CLSIDFromAtom(ATOM aClass, LPCLSID lpclsid)
{
WCHAR szProgID[MAX_STR];
if (!ISATOM (aClass))
return FALSE;
WORD cb=GlobalGetAtomName (aClass, szProgID, MAX_STR);
Assert (cb>0 && cb < (MAX_STR - 1));
return CLSIDFromProgID(szProgID, lpclsid) == S_OK;
}
// CLSIDFromAtomWithTreatAs
//
// Input: *paClass
// Output: *pclsid == corresponding CLSID, taking into account TreatAs and
// AutoConvert
// *paClass == atom correpsonding to *pclsid
//
#pragma SEG(CLSIDFromAtomWithTreatAs)
INTERNAL CLSIDFromAtomWithTreatAs
(ATOM FAR* paClass,
LPCLSID pclsid,
CNVTYP FAR* pcnvtyp)
{
HRESULT hr;
intrDebugOut((DEB_ITRACE,
"%p _IN CLSIDFromAtomWithTreatAs(paClass=%x,"
"pclsid=%x,pcnvtyp=%x)\n",0,
paClass,pclsid,pcnvtyp));
LPOLESTR szProgID = NULL;
CLSID clsidNew;
if (!CLSIDFromAtom (*paClass, pclsid))
{
hr = S_FALSE;
goto exitRtn;
}
DEBUG_GUIDSTR(clsidStr,pclsid);
intrDebugOut((DEB_ITRACE,"Guid %ws",clsidStr));
if (CoGetTreatAsClass (*pclsid, &clsidNew) == NOERROR)
{
DEBUG_GUIDSTR(newStr,pclsid);
intrDebugOut((DEB_ITRACE," cnvtypTreatAs %ws\n",newStr));
if (pcnvtyp)
*pcnvtyp = cnvtypTreatAs;
}
else if (OleGetAutoConvert (*pclsid, &clsidNew) == NOERROR)
{
DEBUG_GUIDSTR(newStr,pclsid);
intrDebugOut((DEB_ITRACE," cnvtypConvertTo %ws\n",newStr));
if (pcnvtyp)
*pcnvtyp = cnvtypConvertTo;
}
else
{
intrDebugOut((DEB_ITRACE," no conversion\n"));
if (pcnvtyp)
*pcnvtyp = cnvtypNone;
clsidNew = *pclsid; // no translation
}
hr = ProgIDFromCLSID(clsidNew, &szProgID);
if (FAILED(hr))
{
intrDebugOut((DEB_ITRACE," ProgIDFromCLSID failed\n"));
goto exitRtn;
}
intrDebugOut((DEB_ITRACE,"ProgIDFromCLSID returns %ws\n",szProgID));
*paClass = GlobalAddAtom (szProgID);
*pclsid = clsidNew;
CoTaskMemFree(szProgID);
exitRtn:
intrDebugOut((DEB_ITRACE,
"%p OUT CLSIDFromAtomWithTreatAs returns %x\n",
0,hr));
return hr;
}
INTERNAL_(BOOL) PostMessageToClientWithReply
(
HWND hWnd,
UINT wMsg,
WPARAM wParam, // posting window
LPARAM lParam,
UINT wReplyMsg
)
{
MSG msg;
if (!IsWindowValid (hWnd))
{
AssertSz(FALSE, "Client's window is missing");
return FALSE;
}
if (!IsWindowValid ((HWND)wParam))
{
AssertSz (0, "Posting window is invalid");
return FALSE;
}
// Post message to client failed. Treat it as if we got the reply.
if (!PostMessageToClient (hWnd, wMsg, wParam, lParam))
return FALSE;
return NOERROR == wTimedGetMessage (&msg, (HWND)wParam, WM_DDE_TERMINATE,
WM_DDE_TERMINATE);
}
INTERNAL_(BOOL) PostMessageToClient
(
HWND hWnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam
)
{
UINT c=0;
while (c < 10)
{
if (!IsWindowValid (hWnd)) {
Warn ("Client's window is missing");
return FALSE;
}
Puts ("Posting"); Puth(wMsg); Puts("to"); Puth(hWnd); Putn();
if (PostMessage (hWnd, wMsg, wParam, lParam))
return TRUE; // success
else
{
Yield();
c++; // try again
}
}
return FALSE;
}
INTERNAL_(BOOL) IsWindowValid
(HWND hwnd)
{
HTASK htask;
if (!IsWindow (hwnd))
return FALSE;
htask = GetWindowThreadProcessId(hwnd, NULL);
#ifndef WIN32
if (IsTask(htask))
#endif
return TRUE;
return FALSE;
}
INTERNAL_(BOOL) UtilQueryProtocol
(
ATOM aClass,
LPOLESTR lpprotocol
)
{
HKEY hKey;
WCHAR key[MAX_STR];
WCHAR cclass[MAX_STR];
if (!aClass)
return FALSE;
if (!GlobalGetAtomName (aClass, cclass, MAX_STR))
return FALSE;
lstrcpyW (key, cclass);
lstrcatW (key, OLESTR("\\protocol\\"));
lstrcatW (key, lpprotocol);
lstrcatW (key, OLESTR("\\server"));
if (RegOpenKey (HKEY_CLASSES_ROOT, key, &hKey) != ERROR_SUCCESS)
return FALSE;
RegCloseKey (hKey);
return TRUE;
}
INTERNAL_(BOOL) IsOleCommand
(
ATOM aCmd,
WORD wType
)
{
if (wType == WT_SRVR) {
if ((aCmd == aStdCreateFromTemplate)
|| (aCmd == aStdCreate)
|| (aCmd == aStdOpen)
|| (aCmd == aStdEdit)
|| (aCmd == aStdShowItem)
|| (aCmd == aStdClose)
|| (aCmd == aStdExit))
return TRUE;
}
else {
if ((aCmd == aStdClose)
|| (aCmd == aStdDoVerbItem)
|| (aCmd == aStdShowItem))
return TRUE;
}
return FALSE;
}
INTERNAL wFileBind
(LPOLESTR szFile,
LPUNKNOWN FAR* ppUnk)
{
HRESULT hresult = NOERROR;
LPBC pbc = NULL;
LPMONIKER pmk = NULL;
*ppUnk = NULL;
ErrRtnH (CreateBindCtx (0, &pbc));
ErrRtnH (CreateFileMoniker (szFile, &pmk));
ErrRtnH (pmk->BindToObject (pbc, NULL, IID_IUnknown, (LPLPVOID) ppUnk));
errRtn:
// AssertOutPtrIface(hresult, *ppUnk);
if (pbc)
pbc->Release();
if (pmk)
pmk->Release();
return hresult;
}
// SynchronousPostMessage
//
// Post a message and wait for the ack.
// (jasonful)
//
INTERNAL SynchronousPostMessage
(HWND hWndTo, // also who you expect the reply from
UINT wMsg,
WPARAM wParam,
LPARAM lParam)
{
#ifdef _MAC
#else
HRESULT hresult = NOERROR;
static unsigned iCounter;
HWND hWndFrom = (HWND) wParam;
RetZ (IsWindowValid(hWndFrom));
RetZ (IsWindowValid(hWndTo));
Assert (wMsg != WM_DDE_INITIATE); // can't check for positive ack.
RetZS (PostMessage (hWndTo, wMsg, wParam, lParam), RPC_E_SERVER_DIED);
MSG msg;
RetErr (wTimedGetMessage (&msg, hWndFrom, WM_DDE_ACK, WM_DDE_ACK));
Assert (msg.message == WM_DDE_ACK);
if (!( GET_WM_DDE_ACK_STATUS(msg.wParam,msg.lParam) & POSITIVE_ACK))
hresult = ResultFromScode (RPC_E_DDE_NACK);
if (msg.hwnd != hWndFrom)
hresult = ResultFromScode (RPC_E_DDE_UNEXP_MSG);
return hresult;
#endif _MAC
}
INTERNAL wFileIsRunning
(LPOLESTR szFile)
{
LPMONIKER pmk = NULL;
LPBINDCTX pbc=NULL;
HRESULT hresult;
RetErr (CreateBindCtx (0, &pbc));
ErrRtnH (CreateFileMoniker (szFile, &pmk));
hresult = pmk->IsRunning (pbc, NULL, NULL);
errRtn:
if (pbc)
pbc->Release();
if (pmk)
pmk->Release();
return hresult;
}
//+---------------------------------------------------------------------------
//
// Function: IsFile
//
// Synopsis: Given a handle to an atom, determine if it is a file
//
// Effects: Attempts to get the files attributes. If there are no
// attributes, then the file doesn't exist.
//
// Arguments: [a] -- Atom for filename
//
// Requires:
//
// Returns:
//
// Signals:
//
// Modifies:
//
// Algorithm:
//
// History: 5-03-94 kevinro Commented/cleaned
//
// Notes:
//
//----------------------------------------------------------------------------
INTERNAL_ (BOOL) IsFile
(ATOM a, BOOL FAR* pfUnsavedDoc)
{
LPMONIKER pmk = NULL;
LPBC pbc = NULL;
LPRUNNINGOBJECTTABLE pROT=NULL;
WCHAR szFile [MAX_STR];
if (0==GlobalGetAtomName (a, szFile, MAX_STR))
return FALSE;
DWORD dwAttribs = GetFileAttributes(szFile);
/* flags prevent sharing violation*/
if (dwAttribs != 0xFFFFFFFF)
{
if (pfUnsavedDoc)
*pfUnsavedDoc = FALSE;
return TRUE;
}
// This will deal with unsaved documents in the ROT.
// We do NOT want to call pmk->IsRunning because if a 2.0 client called
// DdeIsRunning, we do not want call it here, because then we get stuck
// in an infinite loop. We only care about true 2.0 running objects.
//
// BUGBUG: KevinRo There is a function (GetPathFromRot) that could replace
// the following code sequence.
//
BOOL f= NOERROR==CreateBindCtx (0, &pbc) &&
NOERROR==CreateFileMoniker (szFile, &pmk) &&
NOERROR==pbc->GetRunningObjectTable (&pROT) &&
NOERROR==pROT->IsRunning (pmk) ;
if (pROT)
pROT->Release();
if (pmk)
pmk->Release();
if (pbc)
pbc->Release();
if (pfUnsavedDoc)
*pfUnsavedDoc = TRUE;
return f;
}
// wCompatibleClasses
//
// Determine if class "aClient" is Auto-Converted to class "aSrvr" or
// Treated-As class "aSrvr".
// (Does not check if aClient==aSrvr)
//
#pragma SEG(wCompatibleClasses)
INTERNAL wCompatibleClasses
(ATOM aClient,
ATOM aSrvr)
{
CLSID clsidClient, clsidSrvr, clsidTo;
HRESULT hresult;
RetZS (CLSIDFromAtom (aClient, &clsidClient), S_FALSE);
RetZS (CLSIDFromAtom (aSrvr, &clsidSrvr ), S_FALSE);
if (NOERROR==OleGetAutoConvert (clsidClient, &clsidTo)
&& clsidTo == clsidSrvr)
{
// aClient is Auto-Converted to aSrvr
return NOERROR;
}
hresult = CoGetTreatAsClass(clsidClient, &clsidTo);
if (hresult != NOERROR)
{
intrDebugOut((DEB_IERROR,
"wCompatibleClasses CoGetTreatAs returns %x\n",
hresult));
return(hresult);
}
if (clsidTo == clsidSrvr)
{
// aClient is Treated-As aSrvr
return NOERROR;
}
return ResultFromScode (S_FALSE); // not compatible
}
// wCreateStgAroundNative
//
// Build an OLE2 storage around 1.0 native data by putting it in
// stream "\1Ole10Native" and creating valid CompObj and OLE streams.
// Return the IStorage and the ILockBytes it is built on.
//
INTERNAL wCreateStgAroundNative
(HANDLE hNative,
ATOM aClassOld,
ATOM aClassNew,
CNVTYP cnvtyp,
ATOM aItem,
LPSTORAGE FAR* ppstg,
LPLOCKBYTES FAR* pplkbyt)
{
HRESULT hresult;
LPSTORAGE pstg = NULL;
LPLOCKBYTES plkbyt = NULL;
LPOLESTR szUserType = NULL;
WCHAR szClassOld [256];
CLSID clsid;
ATOM aClass;
*ppstg = NULL;
intrDebugOut((DEB_ITRACE,
"%p wCreateStgAroundNative(hNative=%x,aClassOld=%x"
",aClassNew=%x cnvtyp=%x,aItem=%x)\n",
0,hNative,aClassOld,aClassNew,cnvtyp,aItem));
// Create temporary docfile on our ILockBytes
ErrRtnH (CreateILockBytesOnHGlobal (NULL,/*fDeleteOnRelease*/TRUE,&plkbyt));
Assert (plkbyt);
ErrRtnH (StgCreateDocfileOnILockBytes (plkbyt, grfCreateStg, 0, &pstg));
RetZ (pstg);
Assert (NOERROR==StgIsStorageILockBytes(plkbyt));
aClass = (cnvtyp == cnvtypConvertTo)?aClassNew:aClassOld;
if (CLSIDFromAtom (aClass,(LPCLSID)&clsid) == FALSE)
{
hresult = REGDB_E_CLASSNOTREG;
goto errRtn;
}
ErrRtnH (WriteClassStg (pstg, clsid));
// The UserType always corresponds to the clsid.
ErrRtnH (OleRegGetUserType (clsid, USERCLASSTYPE_FULL, &szUserType));
// The format is always the 1.0 format (classname/progid)
ErrZS (GlobalGetAtomName (aClassOld, szClassOld, 256), E_UNEXPECTED);
ErrRtnH (WriteFmtUserTypeStg (pstg, RegisterClipboardFormat(szClassOld),
szUserType));
if (cnvtyp == cnvtypConvertTo)
{
// SetConvertStg also writes a complete default Ole Stream
ErrRtnH (SetConvertStg (pstg, TRUE));
}
else
{
ErrRtnH (WriteOleStg (pstg, NULL, (CLIPFORMAT)0, NULL));
}
ErrRtnH (StSave10NativeData (pstg, hNative, FALSE));
if (aItem)
{
ErrRtnH (StSave10ItemName (pstg, wAtomNameA (aItem)));
}
*ppstg = pstg;
*pplkbyt = plkbyt;
return NOERROR;
errRtn:
if (pstg)
pstg->Release();
if (plkbyt)
plkbyt->Release();
CoTaskMemFree(szUserType);
return hresult;
}
#ifdef _DEBUG
INTERNAL_ (BOOL) IsAtom (ATOM a)
{
WCHAR sz[256]= {0};
if (a < 0xc000)
return FALSE;
WORD cb=GlobalGetAtomName (a, sz, 256);
Assert (lstrlenW(sz) == (int) cb);
return cb>0 && cb < MAX_STR;
}
#include <limits.h>
#undef GlobalFree
INTERNAL_(HANDLE) wGlobalFree (HANDLE h)
{
LPVOID p;
Assert ((GlobalFlags(h) & GMEM_LOCKCOUNT)==0);
if (!(p=GlobalLock(h)))
{
Puts ("Cannot free handle");
Puth (h);
Putn();
AssertSz(0, "Invalid Handle\r\n");
}
Assert (!IsBadReadPtr (p, (UINT) min (UINT_MAX, GlobalSize(h))));
Assert (GlobalUnlock(h)==0);
Verify (!GlobalFree (h));
Puts ("FREEING ");
Puth (h);
Putn ();
return NULL; // success
}
#undef GlobalDeleteAtom
INTERNAL_(ATOM) wGlobalDeleteAtom (ATOM a)
{
WCHAR sz[256];
Assert (0 != GlobalGetAtomName (a, sz, 256));
Assert (0==GlobalDeleteAtom (a));
return (ATOM)0;
}
INTERNAL_(int) wCountChildren
(HWND h)
{
int c = 0;
HWND hwndChild = GetWindow (h, GW_CHILD);
while (hwndChild)
{
c++;
hwndChild = GetWindow (hwndChild, GW_HWNDNEXT);
}
return c;
}
#endif // _DEBUG