Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

WindowsNT System Manager

, 30 Nov 1999
Rate this:
Please Sign up or sign in to vote.
This article presents a comprehensive system control manager for NT
  • Download demo executable - 38 Kb
  • Download project files - 92 Kb
  • <!-- Article Starts -->

    Controller, legend and a MDI services' child: in action

    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:

        /*    Checks operating system version. */
        {
            _osverInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
            if(GetVersionEx(&_osverInfo))
            {
                if(_osverInfo.dwPlatformId != VER_PLATFORM_WIN32_NT) /* Is WindowsNT? */
                {
                    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) /* Is WindowsNT 4.0 or more? */
                {
                    LoadString(g_hInstance, IDS_ERR_WINDOWSNT4REQUIRED, lpszErr, 1024);
                    MessageBox(0, lpszErr, g_lpcszApplicationName, MB_OK | MB_ICONQUESTION);
                    return -21;
                }
    
                /* The service pack. Required version is 5, but user is asked if he/she wants to continue, 
                 * although it may have a lower SP version. */
                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:

        /*    Checks for Common Controls version. */
        {
            _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))    /*    encapsulated dialog window handle                */
        #define GWLAPP_TYPE    (1 * sizeof(LONG))    /*    type of items enumerated (services or device drivers) */
        #define GWLAPP_STATUS    (2 * sizeof(LONG))  /*    status of enumerated items (active, paused, stopped)        */
        #define GWLAPP_MACHINE    (3 * sizeof(LONG)) /*    machine name - has (MAX_COMPUTERNAME_LENGTH + 1) length    */
    

    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 TBBUTTONs. 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 WNDPROCs...

    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);
        //    more release to come
    

    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):
    /*    service filter - used to display data verifying certain conditions */
    typedef struct _tagSERVICEFILTER
    {
        BOOL            fService;               /*    service            */
        BOOL            fDevice;                /*    device driver         */
        BOOL            fRunning;               /*    running service/device     */
        BOOL            fPaused;                /*    paused service/device     */
        BOOL            fStopped;               /*    stopped service/device     */
        HWND            hwndLV;                 /*    list view handle         */
        HWND            hwndDlg;                /*    encapsulated dialog handle     */
        HWND            hwndProgr;              /*    progress bar handle     */
        HANDLE          hCancelEvent;           /*    "cancel" event handle     */
        HANDLE          hCloseEvent;            /*    "close window" event handle    */
        unsigned        uThreadId;              /*    associated thread ID     */
        TCHAR           lpszComputerName[_MAX_PATH + 1];    /*    computer name         */
    } 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
                {
                    // insert all entries in list
                    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;
                                //    advance progress bar
                                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) // enumeration ends, succeeded
                    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;                /*    control code                 */
        BOOL        fService;                    /*    TRUE if service, FALSE if device driver     */
        DWORD        dwErrorCode;                /*    DWORD error code                 */
        HWND        hwndLV;                    /*    the list view window which launch the request */
        UINT        uListIdx;                    /*    index of item in list view             */
        SERVICE_STATUS    ServiceStatus;                /*    item status, if success change item icon     */
        TCHAR        lpszMachineName[MAX_COMPUTERNAME_LENGTH + 1];    /*    machine name                 */
        TCHAR        lpszServiceName[_MAX_PATH + 1];        /*    service name (real, not diplay name)     */
    } 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; 
    
        //    passed parameter
        pCtlService = (LPCONTROLSERVICE)lpParam;
    
        //    obtain description of requested operation
        _tcscpy(lpszMachineName, pCtlService->lpszMachineName);
        _tcscpy(lpszServiceName, pCtlService->lpszServiceName);
        dwCtlCode = pCtlService->dwControlCode;
        fService  = pCtlService->fService;
    
        //    the job
        //    open SCM
        schSCM = OpenSCManager((LPCTSTR)lpszMachineName, NULL, GENERIC_READ | GENERIC_EXECUTE);
        if(schSCM)
        {
            //    open service
            schService = OpenService(schSCM, (LPCTSTR)lpszServiceName, GENERIC_READ | GENERIC_EXECUTE);
            if(schService)
            {
                //    control item
                if(dwCtlCode == SERVICE_CONTROL_STOP - 1) // meaning start
                {
                    bSuccess = StartService(schService, 0, NULL);
                    pCtlService->ServiceStatus.dwCurrentState = SERVICE_RUNNING; // we have to enforce this to signal 
                }
                else
                {
                    bSuccess = ControlService(schService, dwCtlCode, &_ServiceStatus);
                    pCtlService->ServiceStatus = _ServiceStatus;
                }
    
                //    close service handle
                CloseServiceHandle(schService);
            }
            //    close SCM handle
            CloseServiceHandle(schSCM);
        }
    
        if(!bSuccess)
            pCtlService->dwErrorCode = g_dwLastError = GetLastError();    
        else
            pCtlService->dwErrorCode = 0;
    
        //    notify controller window by operation status
        SendMessage(g_hControllerWnd, WM_USER_NOTIFYCTLSTATUS, (WPARAM)0, (LPARAM)pCtlService);
    
        //    destroy allocated memory - we don't need this pointer anymore
        #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; // assume fail
    
        /* Loads services' applet name. */
        if(LoadString(g_hInstance, IDS_CPLAPPLET_SRVMGR, lpszCPLApplet_SrvMgr, _MAX_PATH + 1))
        {
            HMODULE    hCPLApplet_SrvMgr = 0;
    
            /* Loads srvmgr.cpl. */
            hCPLApplet_SrvMgr = LoadLibrary(lpszCPLApplet_SrvMgr);
            if(hCPLApplet_SrvMgr)
            {
                /* Get CPLApplet exported routine, and then call a dialog using CPL_INQUIRE message. */
                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) // 0 for OK
                        {
                            *plResult = 0;            // terminate with success
    
                            g_lpfnCPlApplet = 0;    
                            // Signal for OK closing application. OnClose will close applet instead of program.
                        }
                    }
                }
    
                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;                /*    local computer                 */
        BOOL        fForceApplicationClose;            /*    force application's closing         */
        BOOL        fReboot;                    /*    reboot after shutdown             */
        UINT        nTimeout;                    /*    timeout in seconds             */
        HANDLE        hCancelEvent;                /*    'Cancel' event (abort shutdown if set)     */
        unsigned int    uThreadId;                /*    'Wait for Cancel' thread id         */
        HWND          hwndImage;                    /*    flip image                 */
        HWND          hwndStatus;                    /*    status bar                 */
        HWND          hwndProgress;                /*    progress bar (left seconds)         */
        TCHAR          lpszMachineName[MAX_COMPUTERNAME_LENGTH + 1];    /*    machine name                 */
        TCHAR          lpszMessage[1024];                /*    message prompted to user             */
    } 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) |                    //    reboot after shutdown
                    (lpSS->fForceApplicationClose ? EWX_FORCE : 0);        //    force closing for application(s)
    
                ExitWindowsEx(uFlags, 0L);
            }
        }
        else
        {
            PostMessage(GetParent(hwndStatus), WM_USER_DLGDESTROY, 1, 0);
            AbortSystemShutdown((LPSTR)lpSS->lpszMachineName);
        }
    
        //    deallocate event
        CloseHandle(lpSS->hCancelEvent);
    
        //    destroy allocated memory - we don't need this pointer anymore
        #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) ...
    /* sort data - structure used to pass when a list's header is clicked */
    typedef struct _tagDLGSORTDATA
    {
        HWND    hDlg;                                    /*    dialog handle */
        int        nSortOrder;                                /*    sorting order */
        int        nSortType;                                /*    sorting type */
        int        nColIdx;                                /*    column index */
    } DLGSORTDATA, *LPDLGSORTDATA;
    
    and the description array of list columns, g_pcListColumns of type
    /*    list view column */
    typedef struct _tagLISTVIEWCOLUMN
    {
        UINT     uCXCol;        /*    index                         */
        int     nSortType;    /*    sorting type (STRING = 0, NUMERIC, DATE, DATETIME)     */
        int     nSortOrder;    /*    sorting order (ASCENDING = -1, NONE, DESCENDING)     */
        TCHAR  lpszName[32];    /*    column name                     */
    } 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;
    
        //    ensure some conditions
        //    is a window?
        if(!IsWindow(hwndLV))
            return FALSE;
        //    is a listview?
        {
            TCHAR lpszLVClassName[_MAX_PATH + 1];
            if(!GetClassName(hwndLV, lpszLVClassName, _MAX_PATH + 1) || (_tcsicmp(lpszLVClassName, WC_LISTVIEW) != 0))
                return FALSE;
        }
        //    is in report style?
        if(!(GetWindowLong(hwndLV, GWL_STYLE) & LVS_REPORT))
            return FALSE;
        //    column out of range
        if(sColIndex > ListView_GetColumnCount(hwndLV))    //    it is 0-based
            return FALSE;
    
        nMaxWidth = ListView_GetColumnWidth(hwndLV, sColIndex);
    
        hLVDC = GetDC(hwndLV);
        if(hLVDC)
        {
            //    get image width, if column has subitem images
            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;
    
                //    ensure also extra space if column have and image inside
                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) // also has image
                        {
                            if(nMaxWidth < sizeLVText.cx + nIconSize)
                                nMaxWidth = sizeLVText.cx + nIconSize;
                        }
                        else
                        {
                            if(nMaxWidth < sizeLVText.cx)
                                nMaxWidth = sizeLVText.cx;
                        }
                    }
                }
            }
        }
        ReleaseDC(hwndLV, hLVDC);
    
        //    last test: need header greater size?
        {
            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))
                    {
                        //    assume text only
                        nMaxHeaderWidth = sizeHDText.cx;
    
                        if(hdItem.hbm) // has bitmap
                        {
                            SIZE sizeBmp;
                            if(GetBitmapDimensionEx(hdItem.hbm, &sizeBmp))
                                nMaxHeaderWidth += sizeBmp.cx;
                        }
                    }
                    ReleaseDC(hwndHD, hHDDC);
                }
            }
        }
    
        if(nMaxWidth < nMaxHeaderWidth)
            nMaxWidth = nMaxHeaderWidth;
    
        //    now, since we established the maximum string size inside column, ensure this new column size
        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);
    
        //    Load strings for library name, routine and sound file. If OK, try to "sing" utopia.wav.
        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) // we have audio support
            {
                //    Find MCIWndRegisterClass exported routine.
                lpfnMCIWndRegisterClass = (LPFNMCIWNDREGISTERCLASS)GetProcAddress(g_hVFW32,
                                                                  lpszMCIWndRegisterClass);
            
                if(lpfnMCIWndRegisterClass)    //    ok, create MCI window, if possible
                {
                    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)())    //    registering: success
                    {
                        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)    //    MCI window created: hide and play
                        {
                            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.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here

    Share

    About the Author

    Sardaukar
    Web Developer
    Romania Romania
    No Biography provided

    Comments and Discussions

     
    GeneralLow Rating Low Comments Pinmembereaster_200720-Feb-06 21:12 
    QuestionHow to get local users and groups? PinmemberJian123456713-Sep-04 9:57 
    AnswerNetLocal... and NetUser... Pinmemberpengok23-Jan-05 22:07 
    GeneralRe: NetLocal... and NetUser... PinmemberJian123456724-Jan-05 6:16 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Mobile
    Web04 | 2.8.140921.1 | Last Updated 1 Dec 1999
    Article Copyright 1999 by Sardaukar
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid