I'm learning Shell Extensions from an old but nevertheless outstanding "The Complete Idiot's Guide" series of articles by Michael Dunn. The second part describes an extension that allows to register and unregister COM Servers
via context menu. There's a CProgressDlg
class which is responsible for iterating through the list of dlls that implement the required procedures, trying to load them, call the required procedure, save the operation result as a message to be later shown to the user. Following is the excerpt from the code:
typedef std::list< std::basic_string<TCHAR> > string_list;
class CProgressDlg : public CDialogImpl<CProgressDlg>
{
protected:
bool m_bStopSign;
HWND m_hwndList;
const string_list* m_pFileList;
string_list m_lsStatusMessages; // list of status messages for the dialog
const CMINVOKECOMMANDINFO* m_pCmdInfo;
void DoWork();
}
void CProgressDlg::DoWork()
{
USES_CONVERSION;
HRESULT(STDAPICALLTYPE * pfn)();
HINSTANCE hinst;
TCHAR szMsg[512];
LPCSTR pszFnName;
WORD wCmd;
LVITEM rItem;
int nIndex = 0;
bool bSuccess;
// work area - this used to be outside DoWork() so we could use try/finally in that function.
std::basic_string<TCHAR> strMsg;
wCmd = LOWORD(m_pCmdInfo->lpVerb);
// We only support 2 commands, so check the value passed in lpVerb.
if (0 != wCmd && 1 != wCmd)
{
ATLASSERT(0); // should never get here
return;
}
// Determine which function we'll be calling. Note that these strings are
// not enclosed in the _T macro, since GetProcAddress() only takes an
// ANSI string for the function name.
pszFnName = wCmd ? "DllUnregisterServer" : "DllRegisterServer";
for (string_list::const_iterator it = m_pFileList->begin();
it != m_pFileList->end(); it++)
{
bSuccess = false;
hinst = NULL;
*szMsg = '\0';
// We will print a status message into szMsg, which will eventually
// be stored in the LPARAM of a listview control item.
__try
{
// Try to load the next file.
hinst = LoadLibraryEx(it->c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
// If it failed, construct a friendly error message.
if (NULL == hinst)
{
void* pvMsgBuf = NULL;
DWORD dwLastErr = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dwLastErr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pvMsgBuf, 0, NULL);
wsprintf(szMsg, _T("LoadLibraryEx failed on this module.\nError 0x%08lX (%s)"),
dwLastErr, pvMsgBuf ? pvMsgBuf : _T("No description available"));
LocalFree(pvMsgBuf);
continue;
}
// Get the address of the register/unregister function.
(FARPROC&)pfn = GetProcAddress(hinst, pszFnName);
// If it wasn't found, construct an error message.
if (NULL == pfn)
{
wsprintf(szMsg, _T("%hs not found in this module."), pszFnName);
continue;
}
// Call the function!
HRESULT hr = pfn();
// Construct a message listing the result of the function call.
if (SUCCEEDED(hr))
{
bSuccess = true;
wsprintf(szMsg, _T("%hs succeeded on %s"), pszFnName, it->c_str());
}
else
{
void* pvMsgBuf = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pvMsgBuf, 0, NULL);
wsprintf(szMsg, _T("%hs failed on %s\nError 0x%08lX: %s"),
pszFnName, it->c_str(), (DWORD)hr,
NULL != pvMsgBuf ? pvMsgBuf : _T("(No description available)"));
LocalFree(pvMsgBuf);
}
} // end __try
__finally
{
// Fill in the LVITEM struct. The item text will be the filename,
// and the LPARAM will point to a copy of the szMsg status message.
strMsg = szMsg;
m_lsStatusMessages.push_back(strMsg);
ZeroMemory(&rItem, sizeof(LVITEM));
rItem.mask = LVIF_PARAM | LVIF_TEXT;
rItem.iItem = nIndex;
rItem.pszText = const_cast<LPTSTR>(it->c_str());
rItem.lParam = (LPARAM)(DWORD_PTR)m_lsStatusMessages.back().c_str();
ListView_InsertItem(m_hwndList, &rItem);
// Set the text in column 2 to "succeeded" or "failed" depending
// on the outcome of the function call.
ListView_SetItemText(m_hwndList, nIndex++, 1,
bSuccess ? _T("Succeeded") : _T("Failed"));
if (NULL != hinst)
FreeLibrary(hinst);
}
// Process messages in our queue - this is much easier than doing it
// "the real way" with a worker thread. This is how we detect a click
// on the Stop button.
MSG msg;
while (PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Resize the list columns.
ListView_SetColumnWidth(m_hwndList, 0, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(m_hwndList, 1, LVSCW_AUTOSIZE_USEHEADER);
} // end for
}
I want to understand why did he decide to use SEH
in the first place, why couldn't he use the regular try/catch
? He even moved the strMsg
out of the method to the global space which by itself is as far is I understood previously is better to be avoided. But still the project could only be compiled with the exception handing /EHa
option, I can speculate that this's because of the it
variable which is used in the for
loop, but I'm not sure that my assumption is correct and why would a list iteration variable need to be destructured, isn't it a kind of a pointer to the current list item?
How this function could be possibly rewritten in order to get things done in an acceptable way? I've read about RAII
wrappers and that in order to get functions that use SEH
alongside with objects that require unwinding compilable without changing the aforementioned exception handing option these two have to be separated into different functions, but I just can't wrap my head around all that, and some help will be much appreciated.
Update
In order to make the objects unwinding possible I took the SEH
exceptions handling out from the loop into a different method.
void CProgressDlg::ProcessLib(LPCWSTR lpLibFileName, LPCSTR pszFnName)
{
bool bSuccess;
HINSTANCE hinst;
TCHAR szMsg[512];
HRESULT(STDAPICALLTYPE * pfn)();
LVITEM rItem;
int nIndex = 0;
bSuccess = false;
hinst = NULL;
*szMsg = '\0';
// We will print a status message into szMsg, which will eventually
// be stored in the LPARAM of a listview control item.
__try
{
// Try to load the next file.
hinst = LoadLibraryEx(lpLibFileName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
// If it failed, construct a friendly error message.
if (NULL == hinst)
{
void* pvMsgBuf = NULL;
DWORD dwLastErr = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dwLastErr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pvMsgBuf, 0, NULL);
wsprintf(szMsg, _T("LoadLibraryEx failed on this module.\nError 0x%08lX (%s)"),
dwLastErr, pvMsgBuf ? pvMsgBuf : _T("No description available"));
LocalFree(pvMsgBuf);
return;
}
// Get the address of the register/unregister function.
(FARPROC&)pfn = GetProcAddress(hinst, pszFnName);
// If it wasn't found, construct an error message.
if (NULL == pfn)
{
wsprintf(szMsg, _T("%hs not found in this module."), pszFnName);
return;
}
// Call the function!
HRESULT hr = pfn();
// Construct a message listing the result of the function call.
if (SUCCEEDED(hr))
{
bSuccess = true;
wsprintf(szMsg, _T("%hs succeeded on %s"), pszFnName, lpLibFileName);
}
else
{
void* pvMsgBuf = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pvMsgBuf, 0, NULL);
wsprintf(szMsg, _T("%hs failed on %s\nError 0x%08lX: %s"),
pszFnName, lpLibFileName, (DWORD)hr,
NULL != pvMsgBuf ? pvMsgBuf : _T("(No description available)"));
LocalFree(pvMsgBuf);
}
} // end __try
__finally
{
// Fill in the LVITEM struct. The item text will be the filename,
// and the LPARAM will point to a copy of the szMsg status message.
//strMsg = szMsg;
m_lsStatusMessages.push_back(szMsg); // <-- This line gives C2712 Cannot use __try in functions that require object unwinding
ZeroMemory(&rItem, sizeof(LVITEM));
rItem.mask = LVIF_PARAM | LVIF_TEXT;
rItem.iItem = nIndex;
rItem.pszText = const_cast<LPTSTR>(lpLibFileName);
rItem.lParam = (LPARAM)(DWORD_PTR)m_lsStatusMessages.back().c_str();
ListView_InsertItem(m_hwndList, &rItem);
// Set the text in column 2 to "succeeded" or "failed" depending
// on the outcome of the function call.
ListView_SetItemText(m_hwndList, nIndex++, 1,
bSuccess ? _T("Succeeded") : _T("Failed"));
if (NULL != hinst)
FreeLibrary(hinst);
}
}
But now a push_back
to the list of strings, which is part of the class gives me the C2712
error. Why modification of an object that exists outside of the scope of function that requires object unwinding gives me this error?