Memory leaks in Webbrowser (COM) - c++

This snippet of code generate huge memory leaks. Could you help me to find out where it happens?
This code does the following thing:
1) It gets IHTMLDocuments2 interface
2) Requests for all tags collection
3) Iterates over whole collection
4) and adds some of the tags' data to list
IDispatch* pDisp;
pDisp = this->GetHtmlDocument();
if (pDisp != NULL )
{
IHTMLDocument2* pHTMLDocument2;
HRESULT hr;
hr = pDisp->QueryInterface( IID_IHTMLDocument2,(void**)&pHTMLDocument2 );
if (hr == S_OK)
{
// I know that I could use IHTMLDocument3 interface to get collection by ID
// but it didn't worked and returned NULL on each call.
IHTMLElementCollection* pColl = NULL;
// get all tags
hr = pHTMLDocument2->get_all( &pColl );
if (hr == S_OK && pColl != NULL)
{
LONG celem;
hr = pColl->get_length( &celem );
if ( hr == S_OK )
{
//iterate through all tags
// if I iterate this block of code in cycle, it
// uses memory available upto 2GBs and then
// app crashes
for ( int i=0; i< celem; i++ )
{
VARIANT varIndex;
varIndex.vt = VT_UINT;
varIndex.lVal = i;
VARIANT var2;
VariantInit( &var2 );
IDispatch* pElemDisp = NULL;
hr = pColl->item( varIndex, var2, &pElemDisp );
if ( hr == S_OK && pElemDisp != NULL)
{
IHTMLElement* pElem;
hr = pElemDisp->QueryInterface(IID_IHTMLElement,(void **)&pElem);
if ( hr == S_OK)
{
// check INPUT tags only
BSTR tagNameStr = L"";
pElem->get_tagName(&tagNameStr);
CString tagname(tagNameStr);
SysFreeString(tagNameStr);
tagname.MakeLower();
if (tagname != "input")
{
continue;
}
//get ID attribute
BSTR bstr = L"";
pElem->get_id(&bstr);
CString idStr(bstr);
SysFreeString(bstr);
if (RequiredTag(pElem))
{
AddTagToList(pElem);
}
//release all objects
pElem->Release();
}
pElemDisp->Release();
}
}
}
// I looked over this code snippet many times and couldn't find what I'm missing here...
pColl->Release();
}
pHTMLDocument2->Release();
}
pDisp->Release();
}

Inside your loop, for each retreived element that does not have a tagname of "input" (which will be most elements), you are not calling pElem->Release() when calling continue, so you are leaking them:
if (tagname != "input")
{
pElem->Release(); // <-- add this
continue;
}
With that said, you should re-write your code to use ATL's smart pointer classes (CComPtr, CComQIPtr, CComBSTR, etc) to manage the memory for you so you do not have to manually release everything yourself anymore, eg:
CComPtr<IDispatch> pDisp;
pDisp.Attach(this->GetHtmlDocument());
if (pDisp.p != NULL)
{
CComQIPtr<IHTMLDocument2> pHTMLDocument2(pDisp);
if (pHTMLDocument2.p != NULL)
{
CComPtr<IHTMLElementCollection> pColl;
pHTMLDocument2->get_all(&pColl);
if (pColl.p != NULL)
{
LONG celem;
if (SUCCEEDED(pColl->get_length(&celem)))
{
for (LONG i = 0; i < celem; ++i)
{
VARIANT varIndex;
varIndex.vt = VT_UINT;
varIndex.lVal = i;
VARIANT var2;
VariantInit( &var2 );
CComPtr<IDispatch> pElemDisp;
pColl->item( varIndex, var2, &pElemDisp );
if (pElemDisp.p != NULL)
{
CComQIPtr<IHTMLElement> pElem(pElemDisp);
if (pElem.p != NULL)
{
CComBSTR tagNameStr;
pElem->get_tagName(&tagNameStr);
if (lstrcmpiW(tagNameStr.m_str, L"input") != 0)
continue;
CComBSTR idStr;
pElem->get_id(&idStr);
if (RequiredTag(pElem))
AddTagToList(pElem);
}
}
}
}
}
}
}

Related

WriteFile with an overlapped occasionally gives me ERROR_INVALID_HANDLE. Am I doing memset correct?

