Applies ToOffice Products

症状

尝试使用 GetObject (Microsoft Visual Basic) 或 GetActiveObject (Microsoft Visual C++) 自动执行 Microsoft Office 应用程序时,即使 Office 应用程序正在运行,也会收到以下错误消息之一:

错误消息 1

运行时错误“429”: ActiveX 组件无法创建对象

错误消息 2

错误:0x800401e3“操作不可用”

原因

尽管 Office 应用程序正在运行,但它可能未在 Running Object Table (ROT) 中注册。 必须先在 ROT 中注册正在运行的 Office 应用程序实例,然后才能使用 GetObject (Visual Basic) 或 GetActiveObject (Visual C++) 附加到该实例。Office 应用程序启动时,它不会立即注册其正在运行的对象。 这会优化应用程序的启动过程。 Office 应用程序在失去焦点后,会在 ROT 中注册其正在运行的对象,而不是在启动时注册。 因此,如果在应用程序失去焦点之前尝试使用 GetObject 或 GetActiveObject 附加到正在运行的 Office 应用程序实例,则可能会收到上述错误之一。

解决方法

使用代码,可以将焦点从 Office 应用程序更改为你自己的应用程序 (或其他某个应用程序) ,以允许它在 ROT 中注册自身。 此外,如果代码正在启动 Office 应用程序的 exe 文件,可能需要等待 Office 应用程序完成加载,然后再尝试附加到正在运行的实例。 “更多信息”部分提供了代码示例作为解决方法。

状态

此行为是设计使然。

更多信息

在大多数情况下,想要自动执行 Office 应用程序的开发人员需要使用 CreateObject (Visual Basic) 或 CoCreateInstance (Visual C++) 来启动 Office 应用程序的新实例。但是,在某些情况下,你可能更希望自动执行已运行的 Office 应用程序:例如,如果用户之前启动了 Office 应用程序。 或者,如果使用代码启动 Office 应用程序的可执行文件,以便可以为应用程序指定命令行开关。 若要自动运行正在运行的 Office 应用程序,必须使用 GetObject 或 GetActiveObject。

重现行为的步骤

  1. 启动 Microsoft Visual Basic 并创建新的标准 EXE 项目。 默认情况下,将创建 Form1。

  2. 将 CommandButton 控件添加到 Form1。

  3. 将以下代码添加到窗体的代码模块。

    Private Sub Command1_Click()
        Dim oExcel As Object
        ' Launch a new instance of Microsoft Excel:
        Shell "C:\Program Files\Microsoft Office\Office\Excel.EXE", _
           vbMinimizedNoFocus
        ' An error 429 occurs on the following line:
        Set oExcel = GetObject(, "Excel.Application")
        MsgBox oExcel.Name
        Set oExcel = Nothing
    End Sub
    
  4. 请确保代码示例中 Excel.exe 的位置正确。

  5. 如果 Microsoft Excel 已在运行,请退出它。

  6. 按 F5 运行项目,然后单击 Command1。

解决方法

若要解决此问题,可以:

  • 通过将 Shell 函数的第二个参数更改为 vbMinimizedFocus、vbMaximizedFocus 或 vbNormalFocus,将焦点放在 Office 应用程序上。

  • 为 Visual Basic 窗体提供焦点。

  • 在考虑 Office 应用程序的加载时间时尝试 GetObject。

以下修订的代码演示了此解决方法。

Private Declare Sub Sleep Lib "kernel32" _
    (ByVal dwMilliseconds As Long)

Private Sub Command1_Click()
    Dim intSection As Integer
    Dim intTries As Integer
    Dim oExcel As Object

    ' Enable error handler for this procedure:
    On Error GoTo ErrorHandler

    ' Launch Microsoft Excel, giving it focus:
    Shell "C:\Program Files\Microsoft Office\Office\Excel.EXE", _
        vbMinimizedFocus 'other options for starting with
        'focus: vbMaximizedFocus and vbNormalFocus

    ' Move focus back to this form. (This ensures the Office
    '  application registers itself in the ROT, allowing
    '  GetObject to find it.)
    Me.SetFocus

    ' Attempt to use GetObject to reference the running
    '  Office application:
    intSection = 1 'attempting GetObject...
    Set oExcel = GetObject(, "Excel.Application")
    intSection = 0 'resume normal error handling

    ' Now you can automate Microsoft Excel:
    MsgBox oExcel.Name & ": able to GetObject after " & _
        intTries + 1 & " tries.", vbMsgBoxSetForeground

    ' Finished with automation so release your reference:
    Set oExcel = Nothing

    ' Exit procedure:
    Exit Sub

