Download demo executable - 38 Kb
Download project files - 92 Kb

This article presents a comprehensive system control manager for NT.
This program was born because of a controversy among the programmers' team
I work in. One day, full of MFC resource leaks, I decide to take full control
on code. There were too many image lists, bitmaps and/or fonts handles not released
by MFC library, and NuMega BoundsChecker drives me crazy at the end of debugging.
(One day a Microsoft recruiter asked me: "what was the hardest decision you
had to take as a programmer?". I replied: "use of C, after that, C++ and now
I have go back to C". As consequence, this article does not contain any class,
any MFC improvement. As a matter of fact, no class libraries are used and there
is no .cpp file in the entire project. (I have to mention that for the readers
who may not be interested in good ol' C programming). I believe the string table
entries have quite suggestive names, so I prefer not to explain very deeply
some of the error messages, but rather let the reader to follow the idea and
supply messages with some imagination.
0. Some words about using source code and/or binaries: as 'About...'
box says, use of source code and/or binaries is permitted only with my acceptance
or by providing in Readme files, help files and/or 'About' box this mention:
"Portions developed by Zero Software Group (http://www.swac.8m.com/)".
1. Required components. In the application's initialization/checker
routine, Initialize
, there are performed some checking to see if
the program will have a chance to start. The requirements are: (1) COM initialization
successful; (2) operating system MUST be WindowsNT 4.0 or later, with Service
Pack 5 or higher (although it may be running on a lower Service Pack version,
the code is intended to be run on NT4 SP5. The code responsible for OS version
checking looks like:
{
_osverInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if(GetVersionEx(&_osverInfo))
{
if(_osverInfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
{
LoadString(g_hInstance, IDS_ISNOTWINDOWSNT, lpszFmt, 1024);
_stprintf(lpszErr, lpszFmt,
_osverInfo.dwPlatformId == VER_PLATFORM_WIN32s
? _T("Windows 3.1") :
(_osverInfo.dwMinorVersion ? _T("Windows '98") : _T("Windows '95")));
MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONSTOP);
return -2;
}
else if(_osverInfo.dwMajorVersion < 4)
{
LoadString(g_hInstance, IDS_ERR_WINDOWSNT4REQUIRED, lpszErr, 1024);
MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONQUESTION);
return -21;
}
if(_tcsstr(_osverInfo.szCSDVersion, _T("Service Pack")))
{
TCHAR *szSPVersion, *szStopScan = _T(" \0");
nHeadLen = _tcslen(_T("Service Pack"));
szSPVersion = _osverInfo.szCSDVersion + (nHeadLen + 1) * sizeof(TCHAR);
lSPVersion = _tcstol(szSPVersion, &szStopScan, 10);
if(lSPVersion < 5)
{
LoadString(g_hInstance, IDS_ERR_SP5REQUIRED, lpszFmt, 256);
_stprintf(lpszErr, lpszFmt,
_osverInfo.dwMajorVersion, _osverInfo.dwMinorVersion,
lSPVersion);
if(MessageBox(0, lpszErr, g_lpcszApplicationName, MB_YESNO | MB_ICONQUESTION) == IDNO)
return -22;
}
}
else
{
LoadString(g_hInstance, IDS_ERR_SP5REQUIRED, lpszFmt, 256);
_stprintf(lpszErr, lpszFmt, _osverInfo.dwMajorVersion, _osverInfo.dwMinorVersion, lSPVersion);
if(MessageBox(0, lpszErr, g_lpcszApplicationName, MB_YESNO | MB_ICONQUESTION) == IDNO)
return -23;
}
}
else
{
LoadString(g_hInstance, IDS_ERR_OSVERSION, lpszErr, 1024);
MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONSTOP);
return -3;
}
}
2. The Common Controls library version which must be installed is 4.71. Checking is performed by the following code sequence:
{
_hCommCtl32Dll = LoadLibrary("comctl32.dll");
if(_hCommCtl32Dll)
{
pDllGetVersion =
(DLLGETVERSIONPROC)GetProcAddress(_hCommCtl32Dll, "DllGetVersion");
if(pDllGetVersion)
{
ZeroMemory(&dvi, sizeof(dvi));
dvi.cbSize = sizeof(dvi);
hr = (*pDllGetVersion)(&dvi);
if(SUCCEEDED(hr))
_dwVersion = MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion);
else
goto _error_;
}
else
goto _error_;
goto _ok_;
}
else
goto _error_;
_error_:
LoadString(g_hInstance, IDS_OLDCOMMCTL32, lpszErr, 1024);
MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONSTOP);
return -4;
_ok_:
if(_hCommCtl32Dll)
FreeLibrary(_hCommCtl32Dll);
if(_dwVersion < _dwRequired)
{
LoadString(g_hInstance, IDS_OLDCOMMCTL32, lpszErr, 1024);
MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONSTOP);
return -5;
}
}
The comctl32.dll library is initialized by another routine,
InitializeCommonControls
, which loads list view,
progress bar, bar controls (toolbar, status bar, trackbar & tooltip) and updown control.
3. The windows (classes and windows in general). The windows' classes are registered in this order: main window, MDI child window and others.
The main window class has nothing special; but the MDI child has some extra bytes reserved in cbWndExtra
member of
WNDCLASSEX
structure used. Because a child window has an encapsulated dialog inside, these extra bytes are defined by
the size of extra bytes and the appropriate offsets inside:
#define CBWNDEXTRA ((3 + MAX_COMPUTERNAME_LENGTH + 1) * sizeof(LONG))
#define GWLAPP_HDLG (0 * sizeof(LONG))
#define GWLAPP_TYPE (1 * sizeof(LONG))
#define GWLAPP_STATUS (2 * sizeof(LONG))
#define GWLAPP_MACHINE (3 * sizeof(LONG))
The other two window classes are used for two special windows. If one has a
role which is no more than its name shows, Legend, the other is one of
the functioning keys: the Controller window. This window receives any
control code from MDI children and forwards it to the encapsulated dialog. After
startup checks, Common Controls initialization and class registering, the CreateWindow
routine creates all windows (main window, toolbar, status bar, controller and
legend). Since the main window has a normal creation, status bar has only some
extra initializations (set parts, text and icons), and both controller and legend
are special MDI children, but normally created by WM_MDICREATE
message using a MDICREATESTRUCT
structure address passed as LPARAM.
The most interesting creation is performed in CreateToolbar
function.
The toolbar description is kept in the g_arToolbarButtons
array
of TBBUTTON
s. The Toolbar contains a combobox containing workstations,
created by CreateToolbarComboBox
function. This combo is initialized
by InitComputersCombo
, which does 3 things: (1) store the actual
window procedure in a global WNDPROC
variable (g_lpfnDefTlbCB_WndProc
),
set the combo window procedure with a new one (ToolbarCombo_WndProc
)
which does tooltip stuff and after that calls the original window procedure.
The save/replace of WNDPROC
s...
retcode
SubclassToolbarCombo()
{
g_lpfnDefTlbCB_WndProc = (WNDPROC)GetWindowLong(g_hComboToolbarWnd, GWL_WNDPROC);
SetWindowLong(g_hComboToolbarWnd, GWL_WNDPROC, (LONG)ToolbarCombo_WndProc);
return 0;
}
...toolbar's combo window procedure: showing tooltip...
LRESULT CALLBACK
ToolbarCombo_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
{
MSG msg;
HWND hWndTT;
msg.lParam = lParam;
msg.wParam = wParam;
msg.message = uMsg;
msg.hwnd = hWnd;
hWndTT = (HWND)SendMessage(g_hToolBarWnd, TB_GETTOOLTIPS, 0, 0);
SendMessage(hWndTT, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
break;
}
default:
break;
}
return (CallWindowProc(g_lpfnDefTlbCB_WndProc, hWnd, uMsg, wParam, lParam));
}
... plus adding tooltip and setting font:
retcode
AddToolbarComboTooltip()
{
static HWND hWndTT;
static CHAR szBuf[128];
TOOLINFO lpToolInfo;
hWndTT = (HWND)SendMessage(g_hToolBarWnd, TB_GETTOOLTIPS, 0, 0);
if(IsWindow(hWndTT))
{
lpToolInfo.cbSize = sizeof(lpToolInfo);
lpToolInfo.uFlags = TTF_IDISHWND | TTF_CENTERTIP;
lpToolInfo.lpszText = (LPSTR)IDM_COMPUTERCOMBO;
lpToolInfo.hwnd = g_hMainWnd;
lpToolInfo.uId = (UINT)g_hComboToolbarWnd;
lpToolInfo.hinst = g_hInstance;
SendMessage(hWndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&lpToolInfo);
}
return 0;
}
retcode
SetToolbarComboFont()
{
return SendMessage(g_hComboToolbarWnd, WM_SETFONT,
(WPARAM)(HFONT)SendMessage(g_hToolBarWnd, WM_GETFONT, 0, 0), MAKELPARAM(1, 0));
}
The enumeration of items which may fill the toolbar's combo, the workstations, is performed by the FillComputersCombo
function.
Nothing special, it is one of the hundreds of IShellFolder
enumeration samples you have seen already. Maybe the lpVtbl
could appear
weird for the newcomers; it's the interface's virtual table pointer. When using the "C" call, remember to pass also the "this" pointer, as in
_cleanup_:
if(lpe)
lpe->lpVtbl->Release(lpe);
4. The
actions and the
thread routines. You
can manage almost all via toolbar, menu or accelerators. Open shows a MDI child
with the dialog inside. The dialog's list controls will contain the appropriate
items, depending on the buttons 5-10: if 5-6 (

)
are mutually exclusive and referring type of items (services or device drivers),
7-10 (

) refer state of items (active, paused,
stopped or all). As you will notice, in
MDIChild_OnCreate
will be
allocated a
LPSERVICEFILTER
variable, containing information's about
enumeration (item type and state, two event handles - 'Cancel' and 'Close', necessary
window handles, thread ID and computer name):
typedef struct _tagSERVICEFILTER
{
BOOL fService;
BOOL fDevice;
BOOL fRunning;
BOOL fPaused;
BOOL fStopped;
HWND hwndLV;
HWND hwndDlg;
HWND hwndProgr;
HANDLE hCancelEvent;
HANDLE hCloseEvent;
unsigned uThreadId;
TCHAR lpszComputerName[_MAX_PATH + 1];
} SERVICEFILTER, *LPSERVICEFILTER;
The dialog is created using
CreateDialogParam
, passing the allocated
LPSERVICEFILTER
as
lParam
.
In dialog initialization (
WM_INITDIALOG
interception), will be called
MDIChildDlg_OnInitList
. Here is the place where we have necessary window
handles and we can launch the enumeration thread routine,
FillServicesList
. Since the resize, progress bar advance etc. are common stuff, the enumeration can be the only 'uncommon' thing:
schSCM = OpenSCManager(lpszUNCName, 0, SC_MANAGER_ALL_ACCESS);
if(schSCM)
{
dwResumeHandle = 0;
#pragma warning(disable:4127)
do
{
_retry_:
bEnumRetVal =
EnumServicesStatus
(
schSCM,
dwType,
dwServiceState,
lpSvc,
dwBufferSize,
&dwBytesNeeded,
&dwServicesReturned,
&dwResumeHandle
);
if(!bEnumRetVal)
{
g_dwLastError = GetLastError();
if(g_dwLastError == ERROR_MORE_DATA)
{
#pragma warning(disable:4127)
if(lpSvc != 0)
SafeDeletePointer(lpSvc, dwBytesNeeded);
SafeAllocatePointer(lpSvc, LPENUM_SERVICE_STATUS, GPTR, dwBytesNeeded);
if(lpSvc)
{
dwBufferSize = dwBytesNeeded;
lpSvc->lpDisplayName = lpszDisplayName;
lpSvc->lpServiceName = lpszServiceName;
goto _retry_;
}
else
goto _cleanup_;
}
else if(g_dwLastError == ERROR_NO_MORE_ITEMS)
break;
else
goto _cleanup_;
}
else
{
register DWORD x = 0;
for(x = 0; x < dwServicesReturned; x++)
{
TCHAR *lpszStatusString = 0, *lpszStartupString = 0;
short sStatusImage = IMAGE_UNKNOWN;
short sStartupImage = IMAGE_UNKNOWN;
DWORD dwStartType = 0;
{
schService = OpenService(schSCM, lpSvc[x].lpServiceName, SERVICE_ALL_ACCESS);
if(schService)
{
QUERY_SERVICE_CONFIG *pqsc;
DWORD _dwFalseSize = 0;
DWORD _dwBytesNeeded = 0;
char lpszBPN[_MAX_PATH + 1];
char lpszDep[_MAX_PATH + 1];
char lpszDN[_MAX_PATH + 1];
char lpszLOG[_MAX_PATH + 1];
char lpszSSN[_MAX_PATH + 1];
_dwFalseSize = sizeof(QUERY_SERVICE_CONFIG);
_dwFalseSize += 5 * (_MAX_PATH + 1);
SafeAllocatePointer(pqsc, LPQUERY_SERVICE_CONFIG, GPTR, _dwFalseSize);
pqsc->lpBinaryPathName = lpszBPN;
pqsc->lpDependencies = lpszDep;
pqsc->lpDisplayName = lpszDN;
pqsc->lpLoadOrderGroup = lpszLOG;
pqsc->lpServiceStartName = lpszSSN;
QueryServiceConfig(schService, pqsc, _dwFalseSize, &_dwBytesNeeded);
dwStartType = pqsc->dwStartType;
#pragma warning(disable:4127)
SafeDeletePointer(pqsc, _dwFalseSize);
CloseServiceHandle(schService);
schService = 0;
}
}
if(GetStatusString(lpSvc[x].ServiceStatus.dwCurrentState, (TCHAR **)&lpszStatusString) &&
GetAssociatedStatusImage(lpSvc[x].ServiceStatus.dwCurrentState, &sStatusImage) &&
GetAssociatedStartupImage(dwStartType, &sStartupImage) &&
GetStartupString(dwStartType, (TCHAR **)&lpszStartupString))
{
if(psf->fPaused && !(lpSvc[x].ServiceStatus.dwCurrentState == SERVICE_PAUSED))
continue;
if(psf->fRunning && !(lpSvc[x].ServiceStatus.dwCurrentState == SERVICE_RUNNING))
continue;
{
DWORD dwId = 0;
IsWindow(hwndProgr) && SendMessage(hwndProgr, PBM_STEPIT, 0, 0);
InsertRowInList(hwndLV, 9, &dwId,
sStatusImage, lpSvc[x].lpServiceName,
-1, lpSvc[x].lpDisplayName,
sStatusImage, lpszStatusString,
sStartupImage, lpszStartupString);
#pragma warning(disable:4127)
SafeDeletePointer(lpszStatusString, _tcslen(lpszStatusString));
SafeDeletePointer(lpszStartupString, _tcslen(lpszStartupString));
}
}
}
}
if(dwResumeHandle == 0)
break;
} while(TRUE);
_cleanup_:
#pragma warning(disable:4127)
if(lpSvc != 0)
SafeDeletePointer(lpSvc, dwBytesNeeded);
CloseServiceHandle(schSCM);
}
The 'Cancel' and 'Close' events are not intercepted, but in the
Event Log sample you will see how to '
WaitForSingleObject' them.
Here the enumeration cannot produce so many items (as Event log does).
The following five buttons, 11-15 (

), are intended to control these items. If 1st, 'Refresh', launches the same thread as Open,
the rest (start, stop, pause and continue) call the same handler,
OnControl
, with the appropriate control code.
A special mention for start: because control code for stop is 1, the code 0 (SERVICE_CONTROL_STOP - 1) is used to signal a start request. Although I doubt it, this may be wrong
in a future version of header file <winsvc.h>.
Using the same technique, a
LPCONTROLSERVICE
is allocated as passed as
LPARAM to controller window, via
user-defined message
WM_USER_CTLSERVICE. The controller window procedure send another user-defined message to encapsulated dialog,
WM_USER_REQUESTCTLSERVICE,
to show in controller dialog what request was captured. After this, the
OnControlService
thread routine is called.
typedef struct _tagCONTROLSERVICE
{
DWORD dwControlCode;
BOOL fService;
DWORD dwErrorCode;
HWND hwndLV;
UINT uListIdx;
SERVICE_STATUS ServiceStatus;
TCHAR lpszMachineName[MAX_COMPUTERNAME_LENGTH + 1];
TCHAR lpszServiceName[_MAX_PATH + 1];
} CONTROLSERVICE, *LPCONTROLSERVICE;
unsigned int __stdcall
OnControlService(LPVOID lpParam)
{
unsigned int uRetVal = 0;
TCHAR lpszMachineName[_MAX_PATH + 1], lpszServiceName[_MAX_PATH + 1];
DWORD dwCtlCode;
BOOL fService, bSuccess = FALSE;
SC_HANDLE schSCM = 0, schService = 0;
SERVICE_STATUS _ServiceStatus;
LPCONTROLSERVICE pCtlService;
pCtlService = (LPCONTROLSERVICE)lpParam;
_tcscpy(lpszMachineName, pCtlService->lpszMachineName);
_tcscpy(lpszServiceName, pCtlService->lpszServiceName);
dwCtlCode = pCtlService->dwControlCode;
fService = pCtlService->fService;
schSCM = OpenSCManager((LPCTSTR)lpszMachineName, NULL, GENERIC_READ | GENERIC_EXECUTE);
if(schSCM)
{
schService = OpenService(schSCM, (LPCTSTR)lpszServiceName, GENERIC_READ | GENERIC_EXECUTE);
if(schService)
{
if(dwCtlCode == SERVICE_CONTROL_STOP - 1)
{
bSuccess = StartService(schService, 0, NULL);
pCtlService->ServiceStatus.dwCurrentState = SERVICE_RUNNING;
}
else
{
bSuccess = ControlService(schService, dwCtlCode, &_ServiceStatus);
pCtlService->ServiceStatus = _ServiceStatus;
}
CloseServiceHandle(schService);
}
CloseServiceHandle(schSCM);
}
if(!bSuccess)
pCtlService->dwErrorCode = g_dwLastError = GetLastError();
else
pCtlService->dwErrorCode = 0;
SendMessage(g_hControllerWnd, WM_USER_NOTIFYCTLSTATUS, (WPARAM)0, (LPARAM)pCtlService);
#pragma warning(disable:4127)
SafeDeletePointer(pCtlService, sizeof(CONTROLSERVICE));
return uRetVal;
}
Before returning, another user-defined message,
WM_USER_NOTIFYCTLSTATUS,
ensures reporting back to controller window using the same parameter. As you probably
noticed already, the caller routine allocates and fills (alone or using other
routines) the passed
LPVOID parameter; the called thread routine
is responsable for freeing memory. Two macros, which appear to behave normally
until this moment, helps me in memory management:
#define SafeAllocatePointer(pointer,type,flags,size) (pointer)=(type)(GlobalAlloc((flags),(size)))
#define SafeDeletePointer(block,size) \
{\
if((size)>=0)\
{\
if((block) && !(IsBadReadPtr((block),(size)))) \
{\
GlobalFree((block));\
(block)=0;\
}\
}\
}
5. The managers are governed by the buttons 16-18 (
). New service, modify and delete: these are the 3 actions
possible. Although New and Open are valid only for local machine, mapping network drive is intended to support also service management on remote machine, after
the appropriate path translation. All the work on all the controls (for new and modify) is based on a good reading of MSDN. The dialog calls only one function,
depending if is a new service or an existing one: CreateService
or ChangeServiceConfig
;
for service deletion, the API routine DeleteService
ensures the job.

