Getting application version from within application - c++

Is there a simple way of obtaining the application version information from the resource file at runtime?
Effectively what I'd like to do is be able to have a "Version X.Y.Z" displayed at runtime without having a separate variable somewhere that I'd have to keep in sync with my ProductVersion and FileVersion.
To clarify: yes this is a standard C++ Windows project. I am aware of the GetFileVersionInfo method but it seems silly to have to open the binary from within the version in memory just to query the version information - I'm sure I'm missing something obvious here :-)

If the OS is Windows, use the GetFileVersionInfo and VerQueryValue functions.

I don't believe that there's an easier way (than opening the file and using GetFileVersionInfo and VerQueryValue). I use the following code, in case it's helpful:
static CString GetProductVersion()
{
CString strResult;
char szModPath[ MAX_PATH ];
szModPath[ 0 ] = '\0';
GetModuleFileName( NULL, szModPath, sizeof(szModPath) );
DWORD dwHandle;
DWORD dwSize = GetFileVersionInfoSize( szModPath, &dwHandle );
if( dwSize > 0 )
{
BYTE* pbBuf = static_cast<BYTE*>( alloca( dwSize ) );
if( GetFileVersionInfo( szModPath, dwHandle, dwSize, pbBuf ) )
{
UINT uiSize;
BYTE* lpb;
if( VerQueryValue( pbBuf,
"\\VarFileInfo\\Translation",
(void**)&lpb,
&uiSize ) )
{
WORD* lpw = (WORD*)lpb;
CString strQuery;
strQuery.Format( "\\StringFileInfo\\%04x%04x\\ProductVersion", lpw[ 0 ], lpw[ 1 ] );
if( VerQueryValue( pbBuf,
const_cast<LPSTR>( (LPCSTR)strQuery ),
(void**)&lpb,
&uiSize ) && uiSize > 0 )
{
strResult = (LPCSTR)lpb;
}
}
}
}
return strResult;
}
David

The only officially supported approach is to use GetFileVersionInfo() and VerQueryValue(). However, as you have noticed, GetFileVersionInfo() requires you to pass in the filename of the executable. There is a reason for this. Although it is simple to obtain the running process's filename using GetModuleFileName(), it is not the most efficient option, especially if the executable is running from a remote share, and it is not even guaranteed to be accurate if the executable has been modified on the HDD after the process has started running.
You can access the version info of the process that is already running in memory, by calling FindResource() to locate the process's RT_VERSION resource, then use LoadResource() and LockResource() to obtain a pointer to its data. It is tempting to then pass that pointer as the pBlock parameter of VerQueryValue(), but beware because doing so can crash your code! If you access the RT_VERSION resource directly then you are better off not using VerQueryValue() at all. The format of the RT_VERSION resource is documented, so you can parse the raw data manually, it is not very difficult.

As already said there is no easy way.
You can find here a great working example (ATL free).

Related

Leadtools Not enough memory available