ErrorHandler:
    If intSection = 1 Then 'GetObject may have failed because the
    'Shell function is asynchronous; enough time has not elapsed
    'for GetObject to find the running Office application. Wait
    'wait 1/2 seconds and retry the GetObject. If you try 20 times
    'and GetObject still fails, assume some other reason
    'for GetObject failing and exit the procedure.
        intTries = intTries + 1
        If intTries < 20 Then
            Sleep 500 ' wait 1/2 seconds
            Resume 'resume code at the GetObject line
        Else
            MsgBox "GetObject still failing. Process ended.", _
                vbMsgBoxSetForeground
        End If
    Else 'intSection = 0 so use normal error handling:
        MsgBox Error$
    End If
End Sub

C++ 的解决方法

如果使用 C++ 进行编程,则以下代码示例演示了与上述 Visual Basic 示例中所示类似的解决方法。 请注意,SetForegroundWindow 用于将焦点从 Excel 移开,从而允许它注册其正在运行的对象。

//Store the handle of the currently active window...
HWND hwndCurrent = ::GetForegroundWindow();

//Launch Excel and wait until it is waiting for
//user input...
STARTUPINFO Start;
PROCESS_INFORMATION ProcInfo;
ZeroMemory(&Start,sizeof(STARTUPINFO));
Start.cb=sizeof(Start);
Start.dwFlags = STARTF_USESHOWWINDOW;
Start.wShowWindow = SW_SHOWMINIMIZED;

//Change the path to Excel as needed...
LPSTR pszExcelPath = 
      "c:\\program files\\microsoft office\\office\\excel.exe";

::CreateProcess(NULL, pszExcelPath, 0, 0, 1,
       NORMAL_PRIORITY_CLASS, 0, NULL, &Start, &ProcInfo);


if((::WaitForInputIdle(ProcInfo.hProcess, 10000))==WAIT_TIMEOUT)
{
    ::MessageBox(NULL, "Timed out waiting for Excel.", NULL,  
                 MB_OK);
}

//Restore the active window to the foreground...
//  NOTE: If you comment out this line, the code will fail!
::SetForegroundWindow(hwndCurrent);

//Initialize COM library...
::CoInitialize(NULL);

//Attach to the running instance...
CLSID clsid;
CLSIDFromProgID(L"Excel.Application", &clsid);  
IUnknown *pUnk = NULL;
IDispatch *pDisp = NULL;

for(int i=1;i<=5;i++) //try attaching for up to 5 attempts
{
   HRESULT hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);
   if(SUCCEEDED(hr)) 
   {
       hr = pUnk->QueryInterface(IID_IDispatch, (void **)&pDisp);
       break;
   }
   ::Sleep(1000);
}

if (!pDisp) {
    ::MessageBox(NULL, "Failed to find instance!!", "Error", 
                 MB_ICONHAND);
}
else {
    ::MessageBox(NULL, "Got instance of Excel!", "Success", MB_OK);
}

//Release the no-longer-needed IUnknown...
if (pUnk) 
    pUnk->Release();

//... Add your automation code for Excel here ...

//Release pDisp when no longer needed...
if (pDisp)
    pDisp->Release();

//Cleanup COM...
CoUninitialize();

参考

有关详细信息,请单击以下文章编号以查看文章:

如何查找 Office 应用程序的安装路径

需要更多帮助?

需要更多选项?

了解订阅权益、浏览培训课程、了解如何保护设备等。

社区可帮助你提出和回答问题、提供反馈,并听取经验丰富专家的意见。