Hi Community,
I am currently working on an add-in for Visual Studio to expose some functionality available in the “Debugger Engine and Extension APIs”. The development is in the very early stages, but I have completed already one feature required by the Add-in, which is the ability to read XML configuration files similarly to how .NET and the CLR do but in this case using Visual C++. This Add-in comprises native and managed code, where .NET helps me to interact with the IDE and the core functionality of the Add-in is encapsulated in a DLL. This blog entry describes the ConfigReader class.
A sample configuration file is shown below
<?xml version="1.0" encoding="utf-8"?>
<config>
<sendOutputToVSWindow enabled="true" />
<extensions>
<extension name="ext" path="C:Program Files (x86)Windows Kits8.1Debuggersx86winextext.dll" />
<extension name="wow64exts" path="C:Program Files (x86)Windows Kits8.1Debuggersx86WINXPwow64exts.dll" />
<extension name="exts" path="C:Program Files (x86)Windows Kits8.1Debuggersx86WINXPexts.dll" />
<extension name="uext" path="C:Program Files (x86)Windows Kits8.1Debuggersx86winextuext.dll" />
<extension name="ntsdexts" path="C:Program Files (x86)Windows Kits8.1Debuggersx86WINXPntsdexts.dll" />
</extensions>
</config>
The configuration file must reside in the same folder of the library, and it’s located in the constructor of the class
ConfigReader::ConfigReader() {
ReadConfig();
}
void ConfigReader::ReadConfig() {
if (!LocateConfigFile())
throw std::exception("Config file not found. Unable to proceed.");
}
Since the DLL can be anywhere (unlike .NET) that it’s either in the GAC or the bin folder of the application, I needed to find the configuration file in the same path of the library, and this is done via enumerating the loaded modules.
BOOL ConfigReader::LocateConfigFile() {
auto retval = FALSE;
DWORD nModuleCount = 0;
IXMLDOMDocumentPtr pDocPtr;
MODULEINFO moduleDetails = {0};
HANDLE hToken = NULL, hProcess = NULL;
HMODULE hLoadedModules[Max_Loaded_Modules];
CoInitialize(NULL);
if ((hToken = GetThreadToken()) != NULL && SetPrivilege(hToken, SE_DEBUG_NAME, TRUE)) {
if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId())) != NULL) {
if ((EnumProcessModulesEx(hProcess, hLoadedModules, sizeof(hLoadedModules), &nModuleCount, LIST_MODULES_ALL)) != NULL) {
auto modules = std::vector<HMODULE>(std::begin(hLoadedModules), std::end(hLoadedModules));
#ifdef _WIN64
nModuleCount = Item_Count(nModuleCount) / 2;
#else
nModuleCount = Item_Count(nModuleCount);
#endif
auto config = DoesConfigFileExist(hProcess, modules, TargetImageName);
if (!config.empty())
retval = ParseConfigFile(config);
}
CloseHandle(hProcess);
}
SetPrivilege(hToken, SE_DEBUG_NAME, FALSE);
CloseHandle(hToken);
CoUninitialize();
}
return retval;
}
std::wstring ConfigReader::DoesConfigFileExist(const HANDLE& hProcess, std::vector<HMODULE>& hModules, const wchar_t* targetImage) {
BOOL found = FALSE;
std::wstring retval;
wchar_t szDir[_MAX_DIR];
wchar_t szExt[_MAX_EXT];
wchar_t szBuffer[MAX_PATH];
wchar_t szFName[_MAX_FNAME];
wchar_t szDrive[_MAX_DRIVE];
std::find_if(hModules.begin(), hModules.end(), [&, this](HMODULE hModule) {
auto ret = FALSE;
if (!found && hModule != nullptr && (GetModuleFileNameEx(hProcess, hModule, szBuffer, Array_Size(szBuffer))) != NULL) {
size_t cntConverted;
char szAnsiPath[MAX_PATH];
_wsplitpath_s(szBuffer, szDrive, Array_Size(szDrive), szDir, Array_Size(szDir), szFName, Array_Size(szFName), szExt, Array_Size(szExt));
auto imageName = std::wstring(szFName).append(szExt);
auto configPath = std::wstring(szDrive).append(szDir).append(ConfigFileName);
wcstombs_s(&cntConverted, szAnsiPath, configPath.data(), configPath.size() );
std::ifstream configFile(szAnsiPath);
if (wcscmp(targetImage, imageName.data()) == 0 && configFile.good()) {
configFile.close();
retval.assign(configPath);
found = TRUE;
}
}
return ret;
});
return retval;
}
HANDLE ConfigReader::GetThreadToken() {
HANDLE retval;
auto flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;
if (!OpenThreadToken(GetCurrentThread(), flags, FALSE, &retval)) {
if (GetLastError() == ERROR_NO_TOKEN) {
if (ImpersonateSelf(SecurityImpersonation) &&
!OpenThreadToken(GetCurrentThread(), flags, FALSE, &retval))
retval = NULL;
}
}
return retval;
}
BOOL ConfigReader::SetPrivilege(HANDLE& hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) {
LUID luid;
auto retval = FALSE;
TOKEN_PRIVILEGES tp = {0};
DWORD cb = sizeof(TOKEN_PRIVILEGES);
if (LookupPrivilegeValue(NULL, Privilege, &luid)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, cb, NULL, NULL);
if (GetLastError() == ERROR_SUCCESS)
retval = TRUE;
}
return retval;
}
A few things worth mentioning:
- Always use STL containers and algorithms when possible (e.g.: std::vector instead of a C/C++ type array.
- To query information about a process, this one has to be opened and most of the times (depending on what it’s required to do) a few flags must be set. Also remember to close any handle after it’s been used.
- Use std::wstring (UNICODE) instead of wchar_t*. They’re safer and better to use.
- Use lambdas in conjunction with algorithms (e.g. s td::find_if)
- Get to learn and know the functions to convert from Multibyte (ANSI) to wide characters (UNICODE) (e.g.: wcstombs_s)
- Pass references (and use const whenever is possible)
Once the configuration file is found, it is parsed using the MSMXL parser which it’s COM based, therefore the use of smart pointers is strongly suggested.
BOOL ConfigReader::ParseConfigFile(const std::wstring& configFile) {
auto retval = FALSE;
VARIANT_BOOL success;
IXMLDOMDocumentPtr pDocPtr;
IXMLDOMNodePtr selectedNode;
CoInitialize(NULL);
pDocPtr.CreateInstance("Msxml2.DOMDocument.6.0");
if (SUCCEEDED(pDocPtr->load(_variant_t(configFile.c_str()), &success))) {
if (SUCCEEDED(pDocPtr->selectSingleNode(_bstr_t(XmlRootNode), &selectedNode))) {
ProcessElementRecursively(selectedNode);
retval = TRUE;
}
}
CoUninitialize();
return retval;
}
void ConfigReader::ProcessElementRecursively(IXMLDOMNodePtr& node) {
long childrenCount = 0;
IXMLDOMNodePtr childNode;
IXMLDOMNodeListPtr children;
CoInitialize(NULL);
if (SUCCEEDED(node->get_childNodes(&children)) && SUCCEEDED(children->get_length(&childrenCount)) && childrenCount > 0) {
for (auto nCount = 0; nCount < childrenCount; nCount++) {
if (SUCCEEDED(children->get_item(nCount, &childNode))) {
ExtractInformationFromElement(childNode);
ProcessElementRecursively(childNode);
}
}
}
CoUninitialize();
}
void ConfigReader::ExtractInformationFromElement(IXMLDOMNodePtr& node) {
size_t nSize;
VARIANT value;
std::wstring key;
BSTR nodeContent;
DOMNodeType nodeType;
WCHAR szNodeText[512] = {0};
char szBuffer[MAX_PATH] = {0};
CoInitialize(NULL);
if (SUCCEEDED(node->get_nodeType(&nodeType)) && nodeType == DOMNodeType::NODE_ELEMENT) {
nodeContent = SysAllocString(szNodeText);
auto pElement = (IXMLDOMElementPtr)node;
pElement->get_tagName(&nodeContent);
if (wcscmp(nodeContent, L"sendOutputToVSWindow") == 0) {
pElement->getAttribute(_bstr_t(L"enabled"), &value);
if (value.vt != VT_NULL)
Properties.insert(std::make_pair(nodeContent, value.bstrVal));
} else if (wcscmp(nodeContent, L"extension") == 0) {
pElement->getAttribute(_bstr_t(L"name"), &value);
if (value.vt != VT_NULL)
key.assign(value.bstrVal);
pElement->getAttribute(_bstr_t(L"path"), &value);
if (value.vt != VT_NULL && !key.empty()) {
Properties.insert(std::make_pair(key.c_str(), value.bstrVal));
wcstombs_s(&nSize, szBuffer, key.c_str(), key.size());
std::string name(szBuffer);
wcstombs_s(&nSize, szBuffer, value.bstrVal, wcslen(value.bstrVal));
std::string path(szBuffer);
m_extensions.push_back(ExtInformation(name, path));
}
}
SysFreeString(nodeContent);
}
CoUninitialize();
}
Our ConfigReader object has two main fields (Extensions and Properties) – Depicted below
and we can also retrieve any property from the configuration file, in a similar way we do it in .NET. This is accomplished via the GetSetting method
const std::wstring ConfigReader::GetSetting(const wchar_t* key) {
std::wstring retval;
if (Properties.size() > 0 && key != nullptr && wcslen(key) > 0) {
typedef std::pair<const std::wstring, const std::wstring> item;
std::find_if(Properties.begin(), Properties.end(), [&](item i) {
auto ret = FALSE;
if (retval.size() == 0) {
if (wcscmp(i.first.data(), key) == 0) {
retval.assign(i.second);
ret = TRUE;
}
}
return ret;
});
}
return retval;
}
Regards,
Angel