outlookdrag-and-dropole

How can I extract Outlook message, dragged via OLE drag-and-drop to my window?


My program (C++, Windows) implements a window, receiving OLE drag-and-drop object (a message dragged from MS Outlook), enumerates formats of dragged objects, reads a stream when TYMED_ISTREAM is received. But I am confused - how can I extract an Outlook letter and it's components (letter body, attachments, attributes - From:, To:, Subject etc.)

The attached screenshot shows a listing - what I have read, enumerated FORMATETC records (it's main fields are shown).

For example, I have read 302 bytes from a stream, format code = 49814.

Question1: But how can I extract Outlook letter from these 302 bytes ? Could you please tell me the plan of my actions to do that ?

Question2: Should I or not to call RegisterClipboardormat (I did not) ? If yes, (2.1) which code should I register and (2.2) why should this be done (why my enumeration is not enough) ?

Additionally, I have got system name of several format codes (via GetClipboardFormatName): 49968 - RenPrivateLatestMessages, 49735 - RenPrivateMessages, 49708 - RenPrivateItem, 49395 - FileGroupDescriptor, 49159 - FileNameW etc. Which of them are usefull and allow to get Outlook message ?

The code, used to enumerate dropped objects/formats:

HRESULT __stdcall MyDropTarget_CLASS::Drop(IDataObject* pDataObj, DWORD grfKeyState,
    POINTL pt,
    DWORD* pdwEffect) {

    bool ok_get_enum = false, ok_reset = false;

    DragWindowPtr->SetNormalBrush();

    IEnumFORMATETC* ppenumFormatEtc;
    HRESULT res = pDataObj->EnumFormatEtc(DATADIR_GET, &ppenumFormatEtc);
    ok_get_enum = (res == S_OK);
    if (!ok_get_enum)
        std::cout << "EnumFormatEtc ERROR = " << res << std::endl;

    if (ok_get_enum) {
        res = ppenumFormatEtc->Reset();
        ok_reset = (res == S_OK);
        if (!ok_reset)
            std::cout << "EnumFormatEtc.Reset ERROR = " << res << std::endl;
    }

    if (ok_reset) {

        std::cout << "------ Enumerating ---------" << std::endl;

        const ULONG ElementsInPortions = 5;     // сколько записей читаем
        FORMATETC ItemsArray[ElementsInPortions];
        ULONG FetchedNumber = 0;
        bool DoNext = true;
        while (DoNext) {
            DoNext = (res = ppenumFormatEtc->Next(ElementsInPortions, &(ItemsArray[0]), &FetchedNumber)) == S_OK;
            std::cout << "Requested = " << ElementsInPortions << "    Fetched = " << FetchedNumber << std::endl;
            for (ULONG i = 0; i < FetchedNumber; i++) {

                CLIPFORMAT CurFormatCode = ItemsArray[i].cfFormat;
                DWORD CurAspect = ItemsArray[i].dwAspect;
                DWORD CurTymed = ItemsArray[i].tymed;
                LONG LIndex = ItemsArray[i].lindex;

                std::cout << "      Format = " << CurFormatCode << "(" << GetFormatName(CurFormatCode) << ")" <<
                    "   Aspect = " << CurAspect <<
                    "   Tymed = " << CurTymed << "(" << GetTymedName(CurTymed) << ")" <<
                    "   LIndex = " << LIndex << std::endl;




                if (CurTymed == TYMED_ISTREAM) {
                    std::cout << "--------- Reading from a stream ---------" << std::endl;
                    GetData_IStream(pDataObj, &(ItemsArray[i]));
                    std::cout << "-----------------------------------------" << std::endl << std::endl;
                }

            }

        }

        std::cout << "------ FINISHED ---------" << std::endl;

        ppenumFormatEtc->Release();
    }

    *pdwEffect = DROPEFFECT_COPY;

    return S_OK;
}

// The code that reads ISTREAM

bool GetData_IStream(IDataObject* pDataObj, const FORMATETC* in_FormatEtc) {


    // ---------------- QueryGetData ----------------

    FORMATETC ReadFormat;
    ZeroMemory(&ReadFormat, sizeof(ReadFormat));

    ReadFormat.cfFormat = in_FormatEtc->cfFormat;                     // CLIPFORMAT
    ReadFormat.tymed = TYMED_ISTREAM;           // DWORD
    ReadFormat.ptd = NULL;                      //  [unique] DVTARGETDEVICE*
    ReadFormat.dwAspect = DVASPECT_CONTENT;     // DWORD
    ReadFormat.lindex = -1;                     // LONG

    HRESULT res = pDataObj->QueryGetData(&ReadFormat);
    bool ok_query = (res == S_OK);
    if (ok_query)
        std::cout << "QueryGetData - OK" << std::endl;
    else {
        std::cout << "QueryGetData - ERROR = " << res << std::endl;
        return false;
    }
    

    // ---------------- GetData ----------------

    ZeroMemory(&ReadFormat, sizeof(ReadFormat));

    ReadFormat.cfFormat = in_FormatEtc->cfFormat;                     // CLIPFORMAT
    ReadFormat.tymed = TYMED_ISTREAM;           // DWORD
    ReadFormat.ptd = NULL;                      //  [unique] DVTARGETDEVICE*
    ReadFormat.dwAspect = DVASPECT_CONTENT;     // DWORD
    ReadFormat.lindex = -1;                     // LONG

    STGMEDIUM MediumInfo;   
    res = pDataObj->GetData(&ReadFormat, &MediumInfo);
    bool ok_getdata = (res == S_OK);
    if (ok_getdata)
        std::cout << "GetData - OK" << std::endl;
    else {
        std::cout << "GetData - ERROR = " << res << std::endl;
        return false;
    }

    // MediumInfo должен содержать актуальную ссылку на IStream
    if (MediumInfo.tymed != ReadFormat.tymed) {
        std::cout << "GetData ERROR - invalid _MediumInfo.tymed_ = " << MediumInfo.tymed << std::endl;
        return false;
    }

    IStream* Stream = MediumInfo.pstm;
    if (Stream == NULL) {
        std::cout << "GetData ERROR - Stream == NULL." << std::endl;
        return false;
    }

    bool ReadResult = false;

    // ---------------- allocate a buffer to read stream ----------------
    const int BufferSize = 100000;
    char* BufferPtr = (char* ) malloc(BufferSize);
    if (BufferPtr != nullptr) {

        // ---------------- Read the Stream ------------------------
        ULONG ActuallyRead = 0;     
        ULONG TotalRead = 0;        
        bool StreamEof = false;
        bool ReadError = false;

        // --- позицинируем в начало ---
        ULARGE_INTEGER NewPos = { 0, 0 };
        LARGE_INTEGER SetPos = { 0, 0 };
        HRESULT res_seek = Stream->Seek(SetPos, STREAM_SEEK_SET, &NewPos);
        ReadError = (res_seek != S_OK);

        if (ReadError) {
            bool pending = res_seek == E_PENDING;
            if (pending)
                std::cout << "Stream.Seek is pending :((" << std::endl;
            else
                std::cout << "Stream.Seek ERROR = " << res_seek << std::endl;
        }
        else
            std::cout << "Stream.Seek OK. New pos = LO=" << NewPos.LowPart << "  HI=" << NewPos.HighPart << std::endl;

        while (!StreamEof && !ReadError) {
            HRESULT res_read = Stream->Read(BufferPtr, (ULONG) BufferSize, &ActuallyRead);

            switch (res_read) {
                case S_OK: 
                case S_FALSE: {
                    // --- we have read something ---

                    StreamEof = (ActuallyRead < BufferSize);
                    TotalRead = TotalRead + ActuallyRead;

                    break;
                }  // S_OK, S_FALSE

                case E_PENDING: {
                    // pending... - not processed
                    break;
                } // E_PENDING

                default: {
                    
                    ReadError = true;
                    std::cout << "Stream reading ERROR = " <<res_read << std::endl;

                } // default
            } // switch


        } // while

        ReadResult = !ReadError;

        if (!ReadError) {
            std::cout << "Stream reading OK. TotalRead = " << TotalRead << std::endl;
        }
    }
    else {
        std::cout << "Malloc ERROR - cannot alloc memory for a stream data." << std::endl;
        ReadResult = false;
    }


    Stream->Release();

    return ReadResult;
}

Solution

  • The stream you get is really an MSG file (which is an IStorage file). You can either

    1. open the message as an Extended MAPI IMessage object using StgOpenStorage/OpenIMsgOnIStg,
    2. use Outlook Object Model (NameSpace.OpenSharedItem)
    3. Parse the IStorage object opened using StgOpenStorage