After installation of KB5094126 the Word integration over COM fails

Daratha, Paul 95 Reputation points
2026-06-11T18:46:08.1966667+00:00

After installing Windows Update KB5094126 (June 2026), our application fails when opening Word via COM automation.

Error:

HRESULT 0x80020005 (DISP_E_TYPEMISMATCH)
Type mismatch
  • Happens during Documents.Open() (IDispatch call)
  • Fully reproducible
  • Works again after uninstalling KB5094126

Impact:

  • Breaks Word integration / reporting completely
  • Affects multiple environments

Tested with:

  • Word 2013
  • Word 2021
  • Microsoft 365

→ So not Office-specific, likely COM/Windows layer

Question: Has anyone seen this after KB5094126? Was there a change in COM/OLE automation or VARIANT handling in this update?

Microsoft 365 and Office | Development | Other

Answer accepted by question author
Homero Junior 80 Reputation points
2026-07-14T20:05:47.8366667+00:00

My integration had been having issues ever since the update that broke it. All it took was installing this update—KB5101650—and everything started working again. Just install update KB5101650, restart, and you're all set.

Was this answer helpful?

4 people found this answer helpful.
0 comments No comments

11 additional answers

Sort by: Oldest
  1. Justin 30 Reputation points
    2026-06-15T14:12:17.1666667+00:00

    The error occurs in COleDispatchDriver::InvokeHelper after installing KB5094126. I used the legacy C++/MFC COM automation wrapper class generated by ClassWizard. After replacing the InvokeHelper function using a custom function with IDispatch::Invoke(...) and DISPPARAMS, it works again as expected. In this scenario, IDispatch is m_lpDispatch, coming from COleDispatchDriver.

    It is possible to let AI rewrite the automation wrapper class using a prompt like this:

    I have a legacy C++/MFC ClassWizard COM wrapper for Microsoft Word/Office that uses COleDispatchDriver::InvokeHelper(...). After a Microsoft Office/Word KB update, for example KB5094126, some late-bound calls fail with DISP_E_TYPEMISMATCH (0x80020005), while the same calls work when using direct IDispatch::Invoke(...).

    Please modify the .cpp wrapper file to replace all InvokeHelper(...) calls with a custom CustomInvokeHelper(...) that calls IDispatch::Invoke(...) directly.

    Keep all existing function signatures, parameters, return types, and the generated file structure unchanged.

    The helper must:

    Parse the existing MFC VTS_* parameter descriptors.

    Build explicit VARIANTARG arrays and DISPPARAMS.

    Reverse argument order for DISPPARAMS.rgvarg.

    Support method calls, property get, property put, and property putref.

    Use DISPID_PROPERTYPUT for property puts.

    Convert return values back to the expected MFC types: BOOL, long, short, float, CString, LPDISPATCH, LPUNKNOWN, and VARIANT.

    Initialize and clear all VARIANT / VARIANTARG values correctly.

    Free EXCEPINFO BSTR members.

    Critical KB/Office workaround:
    Do not pass VTS_PVARIANT parameters as VT_VARIANT | VT_BYREF. Instead, copy the actual value into the VARIANTARG with VariantCopyInd(...). If the source VARIANT* is NULL, pass a missing optional argument as VT_ERROR / DISP_E_PARAMNOTFOUND.

    Add special handling for Document.Close, Documents.Close, and Application.Quit:

    Drop trailing optional arguments when they are missing.

    Convert SaveChanges from VT_BOOL to VT_I4 (FALSE = 0, TRUE = -1).

    If m_lpDispatch == NULL because ReleaseDispatch() was already called, return silently for Close/Quit only.

    Return the complete modified .cpp file, compatible with older Visual C++ / MFC projects.

    Was this answer helpful?

    5 people found this answer helpful.

  2. Justin 30 Reputation points
    2026-06-16T09:30:53.05+00:00

    Here is the source code for the InvokeHelper replacement function:

    // -----------------------------------------------------------------------------
    // WordInvokeHelper
    //
    // Replacement for MFC COleDispatchDriver::InvokeHelper for this generated
    // Word wrapper.  It builds explicit VARIANTARG / DISPPARAMS structures and then
    // calls IDispatch::Invoke directly.  VARIANT* parameters that were previously
    // described by VTS_PVARIANT are copied as real VARIANTARG values via
    // VariantCopyInd instead of being forwarded as VT_VARIANT | VT_BYREF.  This is
    // the important compatibility workaround for Word late binding after KB5094126.
    // -----------------------------------------------------------------------------
    
    static int WordParamCount(const BYTE* pbParamInfo)
    {
    	int nCount = 0;
    
    	if (pbParamInfo == NULL)
    		return 0;
    
    	while (pbParamInfo[nCount] != 0)
    		++nCount;
    
    	return nCount;
    }
    
    static void WordFreeExcepInfo(EXCEPINFO* pExcepInfo)
    {
    	if (pExcepInfo == NULL)
    		return;
    
    	if (pExcepInfo->bstrSource)
    		SysFreeString(pExcepInfo->bstrSource);
    	if (pExcepInfo->bstrDescription)
    		SysFreeString(pExcepInfo->bstrDescription);
    	if (pExcepInfo->bstrHelpFile)
    		SysFreeString(pExcepInfo->bstrHelpFile);
    
    	pExcepInfo->bstrSource = NULL;
    	pExcepInfo->bstrDescription = NULL;
    	pExcepInfo->bstrHelpFile = NULL;
    }
    
    static HRESULT WordCopyVariantArgOrMissing(VARIANTARG* pDest, VARIANT* pSrc)
    {
    	VariantInit(pDest);
    
    	if (pSrc == NULL)
    	{
    		pDest->vt = VT_ERROR;
    		pDest->scode = DISP_E_PARAMNOTFOUND;
    		return S_OK;
    	}
    
    	return VariantCopyInd(pDest, pSrc);
    }
    
    static HRESULT WordBuildVariantArg(VARIANTARG* pArg, BYTE bParamType, va_list* pArgList)
    {
    	VariantInit(pArg);
    
    	if (bParamType == (BYTE)VTS_PVARIANT[0])
    	{
    		VARIANT* pVar = va_arg(*pArgList, VARIANT*);
    		return WordCopyVariantArgOrMissing(pArg, pVar);
    	}
    
    	if (bParamType == (BYTE)VTS_BSTR[0])
    	{
    		LPCTSTR psz = va_arg(*pArgList, LPCTSTR);
    		pArg->vt = VT_BSTR;
    
    		if (psz == NULL)
    		{
    			pArg->bstrVal = NULL;
    			return S_OK;
    		}
    
    		pArg->bstrVal = CString(psz).AllocSysString();
    		return pArg->bstrVal ? S_OK : E_OUTOFMEMORY;
    	}
    
    	if (bParamType == (BYTE)VTS_BOOL[0])
    	{
    		BOOL bValue = va_arg(*pArgList, BOOL);
    		pArg->vt = VT_BOOL;
    		pArg->boolVal = bValue ? VARIANT_TRUE : VARIANT_FALSE;
    		return S_OK;
    	}
    
    	if (bParamType == (BYTE)VTS_I4[0])
    	{
    		long lValue = va_arg(*pArgList, long);
    		pArg->vt = VT_I4;
    		pArg->lVal = lValue;
    		return S_OK;
    	}
    
    	if (bParamType == (BYTE)VTS_I2[0])
    	{
    		int nValue = va_arg(*pArgList, int);
    		pArg->vt = VT_I2;
    		pArg->iVal = (short)nValue;
    		return S_OK;
    	}
    
    	if (bParamType == (BYTE)VTS_R4[0])
    	{
    		double dblValue = va_arg(*pArgList, double);
    		pArg->vt = VT_R4;
    		pArg->fltVal = (float)dblValue;
    		return S_OK;
    	}
    
    	if (bParamType == (BYTE)VTS_DISPATCH[0])
    	{
    		LPDISPATCH pDisp = va_arg(*pArgList, LPDISPATCH);
    		pArg->vt = VT_DISPATCH;
    		pArg->pdispVal = pDisp;
    
    		if (pArg->pdispVal != NULL)
    			pArg->pdispVal->AddRef();
    
    		return S_OK;
    	}
    
    	return DISP_E_BADVARTYPE;
    }
    
    static HRESULT WordGetVariantValue(VARIANT* pDest, VARIANT* pSrc)
    {
    	VariantInit(pDest);
    
    	if (pSrc == NULL)
    		return E_POINTER;
    
    	return VariantCopyInd(pDest, pSrc);
    }
    
    static HRESULT WordStoreReturnValue(VARTYPE vtRet, void* pvRet, VARIANT* pVarResult)
    {
    	HRESULT hr = S_OK;
    	VARIANT value;
    	VARIANT converted;
    
    	if (vtRet == VT_EMPTY)
    	{
    		if (pVarResult != NULL)
    			VariantClear(pVarResult);
    		return S_OK;
    	}
    
    	if (pvRet == NULL || pVarResult == NULL)
    	{
    		if (pVarResult != NULL)
    			VariantClear(pVarResult);
    		return S_OK;
    	}
    
    	VariantInit(&value);
    	VariantInit(&converted);
    
    	hr = WordGetVariantValue(&value, pVarResult);
    	VariantClear(pVarResult);
    
    	if (FAILED(hr))
    		return hr;
    
    	switch (vtRet)
    	{
    	case VT_DISPATCH:
    		*((LPDISPATCH*)pvRet) = NULL;
    		if (value.vt == VT_DISPATCH)
    		{
    			*((LPDISPATCH*)pvRet) = value.pdispVal;
    			value.vt = VT_EMPTY;
    		}
    		VariantClear(&value);
    		return S_OK;
    
    	case VT_UNKNOWN:
    		*((LPUNKNOWN*)pvRet) = NULL;
    		if (value.vt == VT_UNKNOWN)
    		{
    			*((LPUNKNOWN*)pvRet) = value.punkVal;
    			value.vt = VT_EMPTY;
    		}
    		VariantClear(&value);
    		return S_OK;
    
    	case VT_VARIANT:
    	{
    		VARIANT* pOut = (VARIANT*)pvRet;
    		VariantInit(pOut);
    		*pOut = value;
    		VariantInit(&value);
    		return S_OK;
    	}
    
    	case VT_BSTR:
    		if (value.vt == VT_EMPTY || value.vt == VT_NULL)
    		{
    			*((CString*)pvRet) = _T("");
    			VariantClear(&value);
    			return S_OK;
    		}
    
    		hr = VariantChangeType(&converted, &value, 0, VT_BSTR);
    		if (SUCCEEDED(hr))
    		{
    			if (converted.bstrVal != NULL)
    				*((CString*)pvRet) = converted.bstrVal;
    			else
    				*((CString*)pvRet) = _T("");
    		}
    		VariantClear(&converted);
    		VariantClear(&value);
    		return hr;
    
    	case VT_BOOL:
    		hr = VariantChangeType(&converted, &value, 0, VT_BOOL);
    		if (SUCCEEDED(hr))
    			*((BOOL*)pvRet) = (converted.boolVal != VARIANT_FALSE);
    		VariantClear(&converted);
    		VariantClear(&value);
    		return hr;
    
    	case VT_I4:
    		hr = VariantChangeType(&converted, &value, 0, VT_I4);
    		if (SUCCEEDED(hr))
    			*((long*)pvRet) = converted.lVal;
    		VariantClear(&converted);
    		VariantClear(&value);
    		return hr;
    
    	case VT_I2:
    		hr = VariantChangeType(&converted, &value, 0, VT_I2);
    		if (SUCCEEDED(hr))
    			*((short*)pvRet) = converted.iVal;
    		VariantClear(&converted);
    		VariantClear(&value);
    		return hr;
    
    	case VT_R4:
    		hr = VariantChangeType(&converted, &value, 0, VT_R4);
    		if (SUCCEEDED(hr))
    			*((float*)pvRet) = converted.fltVal;
    		VariantClear(&converted);
    		VariantClear(&value);
    		return hr;
    
    	default:
    		VariantClear(&value);
    		return DISP_E_BADVARTYPE;
    	}
    }
    
    static void WordInvokeHelper(LPDISPATCH lpDispatch, DISPID dispid, WORD wFlags,
    	VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ...)
    {
    	int nArgCount = WordParamCount(pbParamInfo);
    	VARIANTARG* pArgs = NULL;
    	HRESULT hr = S_OK;
    	va_list argList;
    	DISPPARAMS dispParams;
    	DISPID dispidNamed = DISPID_PROPERTYPUT;
    	VARIANT varResult;
    	VARIANT* pVarResult = NULL;
    	EXCEPINFO excepInfo;
    	UINT argErr = 0;
    	int i;
    
    	if (lpDispatch == NULL)
    		AfxThrowOleException(E_POINTER);
    
    	if (nArgCount > 0)
    	{
    		pArgs = new VARIANTARG[nArgCount];
    		for (i = 0; i < nArgCount; ++i)
    			VariantInit(&pArgs[i]);
    	}
    
    	va_start(argList, pbParamInfo);
    	for (i = 0; i < nArgCount; ++i)
    	{
    		hr = WordBuildVariantArg(&pArgs[nArgCount - 1 - i], pbParamInfo[i], &argList);
    		if (FAILED(hr))
    			break;
    	}
    	va_end(argList);
    
    	if (FAILED(hr))
    	{
    		if (pArgs != NULL)
    		{
    			for (i = 0; i < nArgCount; ++i)
    				VariantClear(&pArgs[i]);
    			delete [] pArgs;
    		}
    		AfxThrowOleException(hr);
    	}
    
    	memset(&dispParams, 0, sizeof(dispParams));
    	dispParams.rgvarg = pArgs;
    	dispParams.cArgs = nArgCount;
    
    	if ((wFlags & DISPATCH_PROPERTYPUT) || (wFlags & DISPATCH_PROPERTYPUTREF))
    	{
    		dispParams.rgdispidNamedArgs = &dispidNamed;
    		dispParams.cNamedArgs = 1;
    	}
    
    	VariantInit(&varResult);
    	pVarResult = (vtRet == VT_EMPTY) ? NULL : &varResult;
    	memset(&excepInfo, 0, sizeof(excepInfo));
    
    	hr = lpDispatch->Invoke(
    		dispid,
    		IID_NULL,
    		LOCALE_USER_DEFAULT,
    		wFlags,
    		&dispParams,
    		pVarResult,
    		&excepInfo,
    		&argErr);
    
    	if (pArgs != NULL)
    	{
    		for (i = 0; i < nArgCount; ++i)
    			VariantClear(&pArgs[i]);
    		delete [] pArgs;
    	}
    
    	if (FAILED(hr))
    	{
    		WordFreeExcepInfo(&excepInfo);
    		if (pVarResult != NULL)
    			VariantClear(pVarResult);
    		AfxThrowOleException(hr);
    	}
    
    	WordFreeExcepInfo(&excepInfo);
    
    	hr = WordStoreReturnValue(vtRet, pvRet, pVarResult);
    	if (FAILED(hr))
    		AfxThrowOleException(hr);
    }
    
    static BOOL WordIsMissingVariant(VARIANT* pSrc)
    {
    	VARIANT value;
    	BOOL bMissing = FALSE;
    
    	if (pSrc == NULL)
    		return TRUE;
    
    	VariantInit(&value);
    	if (FAILED(VariantCopyInd(&value, pSrc)))
    		return FALSE;
    
    	bMissing = (value.vt == VT_ERROR && value.scode == DISP_E_PARAMNOTFOUND);
    	VariantClear(&value);
    	return bMissing;
    }
    
    static HRESULT WordCopyCloseQuitArg(VARIANTARG* pDest, VARIANT* pSrc, BOOL bSaveChanges)
    {
    	HRESULT hr;
    	VARIANT_BOOL boolValue;
    	short shortValue;
    
    	hr = WordCopyVariantArgOrMissing(pDest, pSrc);
    	if (FAILED(hr))
    		return hr;
    
    	/*
    		Word Document.Close/Application.Quit SaveChanges is an enum value.
    		When callers pass COleVariant(FALSE, VT_BOOL), pass VT_I4/0 to Word.
    		When callers pass COleVariant(TRUE, VT_BOOL), pass VT_I4/-1.
    	*/
    	if (bSaveChanges)
    	{
    		if (pDest->vt == VT_BOOL)
    		{
    			boolValue = pDest->boolVal;
    			VariantClear(pDest);
    			pDest->vt = VT_I4;
    			pDest->lVal = (boolValue != VARIANT_FALSE) ? -1L : 0L;
    		}
    		else if (pDest->vt == VT_I2)
    		{
    			shortValue = pDest->iVal;
    			VariantClear(pDest);
    			pDest->vt = VT_I4;
    			pDest->lVal = (long)shortValue;
    		}
    	}
    
    	return S_OK;
    }
    
    static void WordInvokeCloseQuit(LPDISPATCH lpDispatch, DISPID dispid,
    	VARIANT* SaveChanges, VARIANT* OriginalFormat, VARIANT* RouteDocument)
    {
    	VARIANTARG args[3];
    	VARIANT* srcArgs[3];
    	int nArgCount = 3;
    	int i;
    	HRESULT hr = S_OK;
    	DISPPARAMS dispParams;
    	EXCEPINFO excepInfo;
    	UINT argErr = 0;
    
    	/*
    		Close/Quit may be called more than once during cleanup.  If the
    		wrapper has already released its dispatch pointer via ReleaseDispatch(),
    		there is nothing left to close here.  Treat that case as success instead
    		of throwing E_POINTER.  This guard is intentionally limited to
    		Close/Quit and is not used by the general WordInvokeHelper.
    	*/
    	if (lpDispatch == NULL)
    		return;
    
    	/*
    		Do not send trailing Optional parameters to Word.  For calls such as
    		Close(FALSE, Optional, Optional) or Quit(FALSE, Optional, Optional), this
    		means cArgs == 1 and rgvarg[0] == SaveChanges.
    	*/
    	if (WordIsMissingVariant(RouteDocument))
    	{
    		nArgCount = 2;
    		if (WordIsMissingVariant(OriginalFormat))
    		{
    			nArgCount = 1;
    			if (WordIsMissingVariant(SaveChanges))
    				nArgCount = 0;
    		}
    	}
    
    	for (i = 0; i < 3; ++i)
    		VariantInit(&args[i]);
    
    	srcArgs[0] = SaveChanges;
    	srcArgs[1] = OriginalFormat;
    	srcArgs[2] = RouteDocument;
    
    	for (i = 0; i < nArgCount; ++i)
    	{
    		hr = WordCopyCloseQuitArg(&args[nArgCount - 1 - i], srcArgs[i], (i == 0));
    		if (FAILED(hr))
    			break;
    	}
    
    	if (FAILED(hr))
    	{
    		for (i = 0; i < 3; ++i)
    			VariantClear(&args[i]);
    		AfxThrowOleException(hr);
    	}
    
    	memset(&dispParams, 0, sizeof(dispParams));
    	dispParams.rgvarg = (nArgCount > 0) ? args : NULL;
    	dispParams.cArgs = nArgCount;
    
    	memset(&excepInfo, 0, sizeof(excepInfo));
    
    	hr = lpDispatch->Invoke(
    		dispid,
    		IID_NULL,
    		LOCALE_USER_DEFAULT,
    		DISPATCH_METHOD,
    		&dispParams,
    		NULL,
    		&excepInfo,
    		&argErr);
    
    	for (i = 0; i < 3; ++i)
    		VariantClear(&args[i]);
    
    	if (FAILED(hr))
    	{
    		WordFreeExcepInfo(&excepInfo);
    		AfxThrowOleException(hr);
    	}
    
    	WordFreeExcepInfo(&excepInfo);
    }
    

    Replace all InvokerHelper calls like this:

    From:

    LPDISPATCH Documents::Open(VARIANT* FileName, VARIANT* ConfirmConversions, VARIANT* ReadOnly, VARIANT* AddToRecentFiles, VARIANT* PasswordDocument, VARIANT* PasswordTemplate, VARIANT* Revert, VARIANT* WritePasswordDocument, VARIANT* WritePasswordTemplate, VARIANT* Format)`
    {
    	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;
    
    	InvokeHelper(0xc, DISPATCH_METHOD, VT_DISPATCH, (void*)&result, parms,
    		FileName, ConfirmConversions, ReadOnly, AddToRecentFiles, PasswordDocument, PasswordTemplate, Revert, WritePasswordDocument, WritePasswordTemplate, Format);
    	return result;
    }
    

    to

    LPDISPATCH Documents::Open(VARIANT* FileName, VARIANT* ConfirmConversions, VARIANT* ReadOnly, VARIANT* AddToRecentFiles, VARIANT* PasswordDocument, VARIANT* PasswordTemplate, VARIANT* Revert, VARIANT* WritePasswordDocument, VARIANT* WritePasswordTemplate, VARIANT* Format)
    {
    	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;
    	WordInvokeHelper(m_lpDispatch, 0xc, DISPATCH_METHOD, VT_DISPATCH, (void*)&result, parms,
    		FileName, ConfirmConversions, ReadOnly, AddToRecentFiles, PasswordDocument, PasswordTemplate, Revert, WritePasswordDocument, WritePasswordTemplate, Format);
    	return result;
    }
    

    In case of Documents.Close, Document.Close or Application.Quit, use the new helper like this:

    void Documents::Close(VARIANT* SaveChanges, VARIANT* OriginalFormat, VARIANT* RouteDocument)
    {
    	WordInvokeCloseQuit(m_lpDispatch, 0x451, SaveChanges, OriginalFormat, RouteDocument);
    }
    
    void _Document::Close(VARIANT* SaveChanges, VARIANT* OriginalFormat, VARIANT* RouteDocument)
    {
    	WordInvokeCloseQuit(m_lpDispatch, 0x451, SaveChanges, OriginalFormat, RouteDocument);
    }
    
    void _Application::Quit(VARIANT* SaveChanges, VARIANT* OriginalFormat, VARIANT* RouteDocument)
    {
    	WordInvokeCloseQuit(m_lpDispatch, 0x451, SaveChanges, OriginalFormat, RouteDocument);
    }
    

    HTH,

    Justin

    Was this answer helpful?

    6 people found this answer helpful.

  3. Jürgen Hillmann 5 Reputation points
    2026-06-16T12:33:05.7066667+00:00

    one problem remains Word is not closing ! @Justin

    Was this answer helpful?

    1 person found this answer helpful.

  4. Jürgen Hillmann 5 Reputation points
    2026-06-20T05:03:26.76+00:00

    one annotation:

    we were fine for generating one document in a loop after that we had the same error typeconflict

    after research we found out that Table generation did the problem

    oTables.Add() was fault, we had as last parameter vtOptional for autoformation , but it want's a true or false !!!!!!!

    bmks=oDoc.GetBookmarks();

    bmk=bmks.Item(COleVariant("Tabelle_01"));

    oRange = bmk.GetRange();

    oTables = oDoc.GetTables();

    if (anzahl>0)

    {

    			oTable =  oTables.Add(oRange,(long) anzahl,(long) 5,vtOptional**,vtTrue);  //** 
    
    			oBorders = oTable.GetBorders();
    

    ]

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.