I have a really strange problem. Looking for the cause on the web and try everything. Nothing helps.
First case:
(This works exactly as expected. Windows task manager shows the constant memory size, and does not increase.)
unsigned long WINAPI thfun(void * arg)
{
::Sleep(50);
::ExitThread(0);
return 0;
}
int main(int argc, const wchar_t ** argv)
{
HANDLE th = 0;
DWORD thid, err;
while (true)
{
th = ::CreateThread(0, 0, thfun, 0, 0, &thid);
if (!th)
{
err = ::GetLastError();
}
::WaitForSingleObject(th, INFINITE);
}
return 0;
}
Second case:
unsigned long WINAPI thfun(void * arg)
{
::Sleep(50);
::ExitThread(0);
return 0;
}
int main(int argc, const wchar_t ** argv)
{
WORD ver;
WSADATA wsadata;
ver = MAKEWORD(2, 2);
if (WSAStartup(ver, &wsadata)) return 1;
::Sleep(50);
HANDLE th = 0;
DWORD thid, err;
while (true)
{
th = ::CreateThread(0, 0, thfun, 0, 0, &thid);
if (!th)
{
err = ::GetLastError();
}
::WaitForSingleObject(th, INFINITE);
}
return 0;
}
If I call any function from winsock least once created threads do not release memory.
Windows task manager shows ever-growing memory of my application.
What should I do so that I achieve the same behavior as in the first case when I use winsock?
I use visual studio 2013
Thank you very much for any help
You do not close your thread handles. A common error.
Your core loop should look like that:
while (true)
{
th = ::CreateThread(0, 0, thfun, 0, 0, &thid);
if (!th)
{
err = ::GetLastError();
}
::WaitForSingleObject(th, INFINITE);
CloseHandle(th);
}
That problem exists in both of your examples. That memory grow of the second sample can be a side effect.
ExitThread(0) is never a good idea and I do not understand why Microsoft recommand it for C. As the Winsock API should not have any destructor, it should not be a problem. Nevertheless, do not use it.
UPDATE
I tested your code as release on a Windows 7 64bit SP1 System with Antivira personal installed (my gaming machine). Also on my Windows 8 VM (parallels). Both system did not show the problems you described and show in your video. This is IMHO good news for you, because it seems a problem of your installation and not a general problem.
The video shows a leak of only a few bytes per ended thread and strict linear growing per thread. This looks for me like thread associated information, usually stored nowadays in TLS (Thread Local Storage). Also it only appears when you init The WSASocket system. If the WSASocket system itself would be the problem, we would found reports of it for sure(but I didn't). I believe a hook DLL is causing that problem, a DLL is informed over the DllMain of any started or ended thread of the process. Any virus scanner or keyboard addon(!) can cause such a problem as they usually use hook DLLs and manipulate IOs like pipes and sockets.
Unfortunatly I only know one way to find out:
Make a release canditate of your sample. Make sure the problem exist.
Make a clean install of Windows 7
Install step by step the environment you use on your produktive system. Make sure you restart the Computer after every step.
Hopefully find the culprit.
Deactivating or uninstall hooks may help but need not. Unfortunately installing programs on windows system is maximal inversive.
Sorry for not heaving the easy answer.
Related
I'm trying to use the Leadtools API version 21 for automatically scanning some documents and here is a sudo code of what I have done (it runs in a secondary thread and the unlock has been done in the main thread):
void CheckRetCode(int rc)
{
if (SUCCESS != rc)
{
L_TCHAR errMsg[1024];
memset(errMsg, 0, sizeof(errMsg));
L_GetFriendlyErrorMessage(rc, errMsg, 1024, L_FALSE);
throw TLeadException(errMsg, rc);
}
}
void OnThreadExecute(void)
{
HTWAINSESSION hSession = nullptr;
APPLICATIONDATA appData;
L_INT nRet;
L_TCHAR pszTwnSourceName[1024];
LTWAINSOURCE sInfo;
memset(&appData, 0, sizeof(APPLICATIONDATA));
appData.uStructSize = sizeof(APPLICATIONDATA);
appData.hWnd = hWnd;// hWnd is valid handle of my main window
appData.uLanguage = TWLG_ENGLISH_USA;
appData.uCountry = TWCY_USA;
wcscpy(appData.szManufacturerName, L"MyCompanyName");
wcscpy(appData.szAppProductFamily, L"MyProductName");
wcscpy(appData.szAppName, appData.szAppProductFamily);
wcscpy(appData.szVersionInfo, L"Version 0.1.0.1");
nRet = L_TwainInitSession2(&hSession, &appData, LTWAIN_INIT_MULTI_THREADED);
CheckRetCode(nRet);// the exception gets catched elsewhere but no error reported here
memset(pszTwnSourceName, 0, sizeof(pszTwnSourceName));
wcscpy(pszTwnSourceName, L"EPSON Artisan837/PX830"); // the name of the scanner is verifyed
sInfo.uStructSize = sizeof(LTWAINSOURCE);
sInfo.pszTwainSourceName = pszTwnSourceName;
CheckRetCode(L_TwainSelectSource(hSession, &sInfo)); // No error reported here
CheckRetCode(L_TwainStartCapsNeg(hSession)); // in here I get the return value -84 which is reported as "TWAIN DS or DSM reported error, app shouldn't (no need for your app to report the error)."
// the rest of the code but we cannot get there since above code reports error
}
Can anyone tell me what I'm doing wrong? Is there a step that I'm missing here?
EditThe function L_TwainSelectSource() make no effort to make sure the supplied source is valid and does not even return an error. As result, if you set the selected source to a garbage name, it will act as if it accepted it. From that point on if you try to Get/Set anything or try to acquire an image, every function returns -84.
Thank you
Sam
To test your code, I put the main window’s handle in a global variable:
globalhWnd = hWnd;
And modified your function to use that handle like this:
void OnThreadExecute(void *)
{
...
appData.hWnd = globalhWnd; // hWnd is valid handle of my main window
...
}
Then created a thread for it from the main program like this:
globalhWnd = hWnd;
_beginthread(OnThreadExecute, 0, 0);
I tried this with 5 different Twain sources: 2 virtual and 3 physical scanners (one of them an old Epson). All 5 drivers returned SUCCESS when calling L_TwainStartCapsNeg() from within the thread.
Two possibilities come to mind:
The problem might be caused by something else in your code other than the thread function.
Or the problem could be specific to your Twain driver.
To rule out the first possibility, I suggest creating a small test project that only creates a similar thread and does nothing else and trying it with different scanners. If it causes the same problem with all scanners, send that test project (not your full application) to support#leadtools.com and our support engineers with test it for you.
If the problem only happens with a specific Twain driver, try contacting the scanner’s vendor to see if they have an updated driver.
I have a dilemma. My GUI-based C++ app requires to implement drag-and-drop functionality. At the same time, I'm converting this Win32 app to UWP to submit to Windows Store. But there's one issue:
To implement drag-and-drop I need to call these two methods:
OleInitialize(NULL);
//...
HRESULT hr = RegisterDragDrop(hMainWnd, pDropTarget);
and to init WinRT stuff to work with Windows Store, I need to call:
HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED);
Unfortunately OleInitialize initialized COM as single-thread apartment and RoInitialize requires multi-threaded model, while RegisterDragDrop cannot function without calling OleInitialize.
Any idea how to resolve it? (apart from moving RoInitialize and all WinRT code into a worker thread, that will complicate things.)
Raymond Chen in his usual condescending way is pretty good at criticizing things but offers no fix to an existing problem. I'm posting this mostly for later self-reference and in case someone else stumbles upon the same issue as well. I just spent several days trying to resolve this bug, so maybe it will save time for someone else.
Problem
First off, this is a native Win32 code (no .NET or C++/CX.) It is C++ with a sprinkle of WRL for easier handling of WinRT/COM stuff.
In my case I have a Win32 GUI app that implements drag-and-drop of files into its main window. So to init it, one needs to do this from the main thread, right when the app starts:
OleInitialize(NULL);
//...
HRESULT hr = RegisterDragDrop(hMainWnd, pDropTarget);
The OleInitialize call above will initialize COM for the main thread to use single-thread apartment, which is required for RegisterDragDrop to succeed. Without it, the drag-and-drop function will not work.
Then, say you decide to convert this Win32 app to UWP using Microsoft's Project Centennial converter for inclusion into Windows 10 store.
When the app is converted and listed in the store under their trial-license scheme, you will employ the following logic to check if the user has a trial or an activated (i.e. purchased) copy of the app. You'll begin it as such:
//Init COM for WinRT
RoInitialize(RO_INIT_MULTITHREADED);
ComPtr<IStoreContextStatics> pStoreContextStatics;
if(SUCCEEDED(RoGetActivationFactory(
HStringReference(L"Windows.Services.Store.StoreContext").Get(),
__uuidof(pStoreContextStatics),
&pStoreContextStatics)) &&
pStoreContextStatics)
{
//Get store context for the app
ComPtr<IStoreContext> pStoreContext;
if(SUCCEEDED(pStoreContextStatics->GetDefault(&pStoreContext)) &&
pStoreContext)
{
//Got store context
//....
}
}
and then if you need to know trial vs. activated status of the app, using this logic, you'd call:
ComPtr<IAsyncOperation<StoreAppLicense*>> p_opAppLic;
if(SUCCEEDED(pStoreContext->GetAppLicenseAsync(p_opAppLic)) &&
p_opAppLic)
{
ComPtr<IAsyncOperationCompletedHandler<StoreAppLicense*>> p_onAppLicCallback =
Callback<Implements<RuntimeClassFlags<ClassicCom>, IAsyncOperationCompletedHandler<StoreAppLicense*>, FtmBase>>(
[](IAsyncOperation<StoreAppLicense*>* pOp, AsyncStatus status)
{
if (status == AsyncStatus::Completed)
{
ComPtr<IStoreAppLicense> pAppLicResult;
if(SUCCEEDED(pOp->GetResults(&pAppLicResult)) &&
pAppLicResult)
{
BYTE nActive = -1;
BYTE nTrial = -1;
pAppLicResult->get_IsActive(&nActive);
pAppLicResult->get_IsTrial(&nTrial);
//Get app's store ID with SKU
HString strStoreId;
pAppLicResult->get_SkuStoreId(strStoreId.GetAddressOf());
if(nActive == 1 &&
nTrial == 0)
{
//Activated, or purchased copy
}
else if(nActive == 1 &&
nTrial == 1)
{
//Trial copy
}
else
{
//Error -- store returned some gibberish
}
}
}
return S_OK;
});
if(SUCCEEDED(p_opAppLic->put_Completed(p_onAppLicCallback.Get())))
{
//Success initiating async call
}
}
So, if you do all this, your UWP-converted app will behave in a very strange way. Here's an example. Say a user purchases a license for the app thru Windows Store. In turn your app logic calls the code above to see if the app is activated, but what you get back is nActive=0 and nTrial=1. Then if you check strStoreId it will be your app store ID but without the SKU. WTF!?
I know, it's really confusing. As an aside, let me explain. When you first list your app in a Windows Store it will be assigned a Store ID. Something like: ABCDEFG12345. Then if you submit any follow-up update(s) to the first version of the same app, they will add a SKU number to it, that will make the whole app ID change to ABCDEFG12345/0010, then ABCDEFG12345/0011 for the next update, and so on.
Well, the WinRT code above would return my app store ID as ABCDEFG12345 without any SKU attached to it. Which was wrong, since it was a third or so update to the first version of the app. And thus any additional attributes for that app store ID were also wrong.
So that was the issue that I was faced with...
Cause
All the headache that I described above was caused by my omission to check the result code returned from the first RoInitialize call. I would be able to catch the problem much faster if I did this:
//Init COM for WinRT
if(FAILED(RoInitialize(RO_INIT_MULTITHREADED)))
{
//WinRT COM initialization failed
//Go scratch your head why....
}
In this case RoInitialize will fail with error code RPC_E_CHANGED_MODE. The documentation for it is as helpful as Windows Help (F1) option:
A previous call to RoInitialize specified the concurrency model for
this thread as multithread apartment (MTA). This could also indicate
that a change from neutral-threaded apartment to single-threaded
apartment has occurred.
What previous call? The only parameter anyone can call it with is RO_INIT_MULTITHREADED.
So I started digging deeper and by the process of elimination found that the OleInitialize call earlier was the reason why RoInitialize failed and caused the cascade of events that I described above.
Thus I was at the point of asking the question here.
Note on the side, that the bug ridden WinRT library (ref1, ref2, ref3, ref4, ref5) gave me no indications of a problem in all the calls following RoInitialize and somewhere internally silently failed to retrieve the app's SKU because of a single-thread apartment COM initialization.
Hack/Workaround
As was suggested by RbMm in the comments above, doing the following will work, but is a totally undocumented behavior:
if(SUCCEEDED(OleInitialize(0))
{
CoUninitialize();
}
CoInitializeEx(NULL, COINIT_MULTITHREADED);
So if you don't want your app to start crashing for no apparent reason, I would not use it.
Solution
My solution that I went with was to move all the WinRT COM stuff (code I listed above: 2nd and 3rd code segments) into a separate worker thread. It will work fine from there. The issue is marshalling calls between your main thread and this worker thread. It is doable, but requires some work, i.e. using mutexes and events for synchronized access, etc.
So if anyone finds an easier fix for this, please post your solution. I'll mark it as the answer.
solution to the IDsObjPicker crashes I mentioned in the comment ealier, quick code I wrote just now.
Use code below as:
TDsObjPicker lv_PickInfo;
memset(&lv_PickInfo, 0, sizeof(TDsObjPicker));
Sec_InitDsObjPicker(&lv_PickInfo, &lv_InitInfo);
Sec_InvokeDsObjPicker(&lv_PickInfo, 0, &lv_oData);
Solution is to run the dialog in another thread and init the thread without the Ole+Com combination:
// command codes
#define DSOPCMD_EXITTHREAD 1
#define DSOPCMD_INITIALIZE 2
#define DSOPCMD_INVOKE 3
// parameters of object picker via thread
typedef struct tagDsObjPicker
{
// thread handle
HANDLE hThread;
// events
HANDLE hCmdEvt;
HANDLE hRdyEvt;
// commands
UINT CmdCode;
HRESULT hResult;
// command parameters - DSOPCMD_INITIALIZE
DSOP_INIT_INFO *InitInfo;
// command parameters - DSOPCMD_INVOKE
HWND hWnd;
IDataObject **oData;
//
} TDsObjPicker;
DWORD CALLBACK _Sec_DsObjPickerThread(VOID *in_Param)
{
/* locals */
HRESULT lv_hCreateResult;
HRESULT lv_hResult;
TDsObjPicker *lv_PickInfo;
IDsObjectPicker *lv_oPicker;
// get info structure
lv_PickInfo = (TDsObjPicker*)in_Param;
// init COM
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// preclear object pointer
lv_oPicker = NULL;
// create instance of picker
lv_hCreateResult = CoCreateInstance(
CLSID_DsObjectPicker, NULL, CLSCTX_INPROC_SERVER,
IID_IDsObjectPicker, (VOID**)&lv_oPicker);
// while thread is not aborted
while (lv_PickInfo->CmdCode != DSOPCMD_EXITTHREAD)
{
// wait for command event
if (WaitForSingleObject(lv_PickInfo->hCmdEvt, INFINITE) == 0)
{
// what command?
switch (lv_PickInfo->CmdCode)
{
// call init
case DSOPCMD_INITIALIZE:
{
// call object
if (lv_hCreateResult)
lv_hResult = lv_hCreateResult;
else
lv_hResult = lv_oPicker->Initialize(lv_PickInfo->InitInfo);
// done
break;
}
// call invoke
case DSOPCMD_INVOKE:
{
// call object
if (lv_hCreateResult)
lv_hResult = lv_hCreateResult;
else
lv_hResult = lv_oPicker->InvokeDialog(lv_PickInfo->hWnd, lv_PickInfo->oData);
// done
break;
}
// other command codes
default:
lv_hResult = E_FAIL;
break;
}
// store result
lv_PickInfo->hResult = lv_hResult;
// notify caller
SetEvent(lv_PickInfo->hRdyEvt);
}
}
// destroy the picker object
if (lv_oPicker)
lv_oPicker->Release();
// cleanup COM
CoUninitialize();
// leave the thread
return 0;
}
VOID Sec_DoneDsObjPicker(TDsObjPicker *in_PickInfo)
{
// is thread created?
if (in_PickInfo->hThread)
{
// set command code
in_PickInfo->CmdCode = DSOPCMD_EXITTHREAD;
// trigger the thread to process the code
SetEvent(in_PickInfo->hCmdEvt);
// wait for thread to finish
WaitForSingleObject(in_PickInfo->hThread, INFINITE);
// close thread handle
CloseHandle(in_PickInfo->hThread);
}
// close event handles
if (in_PickInfo->hCmdEvt) CloseHandle(in_PickInfo->hCmdEvt);
if (in_PickInfo->hRdyEvt) CloseHandle(in_PickInfo->hRdyEvt);
// clear
memset(in_PickInfo, 0, sizeof(TDsObjPicker));
}
HRESULT Sec_InitDsObjPicker(TDsObjPicker *in_PickInfo, DSOP_INIT_INFO *in_InitInfo)
{
/* locals */
DWORD lv_TID;
// thread not yet created?
if (!in_PickInfo->hThread)
{
// create events
in_PickInfo->hCmdEvt = CreateEvent(0,0,0,0);
in_PickInfo->hRdyEvt = CreateEvent(0,0,0,0);
// if ok
if (in_PickInfo->hCmdEvt && in_PickInfo->hRdyEvt)
{
// create the thread
in_PickInfo->hThread = CreateThread(
0, 0, _Sec_DsObjPickerThread, in_PickInfo, 0, &lv_TID);
}
// failed?
if (!in_PickInfo->hThread)
{
// cleanup
Sec_DoneDsObjPicker(in_PickInfo);
// return with error
return E_OUTOFMEMORY;
}
}
// store parameter
in_PickInfo->InitInfo = in_InitInfo;
// set command code
in_PickInfo->CmdCode = DSOPCMD_INITIALIZE;
// trigger the thread to process the code
SetEvent(in_PickInfo->hCmdEvt);
// wait for result
WaitForSingleObject(in_PickInfo->hRdyEvt, INFINITE);
// return the result
return in_PickInfo->hResult;
}
HRESULT Sec_InvokeDsObjPicker(TDsObjPicker *in_PickInfo, HWND in_hWnd, IDataObject **out_oData)
{
/* locals */
MSG lv_Msg;
// thread not yet created?
if (!in_PickInfo->hThread)
return E_FAIL;
// store parameters
in_PickInfo->hWnd = in_hWnd;
in_PickInfo->oData = out_oData;
// set command
in_PickInfo->CmdCode = DSOPCMD_INVOKE;
// trigger the thread
SetEvent(in_PickInfo->hCmdEvt);
// process messages of this thread while picker runs in other thread until event
while (MsgWaitForMultipleObjects(1, &in_PickInfo->hRdyEvt, 0, INFINITE, QS_ALLINPUT) != 0)
{
// get next message
while (PeekMessage(&lv_Msg, 0,0,0, PM_REMOVE))
{
// translate/dispatch the message
TranslateMessage(&lv_Msg);
DispatchMessage(&lv_Msg);
}
}
// return the result
return in_PickInfo->hResult;
}
You asked why calling OleInitialize() first, followed by CoUnintialize and then reinit COM via CoInitializeEx works and is safe, look at the code of the rewritten OLE server in WINE, https://github.com/wine-mirror/wine/blob/master/dlls/ole32/ole2.c it comes pretty close to the "real thing". The OleInitialize calls CoInitializeEx itself with COINIT_APARTMENTTHREADED and fails before doing the OLE-specific initializations upon a fail of CoInitializeEx. There is no reason to fail as the OLE code can run as well in MULTITHREADED mode. Remember MULTITHREADED means the caller must take care of synchronisation/locking while with APARTMENTTHREADED the COM libray will handle it for the code. So if you make sure you do not call the OLE code like dragdrop and clipboard at the same time from multiple threads then there is no problem. Keeping all UI in the main thread will do that. As you should already write multithreaded-aware code yourself using the requested MULTITHREADED mode.
I have the problem with directshow filters/drivers which lock the process when COM is initialized with APARTMENTTHREADED even when directshow is called from a thread with THREADED while the main UI thread runs in APARTMENTTHREADED.
Uninitializing COM after initializing OLE, then re-inititializing COM with MULTITHREAED during startup in the main UI thread makes you bypass the failure in OleInitialize. It is the best solution to make sure all runs well.
I've been trying to debug a memory leak in my program, and have narrowed it down to the WinHttp comms. I've been able to reproduce the problem in the following test code:
#include <windows.h>
#include "winhttp.h"
void main() {
while (1) {
HINTERNET send_session = WinHttpOpen(L"asdf", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
WinHttpCloseHandle(send_session);
}
}
After running this for a few seconds, the program is already using over 20MB memory. Why is it doing this? The API states that you need to call WinHttpCloseHandle after the handle is no longer needed - I'm doing that.
I'm compiling using mingw32 on Arch Linux, and running the code on Windows 7.
If you modify the code a bit, you will see what is happening.
int _tmain(int argc, _TCHAR* argv[])
{
for(INT n = 0; n < 1000000; n++)
{
if(!(n % 10000))
_tprintf(_T("%d\n"), n / 10000);
HINTERNET send_session = WinHttpOpen(L"asdf", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
WinHttpCloseHandle(send_session);
}
_tprintf(_T("Done\n"));
Sleep(INFINITE);
return 0;
}
Along with creating/closing one million of sessions you will see counters climbing up. The API creates background resources including threads and does not release them immediately, so they keep piling up.
However as soon as you stop creating new sessions and let it have a few seconds of idle time - you will see all that memory is released.
The bottom line is that in real code you should not create a separate session for every small thing. One session might be hosting multiple connections and requests.
How can you find out if an executable is running on Windows given the process name, e.g. program.exe?
The C++ standard library has no such support. You need an operating system API to do this. If this is Windows then you'd use CreateToolhelp32Snapshot(), followed by Process32First and Process32Next to iterate the running processes. Beware of the inevitable race condition, the process could have exited by the time you found it.
I just created one using Hans suggestion. Works like a champ!
Oh and here is the basic code.
Please you will have to add CStrings sAppPath and sAppName.
StartProcess is a small function that uses CreateProcess and returns the PID(not used here). You will need to replace it.
This is not a complete program, just the code to find if the program is running using Hans suggestion. A fun test is to set the path to c:\windows\ and the app to notepad.exe and set it for 10 seconds.
#include <tlhelp32.h>
PROCESSENTRY32 pe32 = {0};
HANDLE hSnap;
int iDone;
int iTime = 60;
bool bProcessFound;
while(true) // go forever
{
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
pe32.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnap,&pe32); // Can throw away, never an actual app
bProcessFound = false; //init values
iDone = 1;
while(iDone) // go until out of Processes
{
iDone = Process32Next(hSnap,&pe32);
if (strcmp(pe32.szExeFile,sAppName) == 0) // Did we find our process?
{
bProcessFound = true;
iDone = 0;
}
}
if(!bProcessFound) // if we didn't find it running...
{
startProcess(sAppPath+sAppName,""); // start it
}
Sleep(iTime*1000); // delay x amount of seconds.
}
Assumptions: since you mention '.exe', you want this for some flavor of Windows. You want to write a program in C++ to determine whether a program with a particular executable name is running (regardless of the language used to implement the target program).
Enumerate the running processes using either the Toolhelp API or the process status API. Compare the name of the executable for each running process to the one you're looking for (and be aware that there may be more than one process with that executable name).
hProcessInfo = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );
do{
if(strcmp(pe32.szExeFile,"process.exe") == 0)
{
processfound = true;
break;
}
}while( Process32Next( hProcessSnap, &pe32 ) );
If you don't want to get process detail from code just press Ctrl+Alt+Del and check process list.
I am working on getting the version of the Software which is installed on the Computer. I have implemented the logic for reading the Uninstall hive of registry, but i have observed that some of the software are not having version entries in the Uninstall hive of the registry. But i want to show the version of those softwares also.
Can some one help me out in this regard?
Supplying a software version to the registry of Windows is voluntary. If the developer of the software you're looking at chose to not display the version there or was simply unaware of such possibility, I am unable to point you to any other location he would choose to use or be aware of. In fact, the software might not even have a version number/name.
Ask yourself this: Where else is the Version detail of the software available if not in the registry? If it is available somewhere else other than registry, ask us if you could get that detail using C++. I guess this would be a better approach to solve your issue.
Added the information below since OP is looking for file version
See if the below code could help you.
CString GetFileVersionInfo(CString strFile, CString strProperty)
{
int rc;
UINT nLen;
DWORD nSize;
DWORD dwHandle = 0;
CString strBuffer;
CString strValue;
CString strBlock;
void *lpPropertyBuffer;
struct LANGANDCODEPAGE
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
nSize = GetFileVersionInfoSize(strFile.GetBuffer(strFile.GetLength()), &dwHandle);
::GetFileVersionInfo(strFile.GetBuffer(strFile.GetLength()), 0, nSize, strBuffer.GetBuffer(nSize));
// Read the list of languages and code pages.
if (VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &nLen))
{
strBlock.Format("\\StringFileInfo\\%04x%04x\\%s",
lpTranslate->wLanguage,
lpTranslate->wCodePage,
strProperty);
rc = VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), strBlock.GetBuffer(nSize), &lpPropertyBuffer, &nLen);
if (rc != 0 && nLen > 0)
{
strncpy(strValue.GetBuffer(nLen + 1), (char *) lpPropertyBuffer, nLen);
strValue.ReleaseBuffer(nLen);
}
}
return strValue;
}
user version.lib while linking and you might need winver.h for compilation. You can call the function like this
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
AfxMessageBox(GetFileVersionInfo("shell32.dll", "ProductVersion"));
}
return nRetCode;
}
I'd say look at the file version information. And you might find this article useful on how the Add/Remove Programs dialog gets its information.
If the software developers chose not to add version information into Uninstall information, then there's no reliable way to get it.
You can try to find where application is installed. But even if you have the path, the application can consist of several .exe files which can have different versions and product names. If you add DLLs into the candidate list for getting version information, your results become even less predictable.