C++ Windows程序如何提取OLE拖放的Outlook邮件内容与组件?
Outlook拖放OLE对象提取邮件内容问题解答
问题背景
用C++实现的Windows程序已支持接收MS Outlook拖放的OLE对象,能枚举拖放对象格式并读取TYMED_ISTREAM类型的流,但无法提取Outlook邮件的核心属性(正文、附件、发件人、收件人、主题等)。枚举到的FORMATETC记录中,格式代码49814的流读取到302字节,以下是具体问题及解决方案:
问题1:如何从读取的流中提取Outlook邮件?
具体操作步骤:
- 定位有效格式:你枚举到的
RenPrivateItem(49708)、RenPrivateMessages(49735)、RenPrivateLatestMessages(49968)是Outlook私有剪贴板格式,对应流中存储的是MAPI对象的二进制序列化数据,这是提取邮件的关键来源。 - 初始化MAPI环境:调用
MAPIInitialize(NULL)启动MAPI库,确保后续接口调用正常。 - 加载邮件对象:将读取到的流传入
OpenIMsgOnIStg函数,该函数会从IStream中解析出Outlook邮件的IMessage接口实例。 - 提取邮件属性:通过
IMessage接口的方法获取各属性:- 主题:调用
GetProps获取PR_SUBJECT属性值 - 发件人:获取
PR_SENDER_NAME、PR_SENDER_EMAIL_ADDRESS属性 - 收件人:调用
GetRecipientTable获取收件人列表,再遍历表项提取信息 - 正文:获取
PR_BODY(纯文本)或PR_HTML(HTML格式)属性 - 附件:调用
GetAttachmentTable枚举附件,通过OpenAttach打开附件并读取内容
- 主题:调用
- 清理资源:使用完MAPI接口后,调用
MAPIUninitialize()释放资源。
问题2:是否需要调用RegisterClipboardFormat?
2.1 应注册的格式名称
需要注册以下Outlook私有格式名称,获取对应的CLIPFORMAT值:
"RenPrivateItem""RenPrivateMessages""RenPrivateLatestMessages"
这些名称对应你枚举到的格式代码49708、49735、49968。
2.2 注册的必要性(枚举为何不够)
- 枚举仅能拿到格式的数值代码,无法直接关联到格式的逻辑含义(比如哪个对应单邮件、哪个对应多邮件集合),注册后可通过名称明确指定目标格式,避免依赖不稳定的数值代码(不同系统/Outlook版本可能变动)。
- 主动请求特定格式数据时,必须用注册得到的CLIPFORMAT构造FORMATETC,确保
GetData能精准获取对应的数据。
可用格式说明
你枚举到的格式中,以下可用于获取Outlook邮件:
- 49708 (RenPrivateItem):对应单封Outlook邮件对象
- 49735 (RenPrivateMessages):对应多封邮件的集合
- 49968 (RenPrivateLatestMessages):最新邮件的集合
- 49395 (FileGroupDescriptor):仅当邮件以.eml文件形式拖放时有效,包含文件描述,但不是直接的邮件对象
枚举拖放对象/格式的代码(中文注释版)
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 错误 = " << res << std::endl; if (ok_get_enum) { res = ppenumFormatEtc->Reset(); ok_reset = (res == S_OK); if (!ok_reset) std::cout << "EnumFormatEtc.Reset 错误 = " << res << std::endl; } if (ok_reset) { std::cout << "------ 枚举格式开始 ---------" << 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 << "请求数量 = " << ElementsInPortions << " 获取数量 = " << 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 << " 格式 = " << CurFormatCode << "(" << GetFormatName(CurFormatCode) << ")" << " 显示方式 = " << CurAspect << " 存储介质 = " << CurTymed << "(" << GetTymedName(CurTymed) << ")" << " 索引 = " << LIndex << std::endl; if (CurTymed == TYMED_ISTREAM) { std::cout << "--------- 读取流数据 ---------" << std::endl; GetData_IStream(pDataObj, &(ItemsArray[i])); std::cout << "-----------------------------------------" << std::endl << std::endl; } } } std::cout << "------ 枚举结束 ---------" << std::endl; ppenumFormatEtc->Release(); } *pdwEffect = DROPEFFECT_COPY; return S_OK; } // 读取ISTREAM的代码 bool GetData_IStream(IDataObject* pDataObj, const FORMATETC* in_FormatEtc) { // ---------------- 查询是否支持该格式 ---------------- FORMATETC ReadFormat; ZeroMemory(&ReadFormat, sizeof(ReadFormat)); ReadFormat.cfFormat = in_FormatEtc->cfFormat; // 剪贴板格式 ReadFormat.tymed = TYMED_ISTREAM; // 存储介质类型 ReadFormat.ptd = NULL; // 目标设备 ReadFormat.dwAspect = DVASPECT_CONTENT; // 内容显示方式 ReadFormat.lindex = -1; // 索引 HRESULT res = pDataObj->QueryGetData(&ReadFormat); bool ok_query = (res == S_OK); if (ok_query) std::cout << "QueryGetData - 成功" << std::endl; else { std::cout << "QueryGetData - 错误 = " << res << std::endl; return false; } // ---------------- 获取数据 ---------------- ZeroMemory(&ReadFormat, sizeof(ReadFormat)); ReadFormat.cfFormat = in_FormatEtc->cfFormat; // 剪贴板格式 ReadFormat.tymed = TYMED_ISTREAM; // 存储介质类型 ReadFormat.ptd = NULL; // 目标设备 ReadFormat.dwAspect = DVASPECT_CONTENT; // 内容显示方式 ReadFormat.lindex = -1; // 索引 STGMEDIUM MediumInfo; res = pDataObj->GetData(&ReadFormat, &MediumInfo); bool ok_getdata = (res == S_OK); if (ok_getdata) std::cout << "GetData - 成功" << std::endl; else { std::cout << "GetData - 错误 = " << res << std::endl; return false; } // 确认返回的存储介质类型正确 if (MediumInfo.tymed != ReadFormat.tymed) { std::cout << "GetData 错误 - 无效的_MediumInfo.tymed_ = " << MediumInfo.tymed << std::endl; return false; } IStream* Stream = MediumInfo.pstm; if (Stream == NULL) { std::cout << "GetData 错误 - Stream == NULL." << std::endl; return false; } bool ReadResult = false; // ---------------- 分配缓冲区读取流 ---------------- const int BufferSize = 100000; char* BufferPtr = (char*)malloc(BufferSize); if (BufferPtr != nullptr) { // ---------------- 读取流数据 ------------------------ 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 等待中 :((" << std::endl; else std::cout << "Stream.Seek 错误 = " << res_seek << std::endl; } else std::cout << "Stream.Seek 成功. 新位置 = 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: { // --- 读取到数据 --- StreamEof = (ActuallyRead < BufferSize); TotalRead = TotalRead + ActuallyRead; break; } // S_OK, S_FALSE case E_PENDING: { // 等待中... 暂不处理 break; } // E_PENDING default: { ReadError = true; std::cout << "Stream 读取错误 = " << res_read << std::endl; } // default } // switch } // while ReadResult = !ReadError; if (!ReadError) { std::cout << "Stream 读取成功. 总读取字节数 = " << TotalRead << std::endl; } } else { std::cout << "Malloc 错误 - 无法分配流数据缓冲区." << std::endl; ReadResult = false; } Stream->Release(); return ReadResult; }
内容的提问来源于stack exchange,提问作者LUN
相关产品推荐
相关产品推荐

