如何在Windows Flutter应用中使用win32包解析Outlook .msg文件
Windows Flutter端基于win32包解析Outlook .msg文件实现方案
前置依赖
- 运行设备已安装桌面版Microsoft Outlook,功能实现依赖Outlook暴露的COM接口,和Python示例的底层调用逻辑完全一致
- Flutter项目已添加win32包依赖,执行
flutter pub add win32即可完成安装
核心实现代码
注意:所有COM操作必须先初始化COM公寓,使用完成后必须释放资源,否则会出现调用失败、Outlook进程残留问题
import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; IDispatch openOutlookMsg(String msgAbsolutePath) { // 1. 初始化COM环境 final initHr = CoInitializeEx( nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE, ); if (FAILED(initHr)) throw WindowsException(initHr); // 2. 创建Outlook.Application COM实例 final progId = 'Outlook.Application'.toNativeUtf16(); final appClsid = GUID.allocate(); var hr = CLSIDFromProgID(progId, appClsid); free(progId); if (FAILED(hr)) { CoUninitialize(); throw WindowsException(hr); } final appDispatch = IDispatch(Pointer<COMObject>.fromAddress(0)); final iidDispatch = GUID.fromString(IID_IDispatch); hr = CoCreateInstance( appClsid, nullptr, CLSCTX_LOCAL_SERVER, iidDispatch, appDispatch.ptr.cast(), ); free(iidDispatch); free(appClsid); if (FAILED(hr)) { CoUninitialize(); throw WindowsException(hr); } // 3. 调用GetNamespace获取MAPI命名空间 final mapiStr = 'MAPI'.toNativeUtf16(); final mapiArg = VARIANT.allocate(); VariantInit(mapiArg); mapiArg.vt = VT_BSTR; mapiArg.bstrVal = SysAllocString(mapiStr); free(mapiStr); final getNsParams = DISPPARAMS.allocate(); getNsParams.cArgs = 1; getNsParams.rgvarg = mapiArg.ptr; getNsParams.cNamedArgs = 0; getNsParams.rgdispidNamedArgs = nullptr; final getNsName = 'GetNamespace'.toNativeUtf16(); final getNsDispid = calloc<Int32>(); hr = appDispatch.GetIDsOfNames( GUID.allocate().address, getNsName.cast(), 1, LOCALE_USER_DEFAULT, getNsDispid, ); free(getNsName); if (FAILED(hr)) { VariantClear(mapiArg); free(mapiArg); free(getNsParams); free(getNsDispid); appDispatch.Release(); CoUninitialize(); throw WindowsException(hr); } final nsResult = VARIANT.allocate(); VariantInit(nsResult); hr = appDispatch.Invoke( getNsDispid.value, GUID.allocate().address, LOCALE_USER_DEFAULT, DISPATCH_METHOD, getNsParams, nsResult, nullptr, nullptr, ); free(getNsDispid); free(getNsParams); VariantClear(mapiArg); free(mapiArg); appDispatch.Release(); if (FAILED(hr)) { CoUninitialize(); throw WindowsException(hr); } final mapiNs = IDispatch(nsResult.pdispVal); // 4. 调用OpenSharedItem打开目标msg文件 final pathStr = msgAbsolutePath.toNativeUtf16(); final pathArg = VARIANT.allocate(); VariantInit(pathArg); pathArg.vt = VT_BSTR; pathArg.bstrVal = SysAllocString(pathStr); free(pathStr); final openParams = DISPPARAMS.allocate(); openParams.cArgs = 1; openParams.rgvarg = pathArg.ptr; openParams.cNamedArgs = 0; openParams.rgdispidNamedArgs = nullptr; final openMethodName = 'OpenSharedItem'.toNativeUtf16(); final openDispid = calloc<Int32>(); hr = mapiNs.GetIDsOfNames( GUID.allocate().address, openMethodName.cast(), 1, LOCALE_USER_DEFAULT, openDispid, ); free(openMethodName); if (FAILED(hr)) { VariantClear(pathArg); free(pathArg); free(openParams); free(openDispid); mapiNs.Release(); CoUninitialize(); throw WindowsException(hr); } final msgResult = VARIANT.allocate(); VariantInit(msgResult); hr = mapiNs.Invoke( openDispid.value, GUID.allocate().address, LOCALE_USER_DEFAULT, DISPATCH_METHOD, openParams, msgResult, nullptr, nullptr, ); free(openDispid); free(openParams); VariantClear(pathArg); free(pathArg); mapiNs.Release(); if (FAILED(hr)) { CoUninitialize(); throw WindowsException(hr); } // 最终返回msg文件对应的COM实例 final msgItem = IDispatch(msgResult.pdispVal); return msgItem; }
关键说明
- 方法入参必须传入.msg文件的绝对路径,不支持相对路径
- 调用
IDispatch.Invoke传参时,参数顺序需要和方法定义的顺序逆序传入,多参数场景下不要传错顺序 - 拿到返回的
msgItem实例后,如果需要读取邮件主题、正文、收件人、附件等属性,先通过GetIDsOfNames拿到对应属性的DISPID,调用Invoke时传入DISPATCH_PROPERTYGET标识即可获取属性值 - 所有COM实例、VARIANT变量、指针使用完成后必须手动释放:COM实例调用
Release(),VARIANT调用VariantClear(),手动申请的内存调用free()/calloc.free(),所有逻辑执行完成后调用CoUninitialize(),避免Outlook进程后台残留 - 如果设备上的Outlook配置了COM调用权限拦截,会弹出授权提示,允许后即可正常调用
内容的提问来源于stack exchange,提问作者BananaMaster
相关产品推荐
相关产品推荐

