MAPISendMail crashes UWP converted app. Alternatives to sending an email from a UWP app?

1

I have a Win32 application that is also distributed via the Windows Store (for that it's converted to UWP using the Desktop Bridge.)

The application uses MAPISendMail function to automate creation of an email in a default email client with an arbitrary attachment, as such:

BOOL SendEmail(HWND hParentWnd, LPCTSTR pStrEmailAddrTo, LPCTSTR pStrSubject, LPCTSTR pStrMsg, LPCTSTR pFilePathAttachment)
{
    BOOL bRes = FALSE;

    HINSTANCE hMAPI = ::LoadLibrary(L"MAPI32.DLL");
    if(hMAPI)
    {
        ULONG (WINAPI *pfnMAPISendMailW)(LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved);

        (FARPROC&)pfnMAPISendMailW = GetProcAddress(hMAPI, "MAPISendMailW");
        if(pfnMAPISendMailW)
        {
            //Fill out data
            MapiMessageW message = {0};
            MapiFileDescW fileDesc = {0};
            MapiRecipDescW recip = {0};

            if(pFilePathAttachment &&
                pFilePathAttachment[0])
            {
                fileDesc.nPosition = (ULONG)-1;
                fileDesc.lpszPathName = ::PathFindFileName(pFilePathAttachment);
                fileDesc.lpszFileName = (PWSTR)pFilePathAttachment;

                message.nFileCount = 1;
                message.lpFiles = &fileDesc;

            }


            message.lpszSubject = (PWSTR)pStrSubject;
            message.lpszNoteText = (PWSTR)pStrMsg;

            if(pStrEmailAddrTo)
            {
                recip.ulRecipClass = MAPI_TO;
                recip.lpszName = L"";
                recip.lpszAddress = (PWSTR)pStrEmailAddrTo;

                message.nRecipCount = 1;
                message.lpRecips = &recip;
            }

            int nError = pfnMAPISendMailW(0, (ULONG_PTR)hParentWnd, &message, MAPI_LOGON_UI | MAPI_DIALOG, 0);

            bRes = nError == SUCCESS_SUCCESS;
        }

        ::FreeLibrary(hMAPI);
    }

    return bRes;
}

And then the function is called after a button click in a dialog window:

SendEmail(this->GetSafeHwnd(), NULL,
    L"Email subject message",
    L"See attached file",
    strFilePath);

This works in a Win32 version, but converted UWP app crashes. Unfortunately there's not much diagnostics available for converted Store apps. All I can see from the crash dump is that it crashes in mmgaclient.dll:

enter image description here

and it is invalid address exception:

enter image description here

Deep in the bowels of that library:

enter image description here

I also saw other reports of MAPISendMail crashing other apps.

I started looking further into it and noticed that the MAPISendMail function behaves in a strange way -- it seems to disable the parent window but in case it shows any of its own UI message boxes, it doesn't make them modal. Here's an example. If I click on to the parent window of this message box, I can interact with it even though it doesn't seem to be processing any messages, which makes Windows show that "it's not responding":

enter image description here

In this case (in the desktop version) it doesn't crash the app though, and "not responding" message goes away when I click OK.

So I was wondering if anyone else ran into the same issue?

PS. Or, is there a different API to send an email from a converted UWP app?


EDIT: This thing turns out to be quite long. I'll try to cut it right down to the chase.

I was able to address the "not responding" parent UI part and a crash in MAPISendMail with the following approaches.

A) The crash might be caught by SEH on the API call itself (I won't know until the app is approved in the Windows Store):

ULONG isolated_MAPISendMailW(ULONG (WINAPI *pfnMAPISendMailW)(LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved),
                                    LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved)
{
    __try
    {
        return pfnMAPISendMailW(lhSession, ulUIParam, lpMessage, flFlags, ulReserved);
    }
    __except(1)
    {
        //SEH handler for all
        ASSERT(NULL);
        return -1;
    }
}

B) And to address not responding parent window UI, I tried calling MAPISendMail from a separate thread.

Updated code:

struct THREAD_INFO{

    HWND hParentWnd;
    LPCTSTR pStrEmailAddrTo;
    LPCTSTR pStrSubject;
    LPCTSTR pStrMsg;
    LPCTSTR pFilePathAttachment;

    BOOL bRes;
};