6. The helpers are managed by the buttons 19-24 (
). The first two buttons have simple jobs to do; the first launches Control panel,
and the second the Control Panel' Services applet. Maybe this could be interesting? And something else: don't ask me how I get the dialog ID CPLAPPLET_SRVMGR_SERVICESDLGID
(1). This could have another ID in Windows2000, for example.
LRESULT*
OnCPLServices(HWND hWnd, LRESULT *plResult)
{
TCHAR lpszCPLApplet_SrvMgr[_MAX_PATH + 1];
_UNREFERENCED_PARAMETER_(hWnd);
*plResult = 1;
if(LoadString(g_hInstance, IDS_CPLAPPLET_SRVMGR, lpszCPLApplet_SrvMgr, _MAX_PATH + 1))
{
HMODULE hCPLApplet_SrvMgr = 0;
hCPLApplet_SrvMgr = LoadLibrary(lpszCPLApplet_SrvMgr);
if(hCPLApplet_SrvMgr)
{
g_lpfnCPlApplet = (APPLET_PROC)GetProcAddress(hCPLApplet_SrvMgr, _T("CPlApplet"));
if(g_lpfnCPlApplet)
{
if((*g_lpfnCPlApplet)(0, CPL_INQUIRE, CPLAPPLET_SRVMGR_SERVICESDLGID, (LPARAM)&g_cpli) == 0)
{
if((*g_lpfnCPlApplet)(0, CPL_DBLCLK, CPLAPPLET_SRVMGR_SERVICESDLGID, 0) == 0)
{
*plResult = 0;
g_lpfnCPlApplet = 0;
}
}
}
FreeLibrary(hCPLApplet_SrvMgr);
}
}
if(*plResult != 0)
ReportLastError(0, 0, TRUE, 0);
*plResult = 0;
return plResult;
}
The next two have the role of network disk mapping. In fact, they are simple calls to
OnNetworkDriveOp handler,
which is using the
mpr.dll library and look for
WNetConnectionDialog
or
WNetDisconnectDialog
. These buttons will be used, as I already said, at remote
service installation.
And finally, of course, the delight of remote shutdown. When I posted the previous
dialog-based MFC version, suddenly many people became VERY interested...Now in
a API version, calling the same
InitiateSystemShutdown library
function. The "countdown" dialog box is waiting you to hit 'Cancel' using the
ShutdownWorkstation
thread routine. If you don't want this, that's
it. Wait for the next logon. The structure pointer used this time to pass in
LPVOID's
thread routine is
SHUTDOWNSTRUCT. Again, the caller allocates
memory and the called frees it.
typedef struct _tagSHUTDOWNSTRUCT
{
BOOL fLocalComputer;
BOOL fForceApplicationClose;
BOOL fReboot;
UINT nTimeout;
HANDLE hCancelEvent;
unsigned int uThreadId;
HWND hwndImage;
HWND hwndStatus;
HWND hwndProgress;
TCHAR lpszMachineName[MAX_COMPUTERNAME_LENGTH + 1];
TCHAR lpszMessage[1024];
} SHUTDOWNSTRUCT, *LPSHUTDOWNSTRUCT;
unsigned int __stdcall
ShutdownWorkstation(LPVOID lpParam)
{
LPSHUTDOWNSTRUCT lpSS = 0;
TCHAR lpszFmt[256], lpszText[1024];
unsigned int uRetVal= 0;
BOOL bFlipImage = TRUE;
UINT i = 0;
DWORD dw = WAIT_OBJECT_0;
HWND hwndImage = 0;
HWND hwndStatus = 0;
HWND hwndProgress = 0;
DWORD dwTickStart = 0;
DWORD dwTickEnd = 0;
lpSS = (LPSHUTDOWNSTRUCT)lpParam;
hwndImage = lpSS->hwndImage;
hwndStatus = lpSS->hwndStatus;
hwndProgress = lpSS->hwndProgress;
dwTickStart = GetTickCount();
LoadString(g_hInstance, IDS_SHUTDOWNPROGRESS, lpszFmt, 256);
_stprintf(lpszText, lpszFmt, lpSS->lpszMachineName, lpSS->nTimeout - i);
SetWindowText(hwndStatus, lpszText);
SendMessage(hwndProgress, PBM_SETRANGE, 0, MAKELPARAM(0, (short)(lpSS->nTimeout)));
SendMessage(hwndProgress, PBM_SETPOS, 0, 0);
while(TRUE)
{
dw = WaitForSingleObject(lpSS->hCancelEvent, 0);
if((lpSS->nTimeout <= i) || (dw == WAIT_OBJECT_0))
break;
dwTickEnd = GetTickCount();
if(dwTickEnd - dwTickStart >= 1000)
{
i++;
SendMessage(hwndProgress, PBM_SETPOS, (WPARAM)i, 0);
_stprintf(lpszText, lpszFmt, lpSS->lpszMachineName, lpSS->nTimeout - i);
SetWindowText(hwndStatus, lpszText);
dwTickStart = dwTickEnd;
bFlipImage = !bFlipImage;
if(bFlipImage)
ShowWindow(hwndImage, SW_SHOW);
else
ShowWindow(hwndImage, SW_HIDE);
}
}
if(dw == WAIT_TIMEOUT)
{
PostMessage(GetParent(hwndStatus), WM_USER_DLGDESTROY, 0, 0);
if(lpSS->fLocalComputer)
{
UINT uFlags =
(lpSS->fReboot ? EWX_REBOOT : 0) |
(lpSS->fForceApplicationClose ? EWX_FORCE : 0);
ExitWindowsEx(uFlags, 0L);
}
}
else
{
PostMessage(GetParent(hwndStatus), WM_USER_DLGDESTROY, 1, 0);
AbortSystemShutdown((LPSTR)lpSS->lpszMachineName);
}
CloseHandle(lpSS->hCancelEvent);
#pragma warning(disable:4127)
SafeDeletePointer(lpSS, sizeof(SHUTDOWNSTRUCT));
lpSS = 0;
return uRetVal;
}

