0

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?

Jyrkka
  • 526
  • 1
  • 8
  • 26
  • _"The Complete Idiot's Guide to Writing Shell Extensions - Part II - __15 May 2006__"_ - this might pre-date regular `try/catch` (or it's full implementation in MSVC) would need to check the Standards and MS docs. – Richard Critten Mar 08 '21 at 18:26
  • SEH are considered obsolete. Even Microsoft acknowledges this now (see "C++ exceptions versus Windows SEH exceptions" in https://learn.microsoft.com/en-us/cpp/cpp/errors-and-exception-handling-modern-cpp ) – Cow Corporation Mar 08 '21 at 18:28
  • @CowCorporation "*Obsolete*" is too strong of a word. From the linked page [Structured Exception Handling (C/C++)](https://learn.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160): "*we recommend that you use ISO-standard C++ exception handling. It makes your code more portable and flexible*. ***However***, *to maintain existing code or for particular kinds of programs, you still might have to use SEH*". – dxiv Mar 08 '21 at 18:36
  • 1
    In this context (Windows Shell API calls), you don't need `try` nor `__try`. Just check the error codes: HRESULT for API that return HRESULT, GetLastError for API that use SetLastError, etc. – Simon Mourier Mar 08 '21 at 18:42
  • @RichardCritten I've checked it and the very first standardized C++ iso/iec 14882:1998 (C++98) already had exception handling(page 291) with `try/catch`. [VS 2003 Tech Docs](https://www.microsoft.com/en-us/download/details.aspx?id=55979), thanks [@Abhishek-Keshri](https://stackoverflow.com/a/49990327/1707245), on page 23183 states that `The Microsoft C++ compiler implements the C++ exception handling model based on the ANSI C++ standard` and already at the time they recommended `For C++ programs, it is recommended that you use the C++ exception-handling mechanism`(@CowCorporation). – Jyrkka Mar 09 '21 at 00:49
  • @SimonMourier Thanks for your input, could you please add several words on when would I need to use SEH? – Jyrkka Mar 09 '21 at 00:54
  • You can use SEH when you call a code that's not yours and want to protect against "exceptional code situations" (ie: pointer reference bugs mostly) https://learn.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp. I personally almost never use it. – Simon Mourier Mar 09 '21 at 07:00
  • @SimonMourier Yes, I've read it, and that's exactly what happens inside this method - the extension calls someone else's code from an arbitrary library that user selects, and I think that it's a good idea to protect against bugs or memory issues in that code, since these could lead to explorer crash. – Jyrkka Mar 09 '21 at 11:40
  • If you already know all this, what's your question then? – Simon Mourier Mar 09 '21 at 12:19
  • @SimonMourier I don't claim knowledge, these are my assumptions based on what I read before posting this question, and I'm asking for clarifications, that's all. You told that in context of this question one don't need neither `try` nor `__try`, but then you told that I can use `SEH` when I call code that's not mine, but that's exactly what happens inside this function in question, so why do you think that exception handling is not necessary here? – Jyrkka Mar 09 '21 at 14:57
  • If you compile with /EHa then try/catch(...) will catch-em-all, including the nasty SEH exceptions. Dereference a null pointer or divide by zero to see that for yourself. That `catch(...)` gets so powerful is not exactly a grand feature, but right now you like it. – Hans Passant Mar 12 '21 at 13:36

0 Answers0