BOOL SendEmail(HWND hParentWnd, LPCTSTR pStrEmailAddrTo, LPCTSTR pStrSubject, LPCTSTR pStrMsg, LPCTSTR pFilePathAttachment)
{
    THREAD_INFO ti;

    ti.hParentWnd = hParentWnd;
    ti.pStrEmailAddrTo = pStrEmailAddrTo;
    ti.pStrSubject = pStrSubject;
    ti.pStrMsg = pStrMsg;
    ti.pFilePathAttachment = pFilePathAttachment;
    ti.bRes = FALSE;

    HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc_SendEmail, &ti, 0, NULL);

    MSG msg;

    for(;;)
    {
        DWORD dwRw = ::MsgWaitForMultipleObjectsEx(1, &hThread, INFINITE, QS_ALLEVENTS, 0) - WAIT_OBJECT_0;
        if(dwRw == 0)
        {
            //Thread has exited
            break;
        }
        else if(dwRw == 1)
        {
            //Process some messages
            for(int t = 0; t < 8; t++)
            {
                if(!::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                    break;
            }
        }
        else
        {
            //Error
            ASSERT(NULL);
            ::TerminateThread(hThread, 0xdeadbeef);
            break;
        }
    }

    ::CloseHandle(hThread);

    return ti.bRes;
}


DWORD ThreadProc_SendEmail(LPVOID lpParameter)
{
    THREAD_INFO* pTI = (THREAD_INFO*)lpParameter;

    pTI->bRes = SendEmail_internal(pTI->hParentWnd, pTI->pStrEmailAddrTo,
        pTI->pStrSubject, pTI->pStrMsg, pTI->pFilePathAttachment);

    return 0;
}

BOOL SendEmail_internal(HWND hParentWnd, LPCTSTR pStrEmailAddrTo, LPCTSTR pStrSubject, LPCTSTR pStrMsg, LPCTSTR pFilePathAttachment)
{
    BOOL bRes = FALSE;

    HINSTANCE hMAPI = ::LoadLibrary(L"MAPI32.DLL");
    if(hMAPI)
    {
        ULONG (WINAPI *pfnMAPISendMailW)(LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved);

        (FARPROC&)pfnMAPISendMailW = GetProcAddress(hMAPI, "MAPISendMailW");
        if(pfnMAPISendMailW)
        {
            //Fill out data
            MapiMessageW message = {0};
            MapiFileDescW fileDesc = {0};
            MapiRecipDescW recip = {0};

            if(pFilePathAttachment &&
                pFilePathAttachment[0])
            {
                fileDesc.nPosition = (ULONG)-1;
                fileDesc.lpszPathName = ::PathFindFileName(pFilePathAttachment);
                fileDesc.lpszFileName = (PWSTR)pFilePathAttachment;

                message.nFileCount = 1;
                message.lpFiles = &fileDesc;

            }


            message.lpszSubject = (PWSTR)pStrSubject;
            message.lpszNoteText = (PWSTR)pStrMsg;

            if(pStrEmailAddrTo)
            {
                recip.ulRecipClass = MAPI_TO;
                recip.lpszName = L"";
                recip.lpszAddress = (PWSTR)pStrEmailAddrTo;

                message.nRecipCount = 1;
                message.lpRecips = &recip;
            }

            int nError = isolated_MAPISendMailW(pfnMAPISendMailW, 0, (ULONG_PTR)hParentWnd, &message, MAPI_LOGON_UI | MAPI_DIALOG, 0);

            bRes = nError == SUCCESS_SUCCESS;
        }

        ::FreeLibrary(hMAPI);
    }

    return bRes;
}

ULONG isolated_MAPISendMailW(ULONG (WINAPI *pfnMAPISendMailW)(LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved),
                                    LHANDLE lhSession, ULONG_PTR ulUIParam, lpMapiMessageW lpMessage, FLAGS flFlags, ULONG ulReserved)
{
    __try
    {
        return pfnMAPISendMailW(lhSession, ulUIParam, lpMessage, flFlags, ulReserved);
    }
    __except(1)
    {
        //SEH handler for all
        ASSERT(NULL);
        return -1;
    }
}

But another part of the previous issue still remains: even though now the parent window doesn't hang up, any UI displayed by the MAPISendMail is still done in a separate window:

enter image description here

So a user can click on the parent to activate it from the taskbar, or just on the window itself:

enter image description here

that will totally obscure it and will be very confusing to the user, as the parent window will not respond to clicks on controls inside of it.

So I was wondering, is there any way I can put my parent window into a modal message loop before MAPISendMail displays its own UI?


EDIT2: I want to post an update that my solution above DID NOT work. My converted UWP app still crashed when MAPISendMailW was called, in despite of the precautions I implemented above. So my only fix so far was to gray out that UI feature for UWP apps if Windows 10, build earlier than 17107 is detected.

c++
winapi
uwp
windows-store-apps
desktop-bridge
asked on Stack Overflow Mar 23, 2018 by c00000fd • edited Apr 3, 2018 by c00000fd

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0