I am using WriteFile to write to a file and I am getting an error of ERROR_INVALID_HANDLE sometimes. I read that this could be because the value of HANDLE in Overlapped is invalid.
But I am just having a hard time figuring this out. I wonder if something is going out of scope. Would appreciate it if someone can take a look. I can always add more code here
const LPOVERLAPPED lpOverlapped = GetOverlapped(true, hFile, ulBufSize, &ullFileOffset,volumeName);
if (lpOverlapped == nullptr)
{
CloseHandle(hFile);
return false;
}
if (!WriteFile(hFile,(const PVOID)(((UINT64) s_pMemoryBuffer[volumeName]) + iBufferOffset),ulBufSize,&dwBytesWritten,lpOverlapped))
{
DWORD errCode = GetLastError(); //Error here
//Why do I get an error code 6 here every now and then
}
Now this is the method that returns the overlapped structure
LPOVERLAPPED foo::GetOverlapped(bool useOverlappedIo, HANDLE hFile, UINT32 ulBufSize, UINT64* ullFileOffset,const std::string& volumeName)
{
if (useOverlappedIo)
{
while (true)
{
int index = 0;
while (index < cMaxOverlappedIOS)
{
if (!OverlappedInUse[volumeName][index])
{
OverlappedInUse[volumeName][index] = true;
LPOVERLAPPED overlapped = &(OverlappedArray[volumeName][index]);
if (overlapped->hEvent == nullptr) // Need Event
{
overlapped->hEvent = CreateEvent(
nullptr,
TRUE,
TRUE,
nullptr);
if (overlapped->hEvent == nullptr)
{
printf("Error creating event (error code: %u)\n", GetLastError());
return nullptr;
}
}
overlapped->Offset = (UINT32)(*ullFileOffset & 0xFFFFFFFF); // Low order 32 bits
overlapped->OffsetHigh = (UINT32)(*ullFileOffset >> 32); // High order 32 bits
*ullFileOffset += ulBufSize; // Update pointer to next record
return overlapped;
}
// Else Keep looking
index++;
}
// None available, wait for at least one to free up
if (WaitForPendingIOs(hFile, FALSE,volumeName) != ERROR_SUCCESS)
{
return nullptr;
}
} // Now start loop over again
}
else
{
return nullptr;
}
}
This is how I am initializing the array before this code gets called
for(auto vol : readVolumes)
{
OVERLAPPED* oarray = new OVERLAPPED[cMaxOverlappedIOS];
memset(oarray, 0, sizeof(oarray));
OverlappedArray[vol] = oarray;
bool* boolinuse = new bool[cMaxOverlappedIOS]{false};
OverlappedInUse[vol] = boolinuse;
s_pMemoryBuffer[vol] = nullptr;
s_uDataBufferSize[vol] = 0;
}
Any suggestions on why I would get that error ?

memory leak in c++ console program calling Windows APIs

