Word的 VBA/自动化中的Documents.Open() 函数在2026年 6月的安全更新(KB5094126)后突然失效
几天前,我安装了最新的Windows 11安全更新(2026年 6月),多年运行无误的我的代码突然不再工作。
现场的客户也在发生同样的情况,他们多年未改动的代码,结果应用程序突然失败。
这段代码与通过自动化(Automation)从基于MFC的 C++应用程序对Microsoft Word进行远程控制有关。这是一个有着20多年历史的遗留应用。
具体错误:打开模板文件时,Word会立即抛出异常“Type conflict”。
我相信我这是按规范的做法:我获取 Documents,也就是Word中所有已打开文档的列表(到目前为止没有任何文档),然后调用Open():
MSWordApplication app;
app.CreateDispatch("Word.Application",&e);
const char *templatePath="C:\\users\\tpoll\\appdata\\local\\fortiter\\titlepage.docx";
COleVariant varOpt;
varOpt.vt=VT_ERROR;
varOpt.scode = DISP_E_PARAMNOTFOUND;
MSWordDocuments allDocs(app.GetDocuments());
MSWordDocument doc(allDocs.Open(COleVariant(templatePath) , &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt, &varOpt));
请注意,路径是硬编码到模板文件夹的。这与找不到文件、路径中包含禁止字符或其他类似情况无关。
另请注意,我主要传递 &varOpt,这是表示“我不为这个参数传递任何内容”的标准做法。
在MFC的深处,这个函数(这是MFC自带的一部分,而非我编写的函数)
void COleDispatchDriver::InvokeHelperV(DISPID dwDispID, WORD wFlags,
VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, va_list argList)
在这行
SCODE sc = m_lpDispatch->Invoke(dwDispID, IID_NULL, 0, wFlags,
&dispparams, pvarResult, &excepInfo, &nArgErr);
返回 0x80020005,这意味着TypeConflict。
我的理解是:这与实际打开文件无关——是在对参数类型进行再次检查时就已经失败。
最近的安全更新是否可能
- 加强了参数检查(例如,以前只是用 &varOpt在缺失参数处填充,但现在不再这样做)?
- 突然将某些原本可选的参数改为必填(MSDN文档 https://learn.microsoft.com/en-us/office/vba/api/word.documents.open 说法不同,但可能已过时)?
- 增加了尚未文档化的更多参数?
- 突然不再允许打开这个模板文件,该文件位于
...\Appdata\Local的某处(不过这肯定会得到不同的错误信息,而不是Type Conflict)?
或者你还有其他线索供我继续调查吗?
附注:我在Word的 Visual Basic编辑器中尝试从内部打开该文件——运行非常顺利
解决方案
问题在于我使用了延迟绑定(late binding)。我过去都是这样调用Documents.Open():
LPDISPATCH MSWordDocuments::Open(VARIANT * FileName, VARIANT * ConfirmConversions, VARIANT * ReadOnly, VARIANT * AddToRecentFiles, VARIANT * PasswordDocument, VARIANT * PasswordTemplate, VARIANT * Revert, VARIANT * WritePasswordDocument,
VARIANT * WritePasswordTemplate, VARIANT * Format, VARIANT * Encoding, VARIANT * Visible, VARIANT * OpenAndRepair, VARIANT * DocumentDirection, VARIANT * NoEncodingDialog)
{
LPDISPATCH result;
static BYTE parms[] =
VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT;
InvokeHelper(0x12, DISPATCH_METHOD, VT_DISPATCH, (void*)&result, parms, FileName, ConfirmConversions, ReadOnly, AddToRecentFiles, PasswordDocument, PasswordTemplate, Revert, WritePasswordDocument, WritePasswordTemplate, Format, Encoding, Visible, OpenAndRepair, DocumentDirection, NoEncodingDialog);
return result;
}
这段代码是通过Visual Studio的“从类型库添加类”向导生成的,多年来一直能用。
显然,在更新KB5094126之后,必须对类型库执行 #import 操作,并在.tlh文件中动态生成相应的代码。将函数改写成如下形式(可以直接替换),一切又重新工作了:
LPDISPATCH MSWordDocuments::Open(VARIANT* FileName, VARIANT* ConfirmConversions, VARIANT* ReadOnly, VARIANT* AddToRecentFiles, VARIANT* PasswordDocument, VARIANT* PasswordTemplate, VARIANT* Revert, VARIANT* WritePasswordDocument,
VARIANT* WritePasswordTemplate, VARIANT* Format, VARIANT* Encoding, VARIANT* Visible, VARIANT* OpenAndRepair, VARIANT* DocumentDirection, VARIANT* NoEncodingDialog)
{
// Convert "this" (MFC wrapper) into the real COM interface
MSWord::DocumentsPtr docs = m_lpDispatch;
// Over the years, a 16th paramtere XMLTransform has been added
// Note that sticking to late binding and simply passing a further &varOpt
// for this additional parameter does _not_ do the trick, so we create
// a varOpt of our own here
COleVariant XMLTransform;
XMLTransform.vt = VT_ERROR;
XMLTransform.scode = DISP_E_PARAMNOTFOUND;
// Call the real, typelib-correct method
MSWord::_DocumentPtr doc = docs->Open(
FileName,
ConfirmConversions,
ReadOnly,
AddToRecentFiles,
PasswordDocument,
PasswordTemplate,
Revert,
WritePasswordDocument,
WritePasswordTemplate,
Format,
Encoding,
Visible,
OpenAndRepair,
DocumentDirection,
NoEncodingDialog,
XMLTransform
);
// Return as LPDISPATCH to keep your old API intact
return doc.Detach();
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。