I have to correct a bug where Leadtools function " L_LoadBitmap() returns ERROR_NO_MEMORY " , more info about it you can find Here.
The application I am working on has to be able to handle images despites the size of each image or their count.
Here the function is called:
HENHMETAFILE hemf = 0;
BITMAPHANDLE bmh = {0};
hemf = LoadMetaFile( (LPCTSTR)strPath, hDC );
if ( !hemf )
{
memset( &bmh, 0, sizeof(BITMAPHANDLE) );
L_INT nResult = 0;
nResult = L_LoadBitmap( const_cast<LPTSTR>( (LPCTSTR)strPath ), &bmh, 0, ORDER_BGR );
if ( nResult != SUCCESS )
{
MFDebugString( DL_FORMULAR, __FUNCTION__ "( %s ): Can't load background file via L_LoadBitmap (%d)\n", (LPCTSTR)strPath, nResult );
return nullptr;
}
}
pOrigBg = std::make_shared<CBackgroundImage>(strPath, hemf, bmh);
m_ImageCache[strKey.GetString()] = pOrigBg;
return pOrigBg;
Here pOrigBgis an std::shared_ptr<CBackgroundImage> object that gets constructed this way:
NxFE::CBackgroundImage::CBackgroundImage(LPCTSTR strPath, HENHMETAFILE emf, const BITMAPHANDLE& bmp)
: m_Filename(strPath), m_Metafile(emf), m_pLeadBitmap(new BITMAPHANDLE(bmp)),
m_pGdiplusBitmap(NxClass::Win32::GDIPlus::CreateBitmapFromFile((LPCSTR) m_Filename))
{
}
How can you see, pOrigBg contains a std::unique_ptr of type BITMAPHANDLE and Gdiplus::Bitmap.
Firstly, I thought that removing constructor of m_pGdiplusBitmap may help , but it doesn't.
Is any possible way to deallocate/reduce the usage of graphic memory ? Or at least some tools of inspecting Graphic Memory Usage (I'm using Microsoft Visual Studio 2017).
As you found out, functions in LEADTOOLS that allocate pixel data must be followed by calling L_FreeBitmap() when you no longer need the bitmap in memory. This is actually mentioned in the help topic you mentioned in your original question, which states: “Since the function allocates storage to hold the image, it is up to you to free this storage by calling L_FreeBitmap.”
Placement of the L_FreeBitmap call can be crucial in avoiding memory leaks. Since pixel data is typically the largest memory object in the bitmap handle, failing to free it correctly could cause huge leaks.
Also, if your code is allocating the BITMAPHANDLE structure itself using the “new” operator, you need to delete it once done with it. Even though the structure itself is typically much smaller in size than the pixel data, you should never allow any type of memory leak in your application.
If you run into any problem related to LEADTOOLS functions, feel free to email the details to our support address support#leadtools.com. Email support is free for all versions of the toolkit, whether Release (purchased) or free evaluation.
Ok, this statement worked, just had to put it in a different place
if ((bmh).Flags.Allocated)
L_FreeBitmap(&bmh);
Still got problems with GdiplusBitmap and loading images with .bmp extension, but that's already something else.
Also, in VS2017 you can go Debug -> Performance Profiler()... (Alt+F2) to use some tools for inspecting CPU / GPU / Memory Usage.

How do I detect a module loaded using LoadLibraryEx

I need to use a windows function like GetModuleHandle or GetModuleFileName to find out if a specific dll is loaded in the same process where my code is executing.
One module I'm looking for is System.Windows.Forms.dll, but even when it is loaded in process... (Here you can see it using Process Explorer)
GetModuleHandle still will not find it!
HMODULE modHandle = GetModuleHandle(L"System.Windows.Forms.dll");
GetLastError() returns ERROR_MOD_NOT_FOUND
If the function succeeds, the return value is a handle to the specified module.
If the function fails, the return value is NULL.
I think it may be something to do with how the CLR loads these dlls. I see a note on LoadLibraryEx that if the LOAD_LIBRARY_AS_DATAFILE flag is used then:
If this value is used, the system maps the file into the calling
process's virtual address space as if it were a data file. Nothing is
done to execute or prepare to execute the mapped file. Therefore, you
cannot call functions like GetModuleFileName, GetModuleHandle or
GetProcAddress with this DLL.
Maybe this is my problem, but regardless of the cause - does anyone know a way to find a managed DotNet dll in a process using native / c++ code?
Thanks!
EDIT:
Based on suggestions from Castorix in the comments I tried to use EnumProcessModules:
HMODULE modules[100];
void* hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, GetCurrentProcessId());
if (hProcess)
{
DWORD bytesNeeded;
BOOL rc = EnumProcessModules(hProcess, modules, sizeof(modules), &bytesNeeded);
if (rc)
{
int count = (int)(bytesNeeded / sizeof(HMODULE));
for (int i = 0; i < count; i++)
{
wchar_t moduleName[260];
GetModuleFileName(modules[i], moduleName, 260);
}
}
}
CloseHandle(hProcess);
This code finds a lot of the modules but not System.Windows.Forms.dll
OK, this is an attempt to an answer (or really just a too long comment, sorry).
Personally, I have never seen managed .NET DLLs in the Process Explorer pane, but might not have been looking hard / often enough. However, what I can (and always could) see are the NGENed images (*.ni.dll).
Note also the presence of System.Data.dll here, which is not NGENed, but is a mixed mode assembly and contains native code as well as managed code.
So one could conclude, that you can only see NGENed and mixed mode "assemblies" here, because they are still loaded by LoadLibrary or LoadLibraryEx.
Also note my comment, which I reproduce here for easier access:
I think the CLR does not use LoadLibrary, which would explain why you
cannot "see" them using the APIs you described. In fact,
CLR 4 Does Not Use LoadLibrary to Load Assemblies
is a blog entry that is relevant. You could always check the sources
(CoreCLR, but shouldn't matter), about how it is done in particular. I
have no really good place, but you could start
here
and then go from it. Use the ICorDebug interface instead.
Here are some relevant quotes from the blog entry linked above:
You may be asking yourself: …who cares? Well, first of all it’s good
to know. I haven’t noticed a public service announcement to the above.
It is an implementation detail, however—CLR assemblies are not even
guaranteed to be implemented using files, not to mention DLL files in
a specific format that are loaded using the LoadLibrary Win32 API.
However, there are several tools and scenarios which have come to rely
on the fact the CLR loads assemblies using LoadLibrary. For example,
up to CLR 4, if you wanted to know which .NET assemblies were loaded
in your process, a fairly reliable heuristic would be to fire up
Sysinternals Process Explorer and look at the DLLs view of a given
process. This doesn’t work for CLR 4, as you can see here:
Frankly, I don't know how Process Explorer manages to show assemblies (not NGENed and not mixed mode) in your case - appart from you are watching a CLR2 process. However, mind you that PE does not only use Win32 APIs. It also uses WMI and probably also uses the CLR directly for more information. For example, the "Process Properties/.NET Assemblies" and "Process Properties/.NET Performance" tabs most likely use ICorDebug/ICorProfile and performance counters/ETW respectively.
You might need to use on of those interfaces as well, or something else from the unmanaged Debugging API or the unmanaged API in general.
Whatever it is, I don't think that EnumProcessModules, etc. will get you there for reasons above.
To add to the above answer and provide relevant code; it was not possible to use a native function like EnumProcessModules to detect the non-ngen'ed DotNet dlls and instead I had to use c++ interfaces to the CLR.
There is a lot more info here: https://blogs.msdn.microsoft.com/calvin_hsia/2013/12/05/use-reflection-from-native-c-code-to-run-managed-code/ The code most relevant to this particular question was:
HRESULT GetAssemblyFromAppDomain(_AppDomain* pAppDomain, LPCWSTR wszAssemblyName, _Deref_out_opt_ _Assembly **ppAssembly)
{
*ppAssembly = NULL;
// get the assemblies into a safearray
SAFEARRAY *pAssemblyArray = NULL;
HRESULT hr = pAppDomain->GetAssemblies(&pAssemblyArray);
if (FAILED(hr))
{
return hr;
}
// put the safearray into a smart ptr, so it gets released
CComSafeArray<IUnknown*> csaAssemblies;
csaAssemblies.Attach(pAssemblyArray);
size_t cchAssemblyName = wcslen(wszAssemblyName);
long cAssemblies = csaAssemblies.GetCount();
for (long i=0; i<cAssemblies; i++)
{
CComPtr<_Assembly> spAssembly;
spAssembly = csaAssemblies[i];
if (spAssembly == NULL)
continue;
CComBSTR cbstrAssemblyFullName;
hr = spAssembly->get_FullName(&cbstrAssemblyFullName);
if (FAILED(hr))
continue;
// is it the one we want?
if (cbstrAssemblyFullName != NULL &&
_wcsnicmp(cbstrAssemblyFullName,
wszAssemblyName,
cchAssemblyName) == 0)
{
*ppAssembly = spAssembly.Detach();
hr = S_OK;
break;
}
}
if (*ppAssembly == 0)
{
hr = E_FAIL;
}
return hr;
}
There's some information on the CLR interfaces here:
ICLRMetaHost
ICLRRuntimeInfo
ICorRuntimeHost
_AppDomain
_Assembly

C++ program needs an file association

I'm distributing a freeware product that reads and writes text files with a unique extension. I hoped that double-clicking such a file would automatically start the app.
While developing on Windows 7 Professional, I set up an association to open my files upon double-click, by right-clicking the file->Open With...->Choose Default Program...->Browse... followed by "Always use the selected program to open this type of file." Good. It did just what it needed to. I was going to ship my program with instructions for the users to do the same.
However, when I moved the location of the binary, I see the "Always use" is now grayed out/insensitive, so while I could browse to the new binary I couldn't make it default. Since I thought my users would have trouble with this too, I wanted to see if I could have installation or run of the program take care of the mapping.
I looked at Windows Installer for about 5 minutes before determining it was far more power and complexity than I needed (For my needs, a zip file would be sufficient except for this file mapping.)
So I took a look at having my program, at start-up, set up the mapping itself if it wasn't there already. (I know this would be very bad behavior if we were talking about a common file type such as .html or .jpg, but in this case its some .blahblah extension that surely no-one else uses for anything.)
Based on information at http://www.cplusplus.com/forum/windows/26987/ and http://msdn.microsoft.com/en-us/library/cc144148(v=vs.85).aspx I was able to have my program, at startup, open HKEY_CLASSES_ROOT\.blahblah and confirm (and change if needed) the default text to be an accurate description of the file (replacing some text that may have been created by default when I did the manual association last summer). However, when it came to creating HKEY_CLASSES_ROOT\firm.app.1\shell\open\command, my RegCreateKeyEx() wrapper that works fine to change the value of \.blahblah is now giving return code 5, apparently a lack of permission.
Upon further research it seems that the permissions model may cause all such requests to fail. Can anyone confirm or deny this? If confirm, is there a good reference I should study on the matter?
Otherwise, what are the suggestions? Should I bite the bullet and study Windows Installer? Or is there a way to get the permissions I need to edit the registry when my own software starts the first time?
Note I'm developing with Visual Studio 2008 on Windows 7 Professional, and although still an amateur Windows programmer I've been doing C++ since the '80s on Unix/Linux...
OK, I got it working and I'll share what I learned.
1) Decide a ProgID. It should be vender.app.versionnumber according to docs, but regedit shows that practically no vendor follows that rule. I did though.
2) Most of the MSFT docs on this topic talk about creating entries under HKEY_CLASSES_ROOT, but I found important info on http://msdn.microsoft.com/en-us/library/cc144148(v=vs.85).aspx:
Important considerations about file types include: The
HKEY_CLASSES_ROOT subtree is a view formed by merging
HKEY_CURRENT_USER\Software\Classes and
HKEY_LOCAL_MACHINE\Software\Classes In general, HKEY_CLASSES_ROOT is
intended to be read from but not written to. For more information, see
the HKEY_CLASSES_ROOT article.
3) to have the association show up without rebooting, you must call SHChangeNotify(). (This threw me, because even when I had the right code, the results didn't show up correctly in Explorer.)
Here's the code I ended up with. If I go through with REGEDIT and delete all mention of .moselle (my extention) and MoselleIDE (my application) then run my program once by hand, I get the click-to-open behavior, file icon becomes the same as the app, etc. Note the code uses a logging function, and it also verbosely reports which type of success it has: 1) variable already correct, 2) variable changed, 3) variable didn't exist.
void RegSet( HKEY hkeyHive, const char* pszVar, const char* pszVa
lue ) {
HKEY hkey;
char szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof( szValueCurrent );
int iRC = RegGetValue( hkeyHive, pszVar, NULL, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize );
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ( iRC != ERROR_SUCCESS && !bDidntExist )
AKS( AKSFatal, "RegGetValue( %s ): %s", pszVar, strerror( iRC ) );
if ( !bDidntExist ) {
if ( dwType != REG_SZ )
AKS( AKSFatal, "RegGetValue( %s ) found type unhandled %d", pszVar, dwType );
if ( strcmp( szValueCurrent, pszValue ) == 0 ) {
AKS( AKSTrace, "RegSet( \"%s\" \"%s\" ): already correct", pszVar, pszValue );
return;
}
}
DWORD dwDisposition;
iRC = RegCreateKeyEx( hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegCreateKeyEx( %s ): %s", pszVar, strerror( iRC ) );
iRC = RegSetValueEx( hkey, "", 0, REG_SZ, (BYTE*) pszValue, strlen( pszValue ) + 1 );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegSetValueEx( %s ): %s", pszVar, strerror( iRC ) );
if ( bDidntExist )
AKS( AKSTrace, "RegSet( %s ): set to \"%s\"", pszVar, pszValue );
else
AKS( AKSTrace, "RegSet( %s ): changed \"%s\" to \"%s\"", pszVar, szValueCurrent, pszValue );
RegCloseKey(hkey);
}
int SetUpRegistry() {
//app doesn't have permission for this when run as normal user, but may for Admin? Anyway, not needed.
//RegSet( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\MoselleIDE.exe", "C:\\Moselle\\bin\\MoselleIDE.exe" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle", "Moselle.MoselleIDE.1" );
// Not needed.
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\Content Type", "text/plain" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\PerceivedType", "text" );
//Not needed, but may be be a way to have wordpad show up on the default list.
//RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\OpenWithProgIds\\Moselle.MoselleIDE.1", "" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1", "Moselle IDE" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1\\Shell\\Open\\Command", "C:\\Moselle\\bin\\MoselleIDE.exe %1" );
SHChangeNotify( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL );
return 0;
}
Finally, yes, I know I should figure out an installer, but I'm a C++ expert, not a Windows configuration and terminology expert and its a lot easier for me to write the above 50 lines than it is to even start figuring out how to configure an installer. This is for an alpha release and I'll watch this topic for better ideas for future releases.
Registry changes and all actions that require higher permissions should be done at installation stage, not at application startup. You probably want to use installation software. Otherwise your software will create serious security breach.

Remote thread is failing on call to LoadLibrary with error 87

I am tring to create a Remote thread that will load a DLL I wrote, and run a function from it.
The DLL is working fine (Checked) but from some reason, the Remote thread fails and the proccess in which it was created stop responding.
I used ollyDebug to try and see what is going wrong and I noticed two things...
My strings (dll name and function name) are passed to the remote thread correctly
The thread fails on LoadLibrary with lasterror code 87 "ERROR_INVALID_PARAMETER"
My best guess is that somehow, The remote thread can't find LoadLibrary (Is this because the linker is done with repspect to my proccess???, Just a guess...)
What am I doing wrong?
This is the code to the remote function:
static DWORD WINAPI SetRemoteHook (DATA *data)
{
HINSTANCE dll;
HHOOK WINAPI hook;
HOOK_PROC hookAdress;
dll = LoadLibrary(data->dll);
hookAdress = (HOOK_PROC) GetProcAddress(dll,data->func);
if (hookAdress != NULL)
{
(hookAdress)();
}
return 1;
}
Edit:
This is the part in which I allocate the memory to the remote proccess:
typedef struct
{
char* dll;
char* func;
} DATA;
char* dllName = "C:\\Windows\\System32\\cptnhook.dll";
char* funcName = "SetHook";
char* targetPrgm = "mspaint.exe";
Data lData;
lData.dll = (char*) VirtualAllocEx( explorer, 0, sizeof(char)*strlen(dllName), MEM_COMMIT, PAGE_READWRITE );
lData.func = (char*) VirtualAllocEx( explorer, 0, sizeof(char)*strlen(funcName), MEM_COMMIT, PAGE_READWRITE );
WriteProcessMemory( explorer, lData.func, funcName, sizeof(char)*strlen(funcName), &v );
WriteProcessMemory( explorer, lData.dll, dllName, sizeof(char)*strlen(dllName), &v );
rDataP = (DATA*) VirtualAllocEx( explorer, 0, sizeof(DATA), MEM_COMMIT, PAGE_READWRITE );
WriteProcessMemory( explorer, rDataP, &lData, sizeof(DATA), NULL );
Edit:
It looks like the problem is that the remote thread is calling a "garbage" address
instead of LoadLibrary base address. Is there a possibily Visual studio linked
the remote proccess LoadLibrary address wrong?
Edit:
when I try to run the same exact code as a local thread (I use a handle to the current procces in CreateRemoteThread) the entire thing works just fine. What can cause this?
Should I add the calling function code? It seems to be doing its job as
the code is being executed in the remote thread with the correct parameters...
The code is compiled under VS2010.
data is a simple struct with char* 's to the names. (As explicetly writing the strings in code would lead to pointers to my original proccess).
What am I doing wrong?
Failing with ERROR_INVALID_PARAMETER indicates that there is a problem with the parameters passed.
So one should look at data->dll which represents the only parameter in question.
It is initialised here:
lData.dll = VirtualAllocEx(explorer, 0, sizeof(char) * (strlen(dllName) + 1), MEM_COMMIT, PAGE_READWRITE);
So let's add a check whether the allocation of the memory which's reference should be store into lData.dll really succeded.
if (!lData.dll) {
// do some error logging/handling/whatsoever
}
Having done so, you might have detected that the call as implemented failed because (verbatim from MSDN for VirtualAllocEx()):
The function fails if you attempt to commit a page that has not been
reserved. The resulting error code is ERROR_INVALID_ADDRESS.
So you might like to modifiy the fourth parameter of the call in question as recommended (again verbatim from MSDN):
To reserve and commit pages in one step, call VirtualAllocEx with
MEM_COMMIT | MEM_RESERVE.
PS: Repeat this exercise for the call to allocate lData.func. ;-)
It's possible that LoadLibrary is actually aliasing LoadLibraryW (depending on project settings), which is the Unicode version. Whenever you use the Windows API with "char" strings instead of "TCHAR", you should explicitly use ANSI version names. This will prevent debugging hassles when the code is written, and also in the future for you or somebody else in case the project ever flips to Unicode.
So, in addition to fixing that horrible unterminated string problem, make sure to use:
LoadLibraryA(data->dll);