I have copied some code I found on-line here. I have successfully run it but if I run it in a loop, there is a bad memory leak. I program mostly in C#, and this example goes way over my head. Could somebody point me in the right direction to fixing the memory leak? Here is my C++ console application in it's entirety. Any help is appreciated. Thanks.
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include "Netlistmgr.h"
bool checkForCaptivePortalMode()
{
bool fCaptivePortalDetected = false;
// Initialize COM.
if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
// Declare a pointer to INetworkListManager
INetworkListManager* pNetworkListManager;
// Create instance of the CLSID_NetworkListManger COM object
if (SUCCEEDED(CoCreateInstance(CLSID_NetworkListManager, NULL,
CLSCTX_ALL, IID_INetworkListManager,
(LPVOID*)&pNetworkListManager)))
{
// Declare pointer to an IEnumNetworkConnections
IEnumNetworks* pEnum;
// Call to GetNetworks from INetworkListManager interface
if (SUCCEEDED(pNetworkListManager->GetNetworks
(NLM_ENUM_NETWORK_CONNECTED, &pEnum)) && pEnum != NULL)
{
INetwork *pINetwork;
HRESULT hr = pEnum->Next(1, &pINetwork, nullptr);
while (hr == S_OK)
{
if (pINetwork != NULL)
{
IPropertyBag *pNetworkPropertyBag;
HRESULT hrQueryInterface = pINetwork->QueryInterface
(IID_IPropertyBag, (LPVOID*)&pNetworkPropertyBag);
if (SUCCEEDED(hrQueryInterface) && pNetworkPropertyBag != nullptr)
{
NLM_CONNECTIVITY networkConnectivity;
VARIANT variantConnectivity;
if (SUCCEEDED(pINetwork->GetConnectivity(&networkConnectivity)))
{
if ((networkConnectivity &
NLM_CONNECTIVITY_IPV4_INTERNET) == NLM_CONNECTIVITY_IPV4_INTERNET)
{
VariantInit(&variantConnectivity);
if (SUCCEEDED(pNetworkPropertyBag->Read
(NA_InternetConnectivityV4, &variantConnectivity, nullptr))
&& (V_UINT(&variantConnectivity) &
NLM_INTERNET_CONNECTIVITY_WEBHIJACK) ==
NLM_INTERNET_CONNECTIVITY_WEBHIJACK)
{
fCaptivePortalDetected = true;
}
auto t = V_UINT(&variantConnectivity);
VariantClear(&variantConnectivity);
}
if (!fCaptivePortalDetected && (networkConnectivity
& NLM_CONNECTIVITY_IPV6_INTERNET) == NLM_CONNECTIVITY_IPV6_INTERNET)
{
VariantInit(&variantConnectivity);
if (SUCCEEDED(pNetworkPropertyBag->Read(NA_InternetConnectivityV6,
&variantConnectivity, nullptr)) &&
(V_UINT(&variantConnectivity) &
NLM_INTERNET_CONNECTIVITY_WEBHIJACK) ==
NLM_INTERNET_CONNECTIVITY_WEBHIJACK)
{
fCaptivePortalDetected = true;
}
VariantClear(&variantConnectivity);
}
}
}
pINetwork->Release();
}
if (fCaptivePortalDetected)
break;
hr = hr = pEnum->Next(1, &pINetwork, nullptr);
}
}
}
}
// Uninitialize COM.
// (This should be called on application shutdown.)
CoUninitialize();
return fCaptivePortalDetected;
}
int main()
{
for (;;)
{
bool check = checkForCaptivePortalMode();
std::cout << "\n" << check;
//char c = std::getchar();
}
return 0;
}
For one thing you never call pNetworkListManager->Release(). More generally, in C++ if you are not using smart pointers you must make sure each and every resource allocation has a matching deallocation. It would be easier and probably make your code simpler if you used smart pointers throughout.

VisualGestureBuilder API not working when more than one player

