// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved // // This code is not part of SDK sample itself. // // The code contained in CmdLineBase.h/.cpp provides the boiler-plate code to create a simple // command-line applications with built-in support for option processing, automatic generation // of usage text, and grouping of related commands. #pragma once #include #include #include #include // Base class for a single command class CCmdBase { public: CCmdBase(PCWSTR pszName, PCWSTR pszDescription, PCWSTR pszArgTemplate); virtual ~CCmdBase() {} // methods for executing the command, and obtaining usage information HRESULT Execute( PCWSTR* ppszArgs, int cArgs); void PrintUsage() const; PCWSTR GetName(bool fIncludePrefix = false) const; PCWSTR GetDescription() const; PCWSTR GetArgTemplate() const; void AddPrefix(PCWSTR pszPrefix); // toggles debug output for all classes derived from CCmdBase static void SetDebugMode(BOOL fDebug); protected: // methods to be overridden by the command implementation virtual HRESULT v_ProcessArguments( PCWSTR*, int) { return S_OK; } virtual HRESULT v_ExecuteCommand() = 0; virtual void v_PrintInstructions() const {} // methods for generating output (use instead of printf) void Output(PCWSTR pszFormat, ...) const; void ParseError(PCWSTR pszFormat, ...); void RuntimeError(PCWSTR pszFormat, ...); // methods to add specify option handlers; call these from the subclass constructor template void AddStringOptionHandler(PCWSTR pszOption, PCWSTR pszUsage, HRESULT (CCommandImpl::*pmfnSetOption)(PCWSTR)) { COptionHandler *pHandler = new (std::nothrow) CStringOptionHandler(pmfnSetOption, pszOption, pszUsage); if (pHandler) { _mapOptionHandlers.Add(pszOption, &pHandler); delete pHandler; } } template struct ARGENTRY; // forward decl template void AddEnumOptionHandler(PCWSTR pszOption, PCWSTR pszDescription, PCWSTR pszUsage, HRESULT (CCommandImpl::*pmfnSetOption)(ARGVALUE), const ARGENTRY *pLookupTable, const UINT cLookupTable) { COptionHandler *pHandler = new (std::nothrow) CEnumeratedOptionHandler(pmfnSetOption, pszOption, pszDescription, pszUsage, pLookupTable, cLookupTable); if (pHandler) { _mapOptionHandlers.Add(pszOption, &pHandler); delete pHandler; } } // Simple associative array implementation template class CStringMap { public: CStringMap() : _pHead(NULL), _ppInsertAt(&_pHead) {} ~CStringMap() { delete _pHead; } __inline bool IsEmpty() const { return _pHead == NULL; } HRESULT Add(PCWSTR pszKey, VALUE **ppValue) { MAPPING *pNew = new (std::nothrow) MAPPING; HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { hr = SHStrDup(pszKey, &pNew->pszKey); if (SUCCEEDED(hr)) { pNew->pValue = *ppValue; *ppValue = NULL; // transfer ownership *_ppInsertAt = pNew; _ppInsertAt = &pNew->pNext; pNew = NULL; // transfer ownership } delete pNew; } return hr; } HRESULT Find(PCWSTR pszKey, VALUE **ppValue) const { *ppValue = NULL; HRESULT hr = E_FAIL; for (MAPPING *pCur = _pHead; FAILED(hr) && pCur; pCur = pCur->pNext) { if (StrCmpIC(pszKey, pCur->pszKey) == 0) { *ppValue = pCur->pValue; hr = S_OK; } } return hr; } template void ForEachValue(void (CCaller::*pmfnDo)(VALUE *pValue) const, const CCaller *pCmd) const { for (MAPPING *pCur = _pHead; pCur; pCur = pCur->pNext) { (pCmd->*pmfnDo)(pCur->pValue); } } private: struct MAPPING { MAPPING() : pszKey(NULL), pValue(NULL), pNext(NULL) {} ~MAPPING() { CoTaskMemFree(pszKey); delete pValue; delete pNext; } PWSTR pszKey; VALUE *pValue; MAPPING *pNext; }; MAPPING *_pHead; MAPPING **_ppInsertAt; }; // option handling framework class COptionHandler { public: virtual ~COptionHandler() {} virtual HRESULT v_SetOption(CCmdBase *pCmd, PCWSTR pszValue) const = 0; virtual void v_PrintUsage(const CCmdBase *pCmd) const = 0; }; template class COptionHandlerBase : public COptionHandler { public: typedef HRESULT (CCommandImpl::*PMFNSETOPTION)(ARGVALUE); COptionHandlerBase(PMFNSETOPTION pmfnSetOption, PCWSTR pszName, PCWSTR pszUsage) : _pmfnSetOption(pmfnSetOption) { StringCchPrintf(_szName, ARRAYSIZE(_szName), L"%s[:ARG]", pszName); StringCchCopy(_szUsage, ARRAYSIZE(_szUsage), pszUsage); } HRESULT v_SetOption(CCmdBase *pCmd, PCWSTR pszValue) const { CCommandImpl *pCmdImpl = dynamic_cast(pCmd); return pCmd ? v_SetOption(pCmdImpl, pszValue) : E_UNEXPECTED; } protected: virtual HRESULT v_SetOption(CCommandImpl *pCmdImpl, PCWSTR pszValue) const = 0; PMFNSETOPTION _pmfnSetOption; WCHAR _szName[32]; WCHAR _szUsage[256]; }; template class CStringOptionHandler : public COptionHandlerBase { public: CStringOptionHandler(PMFNSETOPTION pmfnSetOption, PCWSTR pszName, PCWSTR pszUsage) : COptionHandlerBase(pmfnSetOption, pszName, pszUsage) {} HRESULT v_SetOption(CCommandImpl *pCmd, PCWSTR pszValue) const { return (pCmd->*_pmfnSetOption)(pszValue); } void v_PrintUsage(const CCmdBase *pCmd) const { pCmd->Output(L" -%-18s %s\n", _szName, _szUsage); } }; template struct ARGENTRY { WCHAR szArg[32]; ARGVALUE value; WCHAR szUsage[128]; }; template class CEnumeratedOptionHandler : public COptionHandlerBase { public: CEnumeratedOptionHandler(PMFNSETOPTION pmfnSetOption, PCWSTR pszName, PCWSTR pszDescription, PCWSTR pszUsage, const ARGENTRY *pLookupTable, const UINT cLookupTable) : COptionHandlerBase(pmfnSetOption, pszName, pszUsage), _cLookupTable(0) { StringCchCopy(_szDescription, ARRAYSIZE(_szDescription), pszDescription); _pLookupTable = new (std::nothrow) ARGENTRY[cLookupTable]; if (_pLookupTable) { _cLookupTable = cLookupTable; for (UINT i = 0; i < _cLookupTable; i++) { _pLookupTable[i] = pLookupTable[i]; } } } ~CEnumeratedOptionHandler() { delete[] _pLookupTable; } HRESULT v_SetOption(CCommandImpl *pCmd, PCWSTR pszValue) const { HRESULT hr = E_INVALIDARG; for (UINT iEntry = 0; FAILED(hr) && (iEntry < _cLookupTable); iEntry++) { if (!StrCmpICW(pszValue, _pLookupTable[iEntry].szArg)) { hr = (pCmd->*_pmfnSetOption)(_pLookupTable[iEntry].value); } } if (FAILED(hr)) { pCmd->ParseError(L"Unrecognized %s: %s\n", _szDescription, pszValue); } return hr; } void v_PrintUsage(const CCmdBase *pCmd) const { pCmd->Output(L" -%-18s %s\n", _szName, _szUsage); for (UINT iEntry = 0; iEntry < _cLookupTable; iEntry++) { PCWSTR pszArg = _pLookupTable[iEntry].szArg; pCmd->Output(L" %-19s %s\n", (pszArg[0] ? pszArg : L""), _pLookupTable[iEntry].szUsage); } } private: WCHAR _szDescription[32]; ARGENTRY *_pLookupTable; UINT _cLookupTable; }; private: HRESULT _ProcessOptions(PCWSTR** pppszArgs, int* pcArgs); void _PrintUsageForOption(COptionHandler *pOption) const; static BOOL _fDebug; BOOL _fPrintUsageAndExit; BOOL _fError; WCHAR _szName[32]; WCHAR _szDescription[256]; WCHAR _szArgTemplate[128]; WCHAR _szPrefixedName[MAX_PATH]; CStringMap _mapOptionHandlers; }; // command implementation that server as a wrapper for a set of sub-commands, ala netsh.exe class CMetaCommand : public CCmdBase { public: typedef HRESULT (*PFNCREATECOMMAND)(CCmdBase** ppCmd, PCWSTR pszPrefix); CMetaCommand(PCWSTR pszName, PCWSTR pszDescription, PFNCREATECOMMAND* pCmds, int cCmds); virtual void v_PrintInstructions() const; template static HRESULT Create(CCmdBase** ppCmd, PCWSTR pszPrefix) { HRESULT hr = E_OUTOFMEMORY; *ppCmd = new (std::nothrow) CCmdType(); if (*ppCmd) { (*ppCmd)->AddPrefix(pszPrefix); hr = S_OK; } return hr; } protected: virtual HRESULT v_ProcessArguments( PCWSTR* ppszArgs, int cArgs); virtual HRESULT v_ExecuteCommand(); HRESULT AddCommand(PFNCREATECOMMAND pfnCreate); private: void _PrintDescriptionForCommand(CCmdBase *pCmd) const; CStringMap _mapCmds; CCmdBase *_pSpecifiedCmd; // weak reference PCWSTR* _ppszArgs; int _cArgs; };