Hi Community,
I’ve just come back to the office after rolling off from an engagement, so this week while being back to the bench I’ve been practicing and studying some Qt for my upcoming certification, doing a few things around Raspberry Pi + Kinect + OpenKinect on Raspbian but at the same time I also built an utility that’s been missing in Windows, however available in any other *nix operating system and I’m referring to whatis command which I find very useful.
whatis displays information stored in manual pages description, so I thought that I needed to provide similar functionality, hence I chose to use ESENT that’s built-in to Windows. ESENT is a NoSQL ISAM database primarily for C/C++ developers that can be also used from managed code (ESENT Managed Interop on codeplex, for example).
My version of whatis stores information about any binary (DLL or EXE) that contains a VERSIONINFO resource in an ESENT database. This information is stored the first time the utility is called to query a file, and then subsequent calls retrieve it from the database. The solution contains a few classes being the ESENTCore the most important one because it acts as DAL to interact with ESENT. If the database is deleted, do not worry because it will get created again.
I thought it’d be a great idea to dissect the solution by firstly describing the method that creates the table used by whatis
vector<ColumnInfo> EsentCore::GetColumnDefinition() {
vector<ColumnInfo> retval;
retval.push_back(ColumnInfo{0, wstring(Pk_Id_Column), JET_COLUMNDEF{
sizeof(JET_COLUMNDEF), NULL, JET_coltypLong, NULL, NULL, NULL, NULL,
GetLengthInBytes(4), JET_bitColumnAutoincrement}});
retval.push_back(ColumnInfo{0, wstring(Name_Column), JET_COLUMNDEF{
sizeof(JET_COLUMNDEF), NULL, JET_coltypText, NULL, NULL, NULL, NULL,
GetLengthInBytes(50), JET_bitColumnFixed | JET_bitColumnNotNULL}});
retval.push_back(ColumnInfo{0, wstring(Location_Column), JET_COLUMNDEF{
sizeof(JET_COLUMNDEF), NULL, JET_coltypText, NULL, NULL, NULL, NULL,
GetLengthInBytes(MAX_PATH), JET_bitColumnFixed | JET_bitColumnNotNULL}});
retval.push_back(ColumnInfo{0, wstring(Description_Column), JET_COLUMNDEF{
sizeof(JET_COLUMNDEF), NULL, JET_coltypLongText , NULL, NULL, NULL, NULL,
GetLengthInBytes(MAX_PATH * 5), JET_bitColumnMaybeNull}});
return retval;
}
It is a simple structure (Primary key that’s auto numeric, file name, file location and file description). Data operations with ESENT have to be performed in the context of a transaction, and the database must be prepared for these operations to occur, a good example can be seen in the InsertRecord method shown below
bool EsentCore::InsertRecord(const vector<ColumnType>& columns) {
auto colIndex = 0;
auto retval = false;
auto columnInfo = GetColumnIds();
auto newColumns = make_unique<JET_SETCOLUMN[]>(modifiableColumnCount);
if (columns.size() > 0) {
if (SUCCEEDED(JetBeginTransaction(m_sessionId)) && SUCCEEDED(JetPrepareUpdate(m_sessionId, m_tableId, JET_prepInsert))) {
ZeroMemory(newColumns.get(), sizeof(JET_SETCOLUMN) * modifiableColumnCount);
for_each(columns.begin(), columns.end(), [&](const ColumnType& column) {
if (column.ColumnName != Pk_Id_Column && column.ColumnName != CreatedOn_Column) {
auto colInfo = columnInfo.at(column.ColumnName);
newColumns[colIndex] = JET_SETCOLUMN{0};
newColumns[colIndex].columnid = colInfo.columnid;
auto ptrData = make_unique<char[]>(MAX_PATH * 5);
if (colInfo.coltyp == JET_coltypText || colInfo.coltyp == JET_coltypLongText) {
auto wstr = reinterpret_cast<wchar_t*>(const_cast<void*>(column.ColumnData.pvData));
auto size = wcslen(wstr);
wcstombs(ptrData.get(), wstr, size);
newColumns[colIndex].pvData = malloc(size);
memcpy(const_cast<void*>(newColumns[colIndex].pvData), ptrData.get(), size);
newColumns[colIndex].cbData = size;
}
newColumns[colIndex].err = JET_errSuccess;
colIndex++;
}
});
retval = SUCCEEDED(JetSetColumns(m_sessionId, m_tableId, newColumns.get(), modifiableColumnCount)) &&
SUCCEEDED(JetUpdate(m_sessionId, m_tableId, nullptr, NULL, NULL));
// Free memory
for (auto nIndex = 0; nIndex < colIndex; nIndex++)
free(const_cast<void*>(newColumns[nIndex].pvData));
// Commit or rollback transaction
if (retval)
JetCommitTransaction(m_sessionId, NULL);
else JetRollback(m_sessionId, NULL);
}
}
return retval;
}
I’m a generic programming aficionado that’s why I enjoy using templates in C++ or generics in .NET and they’re not the same in case you’re wondering as I had mentioned here. Speaking of which, there’s a template function (similar to a generic method in .NET) that based on the input parameter it behaves differently yet it returns the same data type
template <typename T>
WhatIsRecord EsentCore::GetRecord(const T* cols) {
WhatIsRecord retval;
vector<void*> values;
JET_RETRIEVECOLUMN* colValues = nullptr;
vector<ColumnType>* colDef = nullptr;
strstream name, location, description;
auto colType = typeid(T) == typeid(JET_RETRIEVECOLUMN);
if (colType)
colValues = (JET_RETRIEVECOLUMN*)cols;
else {
colDef = (vector<ColumnType>*) cols;
for (auto index = 0; index < 3; index++) {
auto colData = colDef->at(index + 1).ColumnData;
auto ptrData = make_unique<char[]>(MAX_PATH * 5);
auto wstr = reinterpret_cast<wchar_t*>(const_cast<void*>(colData.pvData));
auto size = wcslen(wstr);
wcstombs(ptrData.get(), wstr, size);
values.push_back(malloc(colData.cbData));
ZeroMemory(values.at(index), colData.cbData);
memcpy(values.at(index), ptrData.get(), size);
}
}
name << reinterpret_cast<char*>(const_cast<void*>(colType ? colValues[1].pvData : values.at(0))) << endl;
retval.Name = Trim(name);
location << reinterpret_cast<char*>(const_cast<void*>(colType ? colValues[2].pvData : values.at(1))) << endl;
retval.Location = Trim(location);
description << reinterpret_cast<char*>(const_cast<void*>(colType ? colValues[3].pvData : values.at(2))) << endl;
retval.Description = Trim(description);
for_each(values.begin(), values.end(), [&](void* block) {free(block); });
return retval;
}
The method mentioned above uses the typeid operator that’s one of the mechanisms in the language responsible to provide RTTI. This is the closest we can get to typeof operator in .NET.
Strings are one of the trickiest things to deal with in native code, that’s something most managed code developers take for granted and they can easily use and handle, but fear not, in C++ we have STL’s strings and also streams to make our lives easier as shown in my implementation of Trim method below
string EsentCore::Trim(strstream& text) {
string retval;
string newString(text.str());
for (auto index = 0; isprint((unsigned char) newString[index]); index++)
retval.append(1, newString[index]);
retval.erase(retval.find_last_not_of(' ') + 1);
return retval;
}
I’ve also created a project on codeplex – http://whatis.codeplex.com in case you want to use the utility or extend it… and yes, I know it’s got a very funny URL
There’s one tool I use every time I have to work with an ESENT database. It’s free, awesome and lightweight and that’s ESEDatabaseView by NirSoft. The image depicted below show the objects in my whatis database
as well as some of the contents of WhatIsCatalogue (Table used by whatis) with information loaded by running a PowerShell script described next
The following script retrieves all the executable images in System32, loops through the collection and with every iteration it adds the file information to whatis.db
$files = Get-ChildItem "C:\Windows\System32" -Filter *.exe
for ($i=0; $i -lt $files.Count; $i++) {
$exe = "c:\windows\system32\whatis.exe"
&;$exe $files[$i].FullName
}
As mentioned earlier, images below correspond to whatis running on my three different development environments (Ubuntu Linux, OS X El Capitan and our custom version of the utility running on Windows 10