I'm using the VisualGestureBuilder C++ API to track three simple discrete gestures.
When there is only one player in front of the Kinect, it works great.
But as soon as a second player arrives, the gesture analysis seems to go crazy, and for the two players, I get erratic but smooth results, as if the data for the two players and for the three gestures were mixed together, and random data was added...
And while the VGB API is going wrong, I still get valid body data (I'm drawing skeletons at the same time)
I discovered that this phenomenon was triggered as soon as I have more than one IVisualGestureBuilderFrameReader in the not-paused state.
Here are some portions of my code :
class GESTURES_STREAM
{
friend class MY_KINECT;
private:
struct SLOT
{
IVisualGestureBuilderDatabase* database = nullptr;
IGesture** gestures = nullptr;
UINT gestures_count = 0;
IVisualGestureBuilderFrameSource* source = nullptr;
IVisualGestureBuilderFrameReader* reader = nullptr;
IVisualGestureBuilderFrame* frame = nullptr;
};
SLOT slots[BODY_COUNT];
public:
GESTURES_RESULTS gestures_results[BODY_COUNT];
};
GESTURES_STREAM gestures_stream;
//----------------------------------------------------------------------------------------------------
void MY_KINECT::start_gestures_stream(wstring vgb_file)
{
for (int i = 0; i < BODY_COUNT; i++)
{
hr = CreateVisualGestureBuilderDatabaseInstanceFromFile(vgb_file.c_str(), &gestures_stream.slots[i].database);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
gestures_stream.slots[i].gestures_count = 0;
hr = gestures_stream.slots[i].database->get_AvailableGesturesCount(&gestures_stream.slots[i].gestures_count);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
if (gestures_stream.slots[i].gestures_count == 0)
{
MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
}
gestures_stream.slots[i].gestures = new IGesture*[gestures_stream.slots[i].gestures_count];
hr = gestures_stream.slots[i].database->get_AvailableGestures(gestures_stream.slots[i].gestures_count, gestures_stream.slots[i].gestures);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
/////
hr = CreateVisualGestureBuilderFrameSource(kinect_sensor, 0, &gestures_stream.slots[i].source);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
hr = gestures_stream.slots[i].source->AddGestures(gestures_stream.slots[i].gestures_count, gestures_stream.slots[i].gestures);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
hr = gestures_stream.slots[i].source->OpenReader(&gestures_stream.slots[i].reader);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
hr = gestures_stream.slots[i].reader->put_IsPaused(TRUE);
if (FAILED(hr)) MY_UTILITIES::fatal_error("Erreur initialisation Kinect : gestures");
/////
safe_release(&gestures_stream.slots[i].database);
}
}
//----------------------------------------------------------------------------------------------------
void MY_KINECT::update()
{
//...
//...
// handling all the different stream, including body
//...
//...
if (body_stream.data_is_new == true)
{
for (int i = 0; i < BODY_COUNT; i++)
{
if (gestures_stream.slots[i].source != nullptr)
{
BOOLEAN is_tracked;
if (body_stream.bodies[i]->get_IsTracked(&is_tracked) == S_OK)
{
if (is_tracked == TRUE)
{
UINT64 a;
if (body_stream.bodies[i]->get_TrackingId(&a) == S_OK)
{
UINT64 b;
if (gestures_stream.slots[i].source->get_TrackingId(&b) == S_OK)
{
if (a != b)
{
gestures_stream.slots[i].source->put_TrackingId(a);
}
}
}
BOOLEAN paused;
if (gestures_stream.slots[i].reader->get_IsPaused(&paused) == S_OK)
{
if (paused == TRUE)
{
gestures_stream.slots[i].reader->put_IsPaused(FALSE);
}
}
}
else
{
BOOLEAN paused;
if (gestures_stream.slots[i].reader->get_IsPaused(&paused) == S_OK)
{
if (paused == FALSE)
{
gestures_stream.slots[i].reader->put_IsPaused(TRUE);
}
}
}
}
}
if (gestures_stream.slots[i].reader != nullptr)
{
if (gestures_stream.slots[i].reader->CalculateAndAcquireLatestFrame(&gestures_stream.slots[i].frame) == S_OK)
{
BOOLEAN is_valid;
if (gestures_stream.slots[i].frame->get_IsTrackingIdValid(&is_valid) == S_OK)
{
if (is_valid == TRUE)
{
for (int j = 0; j < gestures_stream.slots[i].gestures_count; j++)
{
wchar_t gesture_name[256];
if (gestures_stream.slots[i].gestures[j]->get_Name(sizeof(gesture_name), gesture_name) == S_OK)
{
IDiscreteGestureResult* discrete_result = nullptr;
if (gestures_stream.slots[i].frame->get_DiscreteGestureResult(gestures_stream.slots[i].gestures[j], &discrete_result) == S_OK)
{
BOOLEAN detected;
if (discrete_result->get_Detected(&detected) == S_OK)
{
if (detected == TRUE)
{
float confidence;
if (discrete_result->get_Confidence(&confidence) == S_OK)
{
gestures_stream.gestures_results[i].results[gesture_name] = confidence;
}
}
else
{
gestures_stream.gestures_results[i].results[gesture_name] = 0;
}
}
safe_release(&discrete_result);
}
}
}
}
}
safe_release(&gestures_stream.slots[i].frame);
}
}
}
}
}
//----------------------------------------------------------------------------------------------------

How to get the most current IHTMLDocument2 object from IE DOM

Currently, I use MSAA to get an IHTMLDocument2 object from a IE HWND. However, with some complicated web applications, this IHTMLDocument2 object may contain several IHTMLDocument2 objects, some of them are not belong to the current displaying page, but the previous page.
It seems to me, IE sometimes doesn't refesh its DOM object, but keep adding more IHTMLDocument2 object into its DOM. My question is how can I get the current displaying IHTMLDocument2 object from the DOM object.
Thanks in advance
Update
Hi Remy,
Thanks for your answer.
Yes, you are right, I do use frames to get to other IHTMLDocument2 objects. My understanding is that the IHTMLDocument2 object that I get from a HWND is the top object in its DOM. IE sometimes puts the prevous IHTMLDocument2 objects inside one of the frames as well.
Here is part of my code.
BOOL IESpy::GetHTMLText( CComPtr<IHTMLDocument2> spDoc, int tagNo, int schNo)
{
USES_CONVERSION;
HRESULT hr = NULL;
BOOL res = TRUE;
BOOL doneSearch = FALSE;
// Extract the source code of the document
if (spDoc) {
IHTMLFramesCollection2* pFrames = NULL;
if (hr = (spDoc->get_frames(&pFrames)) == S_OK){
LONG framesCount;
pFrames->get_length(&framesCount);
if (framesCount > 0) {
for( long i=0; i < framesCount; i++) {
VARIANT varIdx;
varIdx.vt=VT_I4;
VARIANT varResult;
varIdx.lVal=i;
VariantInit(&varResult);
hr = pFrames->item(&varIdx, &varResult);
if (SUCCEEDED(hr) && (varResult.vt == VT_DISPATCH)){
CComQIPtr<IHTMLWindow2> pFrameWnd;
CComQIPtr<IHTMLDocument2> pFrameDoc;
CComBSTR description=NULL;
pFrameWnd = varResult.pdispVal;
VariantClear(&varResult);
if (pFrameWnd == 0) {
continue;
}
hr = pFrameWnd->get_document(&pFrameDoc);
if (SUCCEEDED(hr) && pFrameDoc){
GetHTMLText( pFrameDoc, tagNo, schNo );
if ( m_foundText ) {
break;
}
} else if ( hr == E_ACCESSDENIED ) {
CComQIPtr<IWebBrowser2> spBrws = HtmlWindowToHtmlWebBrowser(pFrameWnd);
if ( spBrws != NULL) {
// Get the document object from the IWebBrowser2 object.
CComQIPtr<IDispatch> spDisp;
hr = spBrws->get_Document(&spDisp);
if ( hr == S_OK ) {
pFrameDoc = spDisp;
if ( pFrameDoc ) {
GetHTMLText( pFrameDoc, tagNo, schNo );
if ( m_foundText ) {
break;
}
}
}
}
}
}
}
}
pFrames->Release();
if ( !m_foundText ) {
res = ReadSearchText(spDoc, tagNo, schNo );
doneSearch = TRUE;
}
}
if ( !m_foundText && doneSearch == FALSE ) {
res = ReadSearchText(spDoc, tagNo, schNo );
}
}
return res;
}
BOOL IESpy::ReadSearchText(CComPtr<IHTMLDocument2> spDoc, int tagNo, int schNo )
{
USES_CONVERSION;
HRESULT hr = NULL;
BOOL found = FALSE;
IHTMLElementCollection *pAll;
hr = spDoc->get_all(&pAll);
if (FAILED(hr)) {
return FALSE;
}
long items;
IDispatch *ppvDisp;
IHTMLElement *ppvElement;
pAll->get_length(&items);
std::wstring foundText = L"";
for ( long j = 0; j < items; j++ ) {
VARIANT index;
index.vt = VT_I4;
index.lVal = j;
hr = pAll->item( index, index, &ppvDisp );
if (FAILED(hr)) {
return FALSE;
}
if ( ppvDisp ) {
ppvDisp->QueryInterface(IID_IHTMLElement, (void **)&ppvElement);
if ( ppvElement ) {
CComBSTR bstrTag;
ppvElement->get_tagName(&bstrTag);
wchar_t *wtemp = OLE2W(bstrTag);
if ( wtemp ) {
std::wstring text = ReadSearchText(ppvElement, wtemp, tagNo, schNo, found);
if ( !text.empty() ) {
if ( !foundText.empty() ) {
foundText += concat_string;
}
foundText += text;
}
ppvElement->Release();
if ( found ) {
BOOL stop = FALSE;
for ( size_t i = 0; i < m_tagName[tagNo]->size(); i++ ) {
if ( wcscmp(m_tagName[tagNo]->at(i).c_str(), L"HTML") == 0
|| wcscmp(m_tagName[tagNo]->at(i).c_str(), L"HEAD") == 0
|| wcscmp(m_tagName[tagNo]->at(i).c_str(), L"BODY") == 0 ) {
stop = TRUE;
break;
}
}
if ( stop ) {
break;
}
}
} else {
ppvElement->Release();
}
}
}
}
if ( !foundText.empty() ) {
if ( m_screenCompare ) {
// long timeStamp = GetHPTimeStamp(spDoc);
// m_temp_results[timeStamp] = foundText;
m_temp_results.push_back(foundText);
} else {
m_result += foundText;
m_result += L" ";
m_foundText = TRUE;
}
}
return TRUE;
}
An IHTMLDocument2 cannot contain other IHTMLDocument2 objects (unless they belong to frames on the page), and certainly not from previous pages. How are you determining that exactly? Can you show some code?

Getting actual file name (with proper casing) on Windows

Windows file system is case insensitive. How, given a file/folder name (e.g. "somefile"), I get the actual name of that file/folder (e.g. it should return "SomeFile" if Explorer displays it so)?
Some ways I know, all of which seem quite backwards:
Given the full path, search for each folder on the path (via FindFirstFile). This gives proper cased results of each folder. At the last step, search for the file itself.
Get filename from handle (as in MSDN example). This requires opening a file, creating file mapping, getting it's name, parsing device names etc. Pretty convoluted. And it does not work for folders or zero-size files.
Am I missing some obvious WinAPI call? The simplest ones, like GetActualPathName() or GetFullPathName() return the name using casing that was passed in (e.g. returns "program files" if that was passed in, even if it should be "Program Files").
I'm looking for a native solution (not .NET one).
And hereby I answer my own question, based on original answer from cspirz.
Here's a function that given absolute, relative or network path, will return the path with upper/lower case as it would be displayed on Windows. If some component of the path does not exist, it will return the passed in path from that point.
It is quite involved because it tries to handle network paths and other edge cases. It operates on wide character strings and uses std::wstring. Yes, in theory Unicode TCHAR could be not the same as wchar_t; that is an exercise for the reader :)
std::wstring GetActualPathName( const wchar_t* path )
{
// This is quite involved, but the meat is SHGetFileInfo
const wchar_t kSeparator = L'\\';
// copy input string because we'll be temporary modifying it in place
size_t length = wcslen(path);
wchar_t buffer[MAX_PATH];
memcpy( buffer, path, (length+1) * sizeof(path[0]) );
size_t i = 0;
std::wstring result;
// for network paths (\\server\share\RestOfPath), getting the display
// name mangles it into unusable form (e.g. "\\server\share" turns
// into "share on server (server)"). So detect this case and just skip
// up to two path components
if( length >= 2 && buffer[0] == kSeparator && buffer[1] == kSeparator )
{
int skippedCount = 0;
i = 2; // start after '\\'
while( i < length && skippedCount < 2 )
{
if( buffer[i] == kSeparator )
++skippedCount;
++i;
}
result.append( buffer, i );
}
// for drive names, just add it uppercased
else if( length >= 2 && buffer[1] == L':' )
{
result += towupper(buffer[0]);
result += L':';
if( length >= 3 && buffer[2] == kSeparator )
{
result += kSeparator;
i = 3; // start after drive, colon and separator
}
else
{
i = 2; // start after drive and colon
}
}
size_t lastComponentStart = i;
bool addSeparator = false;
while( i < length )
{
// skip until path separator
while( i < length && buffer[i] != kSeparator )
++i;
if( addSeparator )
result += kSeparator;
// if we found path separator, get real filename of this
// last path name component
bool foundSeparator = (i < length);
buffer[i] = 0;
SHFILEINFOW info;
// nuke the path separator so that we get real name of current path component
info.szDisplayName[0] = 0;
if( SHGetFileInfoW( buffer, 0, &info, sizeof(info), SHGFI_DISPLAYNAME ) )
{
result += info.szDisplayName;
}
else
{
// most likely file does not exist.
// So just append original path name component.
result.append( buffer + lastComponentStart, i - lastComponentStart );
}
// restore path separator that we might have nuked before
if( foundSeparator )
buffer[i] = kSeparator;
++i;
lastComponentStart = i;
addSeparator = true;
}
return result;
}
Again, thanks to cspirz for pointing me to SHGetFileInfo.
Have you tried using SHGetFileInfo?
There is another solution. First call GetShortPathName() and then GetLongPathName(). Guess what character case will be used then? ;-)
Just found that the Scripting.FileSystemObject suggested by #bugmagnet 10 years ago is a treasure. Unlike my old method, it works on Absolute Path, Relative Path, UNC Path and Very Long Path (path longer than MAX_PATH). Shame on me for not testing his method earlier.
For future reference, I would like to present this code which can be compiled in both C and C++ mode. In C++ mode, the code will use STL and ATL. In C mode, you can clearly see how everything is working behind the scene.
#include <Windows.h>
#include <objbase.h>
#include <conio.h> // for _getch()
#ifndef __cplusplus
# include <stdio.h>
#define SafeFree(p, fn) \
if (p) { fn(p); (p) = NULL; }
#define SafeFreeCOM(p) \
if (p) { (p)->lpVtbl->Release(p); (p) = NULL; }
static HRESULT CorrectPathCasing2(
LPCWSTR const pszSrc, LPWSTR *ppszDst)
{
DWORD const clsCtx = CLSCTX_INPROC_SERVER;
LCID const lcid = LOCALE_USER_DEFAULT;
LPCWSTR const pszProgId = L"Scripting.FileSystemObject";
LPCWSTR const pszMethod = L"GetAbsolutePathName";
HRESULT hr = 0;
CLSID clsid = { 0 };
IDispatch *pDisp = NULL;
DISPID dispid = 0;
VARIANT vtSrc = { VT_BSTR };
VARIANT vtDst = { VT_BSTR };
DISPPARAMS params = { 0 };
SIZE_T cbDst = 0;
LPWSTR pszDst = NULL;
// CoCreateInstance<IDispatch>(pszProgId, &pDisp)
hr = CLSIDFromProgID(pszProgId, &clsid);
if (FAILED(hr)) goto eof;
hr = CoCreateInstance(&clsid, NULL, clsCtx,
&IID_IDispatch, (void**)&pDisp);
if (FAILED(hr)) goto eof;
if (!pDisp) {
hr = E_UNEXPECTED; goto eof;
}
// Variant<BSTR> vtSrc(pszSrc), vtDst;
// vtDst = pDisp->InvokeMethod( pDisp->GetIDOfName(pszMethod), vtSrc );
hr = pDisp->lpVtbl->GetIDsOfNames(pDisp, NULL,
(LPOLESTR*)&pszMethod, 1, lcid, &dispid);
if (FAILED(hr)) goto eof;
vtSrc.bstrVal = SysAllocString(pszSrc);
if (!vtSrc.bstrVal) {
hr = E_OUTOFMEMORY; goto eof;
}
params.rgvarg = &vtSrc;
params.cArgs = 1;
hr = pDisp->lpVtbl->Invoke(pDisp, dispid, NULL, lcid,
DISPATCH_METHOD, &params, &vtDst, NULL, NULL);
if (FAILED(hr)) goto eof;
if (!vtDst.bstrVal) {
hr = E_UNEXPECTED; goto eof;
}
// *ppszDst = AllocWStrCopyBStrFrom(vtDst.bstrVal);
cbDst = SysStringByteLen(vtDst.bstrVal);
pszDst = HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, cbDst + sizeof(WCHAR));
if (!pszDst) {
hr = E_OUTOFMEMORY; goto eof;
}
CopyMemory(pszDst, vtDst.bstrVal, cbDst);
*ppszDst = pszDst;
eof:
SafeFree(vtDst.bstrVal, SysFreeString);
SafeFree(vtSrc.bstrVal, SysFreeString);
SafeFreeCOM(pDisp);
return hr;
}
static void Cout(char const *psz)
{
printf("%s", psz);
}
static void CoutErr(HRESULT hr)
{
printf("Error HRESULT 0x%.8X!\n", hr);
}
static void Test(LPCWSTR pszPath)
{
LPWSTR pszRet = NULL;
HRESULT hr = CorrectPathCasing2(pszPath, &pszRet);
if (FAILED(hr)) {
wprintf(L"Input: <%s>\n", pszPath);
CoutErr(hr);
}
else {
wprintf(L"Was: <%s>\nNow: <%s>\n", pszPath, pszRet);
HeapFree(GetProcessHeap(), 0, pszRet);
}
}
#else // Use C++ STL and ATL
# include <iostream>
# include <iomanip>
# include <string>
# include <atlbase.h>
static HRESULT CorrectPathCasing2(
std::wstring const &srcPath,
std::wstring &dstPath)
{
HRESULT hr = 0;
CComPtr<IDispatch> disp;
hr = disp.CoCreateInstance(L"Scripting.FileSystemObject");
if (FAILED(hr)) return hr;
CComVariant src(srcPath.c_str()), dst;
hr = disp.Invoke1(L"GetAbsolutePathName", &src, &dst);
if (FAILED(hr)) return hr;
SIZE_T cch = SysStringLen(dst.bstrVal);
dstPath = std::wstring(dst.bstrVal, cch);
return hr;
}
static void Cout(char const *psz)
{
std::cout << psz;
}
static void CoutErr(HRESULT hr)
{
std::wcout
<< std::hex << std::setfill(L'0') << std::setw(8)
<< "Error HRESULT 0x" << hr << "\n";
}
static void Test(std::wstring const &path)
{
std::wstring output;
HRESULT hr = CorrectPathCasing2(path, output);
if (FAILED(hr)) {
std::wcout << L"Input: <" << path << ">\n";
CoutErr(hr);
}
else {
std::wcout << L"Was: <" << path << ">\n"
<< "Now: <" << output << ">\n";
}
}
#endif
static void TestRoutine(void)
{
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr)) {
Cout("CoInitialize failed!\n");
CoutErr(hr);
return;
}
Cout("\n[ Absolute Path ]\n");
Test(L"c:\\uSers\\RayMai\\docuMENTs");
Test(L"C:\\WINDOWS\\SYSTEM32");
Cout("\n[ Relative Path ]\n");
Test(L".");
Test(L"..");
Test(L"\\");
Cout("\n[ UNC Path ]\n");
Test(L"\\\\VMWARE-HOST\\SHARED FOLDERS\\D\\PROGRAMS INSTALLER");
Cout("\n[ Very Long Path ]\n");
Test(L"\\\\?\\C:\\VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
L"VERYVERYVERYLOOOOOOOONGFOLDERNAME");
Cout("\n!! Worth Nothing Behavior !!\n");
Test(L"");
Test(L"1234notexist");
Test(L"C:\\bad\\PATH");
CoUninitialize();
}
int main(void)
{
TestRoutine();
_getch();
return 0;
}
Screenshot:
Old Answer:
I found that FindFirstFile() will return the proper casing file name (last part of path) in fd.cFileName. If we pass c:\winDOWs\exPLORER.exe as first parameter to FindFirstFile(), the fd.cFileName would be explorer.exe like this:
If we replace the last part of path with fd.cFileName, we will get the last part right; the path would become c:\winDOWs\explorer.exe.
Assuming the path is always absolute path (no change in text length), we can just apply this 'algorithm' to every part of path (except the drive letter part).
Talk is cheap, here is the code:
#include <windows.h>
#include <stdio.h>
/*
c:\windows\windowsupdate.log --> c:\windows\WindowsUpdate.log
*/
static HRESULT MyProcessLastPart(LPTSTR szPath)
{
HRESULT hr = 0;
HANDLE hFind = NULL;
WIN32_FIND_DATA fd = {0};
TCHAR *p = NULL, *q = NULL;
/* thePart = GetCorrectCasingFileName(thePath); */
hFind = FindFirstFile(szPath, &fd);
if (hFind == INVALID_HANDLE_VALUE) {
hr = HRESULT_FROM_WIN32(GetLastError());
hFind = NULL; goto eof;
}
/* thePath = thePath.ReplaceLast(thePart); */
for (p = szPath; *p; ++p);
for (q = fd.cFileName; *q; ++q, --p);
for (q = fd.cFileName; *p = *q; ++p, ++q);
eof:
if (hFind) { FindClose(hFind); }
return hr;
}
/*
Important! 'szPath' should be absolute path only.
MUST NOT SPECIFY relative path or UNC or short file name.
*/
EXTERN_C
HRESULT __stdcall
CorrectPathCasing(
LPTSTR szPath)
{
HRESULT hr = 0;
TCHAR *p = NULL;
if (GetFileAttributes(szPath) == -1) {
hr = HRESULT_FROM_WIN32(GetLastError()); goto eof;
}
for (p = szPath; *p; ++p)
{
if (*p == '\\' || *p == '/')
{
TCHAR slashChar = *p;
if (p[-1] == ':') /* p[-2] is drive letter */
{
p[-2] = toupper(p[-2]);
continue;
}
*p = '\0';
hr = MyProcessLastPart(szPath);
*p = slashChar;
if (FAILED(hr)) goto eof;
}
}
hr = MyProcessLastPart(szPath);
eof:
return hr;
}
int main()
{
TCHAR szPath[] = TEXT("c:\\windows\\EXPLORER.exe");
HRESULT hr = CorrectPathCasing(szPath);
if (SUCCEEDED(hr))
{
MessageBox(NULL, szPath, TEXT("Test"), MB_ICONINFORMATION);
}
return 0;
}
Advantages:
The code works on every version of Windows since Windows 95.
Basic error-handling.
Highest performance possible. FindFirstFile() is very fast, direct buffer manipulation makes it even faster.
Just C and pure WinAPI. Small executable size.
Disadvantages:
Only absolute path is supported, other are undefined behavior.
Not sure if it is relying on undocumented behavior.
The code might be too raw too much DIY for some people. Might get you flamed.
Reason behind the code style:
I use goto for error-handling because I was used to it (goto is very handy for error-handling in C). I use for loop to perform functions like strcpy and strchr on-the-fly because I want to be certain what was actually executed.
Okay, this is VBScript, but even so I'd suggest using the Scripting.FileSystemObject object
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")
Dim f
Set f = fso.GetFile("C:\testfile.dat") 'actually named "testFILE.dAt"
wscript.echo f.Name
The response I get is from this snippet is
testFILE.dAt
Hope that at least points you in the right direction.
FindFirstFileNameW will work with a few drawbacks:
it doesn't work on UNC paths
it strips the drive letter so you need to add it back
if there are more than one hard link to your file you need to identify the right one
After a quick test, GetLongPathName() does what you want.