7. The controls. Maybe list control implementation file, ListViewX.c, contains some useful things:
- a simple column count:
short
ListView_GetColumnCount(HWND hwndLV)
{
short sCol = 0;
LVCOLUMN lvCol;
lvCol.mask = LVCF_WIDTH;
while(ListView_GetColumn(hwndLV, sCol++, &lvCol))
;
return --sCol;
}
- sorting list, using another custom structure (
DLGSORTDATA
) ...
typedef struct _tagDLGSORTDATA
{
HWND hDlg;
int nSortOrder;
int nSortType;
int nColIdx;
} DLGSORTDATA, *LPDLGSORTDATA;
and the description array of list columns,
g_pcListColumns
of type
typedef struct _tagLISTVIEWCOLUMN
{
UINT uCXCol;
int nSortType;
int nSortOrder;
TCHAR lpszName[32];
} LISTVIEWCOLUMN, *LPLISTVIEWCOLUMN;
to sort the list:
int
ListView_OnSort(HWND hDlg, HWND hwndLV, int iColumn, PFNLVCOMPARE lpfnCompare)
{
int nColCount;
nColCount = ListView_GetColumnCount(hwndLV);
if(iColumn < nColCount)
{
DLGSORTDATA ds;
if(g_pcListColumns[iColumn].nSortOrder == ASCENDING)
g_pcListColumns[iColumn].nSortOrder = DESCENDING;
else if(g_pcListColumns[iColumn].nSortOrder == DESCENDING)
g_pcListColumns[iColumn].nSortOrder = NONE;
else
g_pcListColumns[iColumn].nSortOrder = ASCENDING;
ds.hDlg = hDlg;
ds.nSortOrder = g_pcListColumns[iColumn].nSortOrder;
ds.nSortType = g_pcListColumns[iColumn].nSortType;
ds.nColIdx = iColumn;
return (int)ListView_SortItems(hwndLV, lpfnCompare, (LPARAM)(&ds));
}
else
return 0;
}
Also, the variable-arguments routine
InsertRowInList
helps a lot on the insert operation in list. Although custom, this
routine could be easily adapted to serve your own purpose.
BOOL
InsertRowInList(HWND hwndLV, int nInsertCount, ...)
{
int nCount, nItem;
va_list marker;
DWORD dwId;
register int i;
LVITEM lvItem;
if(!IsWindow(hwndLV))
return FALSE;
nCount = ListView_GetItemCount(hwndLV);
lvItem.mask = LVIF_TEXT | LVIF_PARAM;
lvItem.pszText = " ";
lvItem.cchTextMax = strlen(" ");
lvItem.iItem = nCount;
lvItem.iImage = 0;
lvItem.iSubItem = 0;
lvItem.state = 0;
lvItem.stateMask = 0;
lvItem.iIndent = 0;
lvItem.lParam = nCount;
nItem = ListView_InsertItem(hwndLV, &lvItem);
va_start(marker, nInsertCount);
dwId = *((DWORD *)(va_arg(marker, DWORD*)));
lvItem.mask = LVIF_PARAM;
lvItem.iItem = nCount;
lvItem.iSubItem = 0;
lvItem.lParam = (LPARAM)dwId;
ListView_SetItem(hwndLV, &lvItem);
for(i = 0; i < nInsertCount - 1; i += 2)
{
char *szItemText;
short sImage;
sImage = va_arg(marker, short);
szItemText = va_arg(marker, char*);
lvItem.mask = LVIF_TEXT | (sImage == -1 ? 0 : LVIF_IMAGE);
lvItem.iItem = nCount;
lvItem.iSubItem = i / 2;
lvItem.iImage = sImage;
lvItem.pszText = szItemText;
lvItem.cchTextMax = strlen(szItemText);
ListView_SetItem(hwndLV, &lvItem);
}
va_end(marker);
return TRUE;
}
And finally, a routine to ensure full visibility of a list column (both header and text inside). It works, for the moment, only if the list has report style. But
I am waiting for serious improvement.
BOOL
ListView_EnsureColumnVisible(HWND hwndLV, short sColIndex)
{
int iCount, nLVCount = ListView_GetItemCount(hwndLV), nMaxWidth = 0, nMaxHeaderWidth = 0;
HDC hLVDC;
if(!IsWindow(hwndLV))
return FALSE;
{
TCHAR lpszLVClassName[_MAX_PATH + 1];
if(!GetClassName(hwndLV, lpszLVClassName, _MAX_PATH + 1) || (_tcsicmp(lpszLVClassName, WC_LISTVIEW) != 0))
return FALSE;
}
if(!(GetWindowLong(hwndLV, GWL_STYLE) & LVS_REPORT))
return FALSE;
if(sColIndex > ListView_GetColumnCount(hwndLV))
return FALSE;
nMaxWidth = ListView_GetColumnWidth(hwndLV, sColIndex);
hLVDC = GetDC(hwndLV);
if(hLVDC)
{
int nIconSize = 0;
{
HIMAGELIST himgList = ListView_GetImageList(hwndLV, LVSIL_SMALL);
if(himgList)
{
int cx, cy;
if(ImageList_GetIconSize(himgList, &cx, &cy))
{
nIconSize = cx;
}
}
}
for(iCount = 0; iCount < nLVCount; iCount++)
{
LVITEM lvItem;
TCHAR lpszBuffer[_MAX_PATH + 1];
SIZE sizeLVText;
lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
lvItem.pszText = lpszBuffer;
lvItem.cchTextMax = _MAX_PATH + 1;
lvItem.iItem = iCount;
lvItem.iSubItem = sColIndex;
lvItem.iImage = -1;
if(ListView_GetItem(hwndLV, &lvItem))
{
if(GetTextExtentPoint32(hLVDC, lpszBuffer, _tcslen(lpszBuffer), &sizeLVText))
{
if(lvItem.iImage >= 0)
{
if(nMaxWidth < sizeLVText.cx + nIconSize)
nMaxWidth = sizeLVText.cx + nIconSize;
}
else
{
if(nMaxWidth < sizeLVText.cx)
nMaxWidth = sizeLVText.cx;
}
}
}
}
}
ReleaseDC(hwndLV, hLVDC);
{
HWND hwndHD;
hwndHD = ListView_GetHeader(hwndLV);
if(hwndHD)
{
HDITEM hdItem;
TCHAR lpszHeaderText[_MAX_PATH + 1];
hdItem.mask = HDI_BITMAP | HDI_TEXT;
hdItem.pszText = lpszHeaderText;
hdItem.cchTextMax = _MAX_PATH + 1;
hdItem.hbm = 0;
if(Header_GetItem(hwndHD, sColIndex, &hdItem))
{
SIZE sizeHDText;
HDC hHDDC;
hHDDC = GetDC(hwndHD);
if(GetTextExtentPoint32(hHDDC, lpszHeaderText, _tcslen(lpszHeaderText), &sizeHDText))
{
nMaxHeaderWidth = sizeHDText.cx;
if(hdItem.hbm)
{
SIZE sizeBmp;
if(GetBitmapDimensionEx(hdItem.hbm, &sizeBmp))
nMaxHeaderWidth += sizeBmp.cx;
}
}
ReleaseDC(hwndHD, hHDDC);
}
}
}
if(nMaxWidth < nMaxHeaderWidth)
nMaxWidth = nMaxHeaderWidth;
return ListView_SetColumnWidth(hwndLV, sColIndex, nMaxWidth);
}
Also the 'About' Box contains a primitive 'Hyperlink' behaviour, but in fact is some GDI work and an ordinary static control changing the bitmap inside. The scrolling
is ensured by the
ScrollWindow routine. The 'singing' part, present also in the shutdown options dialog, has some MCI dealings. Common, but let's see it:
#pragma warning(disable:4702)
BOOL*
AboutBox_MCIPlaySound(HWND hWnd, WPARAM wParam, LPARAM lParam, BOOL *pbResult)
{
TCHAR lpszVFWLibrary[_MAX_PATH + 1];
TCHAR lpszMCIWndRegisterClass[_MAX_PATH + 1];
TCHAR lpszWAVFile[_MAX_PATH + 1];
_UNREFERENCED_PARAMETER_(hWnd);
_UNREFERENCED_PARAMETER_(wParam);
_UNREFERENCED_PARAMETER_(lParam);
if(LoadString(g_hInstance, IDS_VFW32DLL, lpszVFWLibrary, _MAX_PATH + 1) &&
LoadString(g_hInstance, IDS_MCIWNDREGISTERCLASS, lpszMCIWndRegisterClass, _MAX_PATH + 1) &&
LoadString(g_hInstance, IDS_ABOUTBOXWAVFILE, lpszWAVFile, _MAX_PATH + 1))
{
g_hVFW32 = LoadLibrary(lpszVFWLibrary);
if(g_hVFW32)
{
lpfnMCIWndRegisterClass = (LPFNMCIWNDREGISTERCLASS)GetProcAddress(g_hVFW32,
lpszMCIWndRegisterClass);
if(lpfnMCIWndRegisterClass)
{
MDICREATESTRUCT mdics;
mdics.szClass = MCIWND_WINDOW_CLASS;
mdics.szTitle = _T("__ZS_InvisibleMCIWindow__");
mdics.hOwner = g_hInstance;
mdics.x = CW_USEDEFAULT;
mdics.y = 0;
mdics.cx = CW_USEDEFAULT;
mdics.cy = 0;
mdics.style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | MCIWNDF_SHOWALL;
mdics.lParam = 0;
if((*lpfnMCIWndRegisterClass)())
{
g_hwndMdi = CreateWindow
(
MCIWND_WINDOW_CLASS,
_T("InvisibleMCIWindow"),
WS_CLIPCHILDREN | WS_CLIPSIBLINGS | MCIWNDF_SHOWALL,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
0,
g_hInstance,
(LPVOID)(LPSTR)(LPCTSTR)lpszWAVFile
);
if(g_hwndMdi)
{
ShowWindow(g_hwndMdi, SW_HIDE);
UpdateWindow(g_hwndMdi);
SendMessage(g_hwndMdi, MCI_PLAY, 0, 0);
}
}
}
}
}
*pbResult = TRUE;
return pbResult;
}
Pay attention when you launch the shutdown dialog. I 'borrowed' the 'nuke.wav' file from BattleNet' Starcraft Compendium site - you now, the worry sound which accompaniates 'Nuclear Launch Detected'
(I hope this is not against the law). First time I was about to loose my auditive system.