Word的 VBA/自动化中的Documents.Open() 函数在2026年 6月的安全更新(KB5094126)后突然失效

后端开发 2026-07-08

几天前,我安装了最新的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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章