How to open the registry and get the specific value in c++

Ineed to open this key" HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\100\"
and get the "VerSpecificRootDir" value using c++ ....How can i do this
I have no knowledge abt it can any one help me in this regard..
After Getting All The Support I did it as
unsigned long type=REG_SZ, size=1024;
char res[1024]="";
HKEY hk;
long n = RegOpenKeyEx(HKEY_LOCAL_MACHINE,TEXT("SOFTWARE\\Microsoft\\Microsoft SQL Server\\100"),
0,KEY_QUERY_VALUE, &hk );
if ( n == ERROR_SUCCESS )
{
printf("OK \n");
RegQueryValueEx(hk,L"VerSpecificRootDir",NULL,&type,(LPBYTE)&res[0],&size);
RegCloseKey(hk);
}
But in this i am not getting the value of "VerSpecificDirectory" what i have missed let me know?? what is wrong in this code....
You can use the Windows function SHRegGetValue like this:
TCHAR buffer[260];
DWORD bufferSize = sizeof(buffer);
SHRegGetValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SQL Server\\100", "VerSpecificRootDir", SRRF_RT_REG_SZ, NULL, buffer, &bufferSize);
After calling the function, buffer will contain a null-terminated string of the directory. Might want to check the return value for errors too.
#include <windows.h>
HKEY hKey;
int buffersize = 1024;
char *buffer = new char[buffersize];
RegOpenKeyEx (HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Microsoft SQL Server\\100",NULL,KEY_READ,&hKey);
RegQueryValueEx(hKey,"VerSpecificRootDir",NULL,NULL,(LPBYTE) buffer, buffersize);
std::cout << buffer;
RegCloseKey (hKey);
I currently only know how to do that by using the Qt framework:
QSettings settings("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\100\");
QString dir = settings.value("VerSpecificRootDir");
Qt is free and allows you to use simple and very good documented c++ APIs instead of the mixed and up Windows APIs. Sorry - that sounds for advertising... but I formerly struggled with the very bad designed Windows API and than found Qt, which allows me to develop faster and (a cool benefit) for multiple platforms without having to adapt my code.
Regards,
Chris
You can sure use ATL::CRegKey. It has all the functionality you need.
http://msdn.microsoft.com/en-us/library/xka57xy4(VS.80).aspx
Registry API was available (for ~15 years) from Windows 95 onwards. It's all well documented on MSDN ; and if you care to Google so many examples.