共用方式為


Registration-Free COM 元件的啟用:逐步解說

 

Steve White
Microsoft UK 開發人員的頂級支援

Leslie Muller
全球 IT 研究 & 開發,Credit Suisse First Boston

2005 年 7 月

總結: Microsoft Platform SDK 會執行記錄 隔離應用程式和並存元件主題的絕佳工作。 不過,並非所有人都將本主題與 COM 元件的無註冊啟用相等。 無註冊 COM 是一項平臺功能,對於在共用基礎結構上隔離鎖定的伺服器和應用程式的企業非常感興趣。 本文逐步解說原生用戶端的原生 COM 元件無註冊啟用,以及透過受管理用戶端透過 COM Interop 啟用的工作範例。 (18 個列印頁面)

適用於:
   Microsoft Windows Server 2003
   Microsoft Windows XP
   Microsoft .NET Framework 1.1 版
   Microsoft Visual Studio .NET 2003
   Microsoft Visual Studio 6.0

下載本文隨附的範例, MSDNRegFreeCOM.msi

目錄

簡介
Registration-Free COM 術語
執行範例
建置 Visual C++ COM 伺服器
建置 C++/.NET 用戶端
Registration-Free啟用
Visual Basic 6.0 COM 伺服器和用戶端
不相容 Apartments
使用啟用內容 API
疑難排解
結論
深入閱讀

簡介

無註冊 COM 是 Microsoft Windows XP (SP2 上提供的機制。以 NET 為基礎的元件) 和 Microsoft Windows Server 2003 平臺。 如名稱所示,機制可讓您輕鬆地 (,例如,使用 XCOPY) 將 COM 元件部署至電腦,而不需要註冊它們。

在目標平臺上,初始化進程及其相依模組的其中一個階段是將任何相關聯的 資訊清單檔 載入記憶體結構,稱為 啟用內容。 如果沒有對應的登錄專案,這是一種啟用內容,可提供 COM 執行時間所需的系結和啟用資訊。 除非您選擇使用 啟用內容 API自行建置啟用內容,以省略檔案的使用方式,否則 COM 伺服器或用戶端中不需要任何特殊程式碼。

在本逐步解說中,我將建置簡單的原生 COM 元件,並從原生和受控用戶端取用它。 元件和原生用戶端會顯示在 Visual C++ 和 Visual Basic 6.0 中;Managed 用戶端會顯示在 C# 和 Visual Basic .NET 中。 您可以立即下載原始程式碼和範例,並立即查看它們,也可以遵循逐步解說並自行建置它們。

Registration-Free COM 術語

熟悉.NET Framework技術的任何人都習慣使用元件一詞,這代表一組部署、命名和版本設定為單位的模組,其中一個模組包含定義集合的資訊清單。 在無註冊 COM 中,會借用 元件資訊清單 詞彙,以取得概念類似但與 .NET 對應專案不相同的概念。

無註冊 COM 會使用 元件 來表示一組一或多個 PE 模組 (也就是原生 受控) 部署、具名和版本設定為單位。 無註冊 COM 會使用 資訊清單 來參考副檔名為 .manifest 副檔名的文字檔,其中定義 元件 (指令 清單) 的身分識別,以及其類別的系結和啟用詳細資料,或定義 應用程式 (應用程式 資訊清單) 的身分識別,以及一或多個元件識別參考。 元件資訊清單檔案會針對元件命名,而應用程式資訊清單檔則為應用程式命名。

並行 (SxS) 元件 一詞是指透過資訊清單檔設定相同 COM 元件的不同版本,以便由不同的執行緒同時載入它們,而不需要註冊。 SxS 啟用,且與 無註冊 COM的鬆散同義。

執行範例

下載並解壓縮範例程式碼之後,您會發現名為 \deployed的資料夾。 以下是用戶端應用程式的 Visual C++ 版本 (client.exe) 、其資訊清單 (client.exe.manifest) 、COM 伺服器的 Visual C++ 版本 (SideBySide.dll) 及其資訊清單 (SideBySide.X.manifest) 。 請繼續執行 client.exe。 預期的結果是,client.exe會啟動在SideBySide.dll) 中實作的SideBySideClass (實例,並顯示呼叫其Version方法的結果,看起來應該像 「1.0.0-CPP」。

建置 Visual C++ COM 伺服器

步驟 1

第一個步驟是建置 COM 伺服器。 在 Visual Studio 中,建立新的 Visual C++ ATL 專案,並將其命名為 SideBySide。 在 [ATL 專案精靈] 的 [ 應用程式設定 ] 索引標籤上,取消選取 [ 屬性] 核取方塊,然後選取 [ 允許合併 Proxy/存根程式碼 ] 核取方塊。

在方案總管中,以滑鼠右鍵按一下專案節點,然後選擇 [新增] |新增類別...。選取[ATL 簡單物件],然後選擇 [開啟]。 為類別提供 SideBySideClass 的簡短名稱,然後按一下 [ 完成]。

在 [類別檢視] 中,以滑鼠右鍵按一下 [ISideBySideClass ] 節點,然後選擇 [ 新增] |新增方法...。在 [新增方法精靈] 中,輸入 Version 作為方法名稱,選擇 BSTR*的參數類型,輸入 pVer 作為參數名稱,選取 [重試] 核取方塊,然後按一下 [ 新增 ],然後按一下 [ 完成]。

在 [類別檢視] 中,展開 CSideBySideClass 節點,然後按兩下 Version 方法。 將 // TODO 行取代為:

*pVer = SysAllocString(L"1.0.0-CPP");

產生發行組建,並將\release\SideBySide.dll複製到\deploy。

建置 C++/.NET 用戶端

下一個步驟是建置用戶端。 在逐步解說的這個部分中,您可以選擇建置 Visual C++ 或 Visual C++ COM 伺服器的 .NET 用戶端。 不需要說,可以混合和比對以 Visual C++、Visual Basic 6.0 和 .NET 撰寫的用戶端和伺服器。 如果您想要這麼做,您會發現範例會簡單修改,以便它們一起運作。 用戶端和伺服器的集合會依照本逐步解說中的組織方式,以呈現現成運作的程式碼。

步驟 2 (選項 A:Visual C++)

在與SideBySide專案資料夾相對的同層級資料夾中,建立名為client的新 Visual C++ Win32 主控台專案。 在 [Win32 應用程式精靈] 的 [ 應用程式設定 ] 索引標籤上,核取 [ 新增 ATL 支援 ] 核取方塊。

編輯 stdafx.h ,並在 檔案頂端緊接在 後面 #pragma once 新增下列這一行:

#define _WIN32_DCOM

此外,在 stdafx.h 中,于檔案底部新增下列這一行:

#import "..\deployed\SideBySide.dll" no_namespace

以下列程式碼取代 client.cpp 的內容:

#include "stdafx.h"
#include <iostream>
using namespace std;

void ErrorDescription(HRESULT hr)
{
    TCHAR* szErrMsg;
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
      FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
      (LPTSTR)&szErrMsg, 0, NULL) != 0)
   {
        cout << szErrMsg << endl;
        LocalFree(szErrMsg);
    }
   else
        cout << "Could not find a description for error 0x" 
          << hex << hr << dec << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
   CoInitializeEx(0, COINIT_MULTITHREADED);

   {
      ISideBySideClassPtr ptr;
      HRESULT hr = ptr.CreateInstance(__uuidof(SideBySideClass));
      if (SUCCEEDED(hr))
      {
         cout << ptr->Version() << endl;
      }
      ErrorDescription(hr);

      char c;
      cin >> c;
   }

   CoUninitialize();

   return 0;
}

產生發行組建,並將\release\client.exe複製到\deploy。

步驟 2 (選項 B:.NET Framework)

在 Visual Studio .NET 2003 中,在與SideBySide專案資料夾相對的同層級資料夾中,建立名為client的新 C# 或 Visual Basic .NET 主控台應用程式。 從 [新增參考] 對話方塊的[COM] 索引標籤,將參考新增至SideBySide 1.0 型別程式庫

Main 方法中貼上下列程式碼:

C# 程式碼

   SideBySideLib.ISideBySideClass obj = 
        new SideBySideLib.SideBySideClassClass();
   Console.WriteLine(obj.Version());
   Console.ReadLine();

Visual Basic .NET 程式碼

    Dim obj As SideBySideLib.ISideBySideClass = 
      New SideBySideLib.SideBySideClassClass
    Console.WriteLine(obj.Version())
    Console.ReadLine()

產生發行組建,並將client.exe複製到\deploy。

步驟 3

目前,除了某些中繼檔案之外, \deployed 資料夾應該包含,只有 client.exeSideBySide.dll,後者將會由其建置程式註冊。 若要檢查您的伺服器和用戶端在這些正常情況下是否一起運作,請執行 \deployed\client.exe 並記下預期的輸出 「1.0.0-CPP」。

步驟 4

本逐步解說是 關於無註冊 COM,因此我們現在需要取消註冊 SideBySide 元件。 在命令提示字元中,流覽至 \deployed 資料夾並執行命令: regsvr32 /u SideBySide.dll

步驟 5

若要查看上一個步驟的效果,請再次執行 \deployed\client.exe ,您會看到「類別未註冊」訊息。 在這個階段,我們感到挫折 COM 執行時間無法尋找登錄中所需的資訊,但我們尚未透過替代方式提供資訊。 我們將在下列步驟中解決此問題。

Registration-Free啟用

步驟 6

\deployed 資料夾中,建立應用程式資訊清單檔案, () client.exe 應用程式的文字檔,並將其呼叫 為client.exe.manifest。 將下列內容貼到 檔案中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
            type = "win32"
            name = "client"
            version = "1.0.0.0" />
<dependency>
            <dependentAssembly>
                        <assemblyIdentity
                                    type="win32"
                                    name="SideBySide.X"
                                    version="1.0.0.0" />
            </dependentAssembly>
</dependency>
</assembly>

步驟 7

\deployed 資料夾中,建立私人組件資訊清單檔案, () SideBySide.dll元件的文字檔 ,並呼叫 SideBySide.X.manifest。 將下列內容貼到 檔案中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">

<assemblyIdentity
   type="win32"
   name="SideBySide.X"
   version="1.0.0.0" />

<file name = "SideBySide.dll">

<comClass
    clsid="{[CLSID_SideBySideClass]}"
    threadingModel = "Apartment" />

<typelib tlbid="{[LIBID_SideBySide]}"
       version="1.0" helpdir=""/>

</file>

<comInterfaceExternalProxyStub 
    name="ISideBySideClass" 
    iid="{[IID_ISideBySideClass]}"
    proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
    baseInterface="{00000000-0000-0000-C000-000000000046}"
    tlbid = "{[LIBID_SideBySide]}" />

</assembly>

我已撰寫 GUID 值,其會以預留位置的形式呈現您的專案。 您可以在SideBySide 專案的 SideBySide.idl檔案中找到這些預留位置的個別值,或在 OLE/COM ObjectViewer (Oleview.exe) 工具中開啟SideBySide.dll

[
   object,
   uuid([IID_ISideBySideClass]),
   dual,
   nonextensible,
   helpstring("ISideBySideClass Interface"),
   pointer_default(unique)
]
interface ISideBySideClass : IDispatch{
   [id(1), helpstring("method Version")] HRESULT 
        Version([out,retval] BSTR* pVer);
};
[
   uuid([LIBID_SideBySide]),
   version(1.0),
   helpstring("SideBySide 1.0 Type Library")
]
library SideBySideLib
{
   importlib("stdole2.tlb");
   [
      uuid([CLSID_SideBySideClass]),
      helpstring("SideBySideClass Class")
   ]
   coclass SideBySideClass
   {
      [default] interface ISideBySideClass;
   };
};

步驟 8

此時,我應該將內嵌元件資訊清單檔案的主旨作為 Win32 資源。 在開發人員能夠和願意重建其 COM 元件的地方,建議您將元件資訊清單 (,例如上一個步驟中建立的資訊清單,) 會內嵌到 COM DLL 中,做為 windows.h) 中所定義之類型RT_MANIFEST (的 Win32 資源。 如果無法這樣做,請小心提供您的元件 (,因此您的元件資訊清單) 與 COM DLL 檔案名的名稱不同。 因此,在上述案例中,COM DLL 稱為 SideBySide ,但元件稱為 SideBySide.X。 如果您有興趣瞭解此條件約束的原因,則會在 疑難排解 一節中說明。 在此逐步解說中,元件資訊清單並未內嵌,以反映許多實際案例,因此無法執行。

步驟 9

若要驗證資訊清單檔案,您的用戶端再次能夠啟用 SideBySideClass 類別、執行 \deployed\client.exe 並記下預期的輸出 「1.0.0-CPP」。

Visual Basic 6.0 COM 伺服器和用戶端

步驟 1

第一個步驟是建置 COM 伺服器。 建立新的 Visual Basic 6.0 ActiveX DLL 專案。 在 [專案總管] 中,選取 Project1 節點,然後在 [ 屬性視窗] 中,將其名稱變更為 SideBySide。 在 [專案總管] 中,選取 Class1 節點,然後在 [屬性] 視窗中,將其名稱變更為 SideBySideClass

將下列副程式貼到程式碼視窗中:

Public Function Version()
Version = "1.0.0-VB6"
End Function

選擇 檔案 |製作SideBySide.dll... 並流覽至 \deployed 資料夾,然後按一下 [ 確定]。 若要防止每次建立 DLL 時產生新的 GUID,請選擇 [專案] |SideBySide 屬性...,然後按一下 [ 元件 ] 索引標籤,然後在 [版本相容性] 群組中選取 [ 二進位相容性] 選項按鈕。

步驟 2

建立新的 Visual Basic 6.0 Standard EXE 專案。 在 [專案總管] 中,選取 Project1 節點,然後在 [屬性] 視窗中,將其名稱變更為 用戶端。 選擇 檔案 |另存專案為 ,並將表單檔案和專案檔儲存在 與 SideBySide 專案資料夾相對的同層級資料夾中。 選擇 專案 |參考,選取 SideBySide旁的核取方塊,然後按一下 [ 確定]。

按兩下表單設計工具中的主表單,然後將下列程式碼貼到 Sub Form_Load () 中:

    Dim obj As New SideBySideClass
    MsgBox obj.Version()

選擇 檔案 |製作client.exe... 並流覽至 \deployed 資料夾,然後選擇 [ 確定]。

步驟 3

目前 ,\deployed 資料夾應該包含,除了某些中繼檔案之外,只有 client.exeSideBySide.dll;而後者會由其建置程式註冊。 若要檢查您的伺服器和用戶端在這些正常情況下是否一起運作,請執行 \deployed\client.exe 並記下預期的輸出 「1.0.0-VB6」。

步驟 4

本逐步解說是 關於無註冊 COM,因此我們現在需要取消註冊 SideBySide 元件。 在命令提示字元中,流覽至 \deployed 資料夾並執行命令: regsvr32 /u SideBySide.dll

步驟 5

若要查看上一個步驟的效果,請再次執行 \deployed\client.exe ,您會看到「執行階段錯誤 '429':ActiveX 元件無法建立物件」訊息。 在這個階段,我們感到挫折 COM 執行時間無法尋找登錄中所需的資訊,但我們尚未透過替代方式提供資訊。 我們將在下列步驟中解決此問題。

步驟 6

\deployed 資料夾中,建立應用程式資訊清單檔案, () client.exe 應用程式的文字檔,並將其呼叫 為client.exe.manifest。 將下列內容貼到 檔案中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
            type = "win32"
            name = "client"
            version = "1.0.0.0" />
<dependency>
            <dependentAssembly>
                        <assemblyIdentity
                                    type="win32"
                                    name="SideBySide.X"
                                    version="1.0.0.0" />
            </dependentAssembly>
</dependency>
</assembly>

步驟 7

\deployed 資料夾中,建立私人組件資訊清單檔案, () SideBySide.dll元件的文字檔 ,並呼叫 SideBySide.X.manifest。 將下列內容貼到 檔案中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">

<assemblyIdentity
   type="win32"
   name="SideBySide.X"
   version="1.0.0.0" />

<file name = "SideBySide.dll">

<comClass
    clsid="{[CLSID_SideBySideClass]}"
    threadingModel = "Apartment" />

<typelib tlbid="{[LIBID_SideBySide]}"
       version="1.0" helpdir=""/>

</file>

<comInterfaceExternalProxyStub 
    name="_SideBySideClass" 
    iid="{[IID__SideBySideClass]}"
    proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
    baseInterface="{00000000-0000-0000-C000-000000000046}"
    tlbid = "{[LIBID_SideBySide]}" />

</assembly>

我已撰寫 GUID 值,其會以預留位置的形式呈現您的專案。 您可以在 OLE/COM ObjectViewer (Oleview.exe) 工具中開啟 SideBySide.dll ,找到這些預留位置的個別值。

[
  uuid([LIBID_SideBySide]),
  version(1.0),
  custom(50867B00-BB69-11D0-A8FF-00A0C9110059, 8169)

]
library SideBySide
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-
C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _SideBySideClass;

    [
      odl,
      uuid([IID__SideBySideClass]),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _SideBySideClass : IDispatch {
        [id(0x60030000)]
        HRESULT Version([out, retval] VARIANT* );
    };

    [
      uuid([CLSID_SideBySideClass]),
      version(1.0)
    ]
    coclass SideBySideClass {
        [default] interface _SideBySideClass;
    };
};

步驟 8

若要確認,資訊清單檔案由資訊清單檔案提供,您的用戶端再次能夠啟用 SideBySideClass 類別、執行 \deployed\client.exe ,並記下預期的輸出 「1.0.0-VB6」。

不相容 Apartments

本逐步解說中的所有 COM 伺服器都是建置在Single-Threaded Apartment (中執行,也就是 STA 或 Apartment 執行緒元件) 。 除了 C++ 用戶端以外,所有用戶端都是 STA,這是 MTA (其執行緒會在多執行緒 Apartment) 中執行。 因此,有一種情況是用戶端和伺服器位於不相容的 Apartment 中,在此情況下,必須在 Proxy 與存根之間進行 COM 呼叫的跨 Apartment 封送處理。 此案例會使用元件資訊清單檔的下列區段:

...
<typelib tlbid="{[LIBID_SideBySide]}"
       version="1.0" helpdir=""/>

</file>

<comInterfaceExternalProxyStub 
    name="ISideBySideClass" 
    iid="{[IID_ISideBySideClass]}"
    proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
    baseInterface="{00000000-0000-0000-C000-000000000046}"
    tlbid = "{[LIBID_SideBySide]}" />
...

這些專案會提供在其他登錄中出現的資訊。 comInterfaceExternalProxyStub元素提供足夠的資訊以供類型程式庫封送處理發生,而且適用于衍生自 IDispatch (的 COM 介面,其中包含所有自動化介面) 。 在這些情況下 ,ole32.dll 提供 (所使用的外部 Proxy 存根,也就是元件) 中的檔案 外部 。 如果您的 COM 元件只實作 分派雙重 介面,則這是您應該使用的元素。

自訂介面比較罕見,因此您必須執行更多工作。 請考慮稱為 ISxSCustom 的介面,其衍生自 IUnknown 且與自動化不相容。 若要讓 SideBySideClass 實作 ISxSCustom,以及讓用戶端在無註冊案例中呼叫其方法,我需要將 comInterfaceProxyStub 元素新增至我的元件資訊清單。 如專案名稱所建議,這次 Proxy 存根不是外部專案:如果我在 ATL 專案精靈) 或在SideBySidePS.dll中核取 [允許合併 Proxy/stub 程式碼] 核取方塊,則會由SideBySide.dll提供 (。 如果我的 Proxy 存根合併,則新元素是現有 檔案 元素的子系:

<file name = "SideBySide.dll">
   ...
<comInterfaceProxyStub
name="ISxSCustom" 
iid="{[IID_ISxSCustom]}" />
</file>

否則,我需要新增宣告 Proxy 存根 dll 的 進一步 檔案專案:

<file name = "SideBySide.dll"> ... </file>
<file name = "SideBySidePS.dll">
<comInterfaceProxyStub
name="ISxSCustom" 
iid="{[IID_ISxSCustom]}" />
</file>

如果我不需要說明上述元素,我就會建置 Visual C++ COM 伺服器,使其 在用戶端的 MTA 中啟動其 Coclass。 如果用戶端和伺服器存在於相同的 Apartment 中,則會忽略剛才討論的專案。 不過,我鼓勵您不依賴此情況,因為在控制項之外可能會有元件在與其用戶端不同的 Apartment 中啟動的情況,即使其執行緒模型一致也一樣。 考慮所需的相對少量工作,我認為建議您 一律 在資訊清單中包含 Proxy 存根設定。

使用啟用內容 API

在 「 簡介 」一節中,我提到在本文適用的平臺上,初始化應用程式程式的其中一個階段是尋找應用程式資訊清單檔案。 應用程式資訊清單檔包含應用程式具有相依性的 元件 參考。 在 原生 COM 元件的無註冊啟用的情況下, 元件 所代表的是一組以通用身分識別和版本收集的一或多個 COM DLL。

如果您的應用程式知道 存留期期間可能直接或間接啟用的一組共同類別,則應用程式資訊清單會很方便。 但應用程式 (有相對罕見的類別,例如,在執行時間之前,格線伺服器) 不會知道它們將載入的模組。 在此情況下,呼叫進程初始化之後參考元件資訊清單檔案的方式。 啟用 內容 API會滿足這項作業,其使用方式會遮掩應用程式資訊清單檔案。 最簡單的方式是使用元件資訊清單檔案的位置初始化 ACTCTX 結構,然後從中建立並啟用 啟用內容 。 下列指示假設您已經建置步驟 2 (選項 A) 中所述的 Visual C++ 用戶端應用程式

編輯 stdafx.h ,並在 定義之後 _WIN32_DCOM 立即新增下列這一行:

#define _WIN32_FUSION 0x0100 // this causes activation context 
structs and APIs to be included.

如果您在client.cpp中查看_tmain函式,請在 COM 初始化和未初始化呼叫之間,您會看到可啟動並呼叫SideBySideClass的複合陳述式。 我們需要將這個複合陳述式 (大括弧) 程式碼的新區段 的所有專案,初始化啟用內容,如下所示:

   ACTCTX actCtx;
   memset((void*)&actCtx, 0, sizeof(ACTCTX));
   actCtx.cbSize = sizeof(ACTCTX);
   actCtx.lpSource = "SideBySide.X.manifest";

   HANDLE hCtx = ::CreateActCtx(&actCtx);
   if (hCtx == INVALID_HANDLE_VALUE)
      cout << "CreateActCtx returned: INVALID_HANDLE_VALUE" 
             << endl;
   else
   {
      ULONG_PTR cookie;
      if (::ActivateActCtx(hCtx, &cookie))
      {
         // previous compound statement goes here...
         ::DeactivateActCtx(0, cookie);
      }
   }

上述程式碼會在啟動任何 coclass 之前執行。 程式碼只會讀取元件資訊清單檔案, (此範例中名稱為硬式編碼,但應該對應到您想要動態載入的元件,) 載入到 (啟用內容,也就是讓目前的) 啟用。 從這一點開始,coclasses 的啟用就如同之前一樣發生。 您現在可以捨棄 client.exe.manifest

直接啟用內容 API 的替代方法,但僅適用于 Windows Server 2003,是 Microsoft.Windows.ActCtx 物件。

不需要說,啟用內容 API 會比我在這裡顯示更多,而且您可以在 進階閱讀 一節中找到完整 API 檔的連結。

疑難排解

如我們所見,COM 元件的無註冊啟用不需要在伺服器或用戶端中使用特殊程式碼。 所有必要專案都是相符的資訊清單檔。

我建議您以本逐步解說的方式,採用您自己的無註冊開發方式。 具體來說:請先查看用戶端使用已註冊的伺服器,以取得已知狀態;然後取消註冊伺服器,並確認您的錯誤訊息是您預期的訊息;最後,藉由製作和部署資訊清單檔案來補救這種情況。 如此一來,您針對無註冊啟用的疑難排解工作將會受限於資訊清單檔案的結構 (,如果您選擇這麼做,則會正確內嵌元件資訊清單) 。

針對無註冊的 COM 問題進行疑難排解時,Windows Server 2003 上的事件檢視器是您朋友。 當 Windows XP 或 Windows Server 2003 偵測到組態錯誤時,通常會顯示標題為已啟動之應用程式的錯誤訊息框,並包含「此應用程式因為應用程式設定不正確而無法啟動」訊息。 重新安裝應用程式可能會修正此問題。」建議您每當看到此訊息時,您會在 Windows Server 2003 上重現問題,請參閱系統事件記錄檔,並從 SideBySide 來源尋找事件。 我不建議在這些情況下查看 Windows XP 事件記錄檔,原因是它不一定會包含「產生 [path]\[應用程式檔名的啟用內容失敗」之類的訊息。清單。 參考錯誤訊息:作業已順利完成」,這無法協助識別問題。

各種資訊清單檔案的架構記載于平臺 SDK 標題的 [資訊清單檔案參考] 底下,而架構驗證工具 Manifestchk.vbs 可供使用,因此在這裡,我只會呼叫幾個與逐步解說相關的點。 首先,讓我們檢查元件資訊清單檔。 如需範例,請回頭查看步驟 7。

您應該記得,在 無註冊的 COM 意義中, 元件 是一種抽象概念,您可以利用 元件資訊清單 檔的內容來建立一或多個實體檔案的關聯。 元件的名稱會出現在三個位置,而且每個位置必須相同:在元件資訊清單檔assemblyIdentity元素的名稱屬性中;在應用程式資訊清單檔之dependentAssembly/assemblyIdentity元素的名稱屬性中,以及元件資訊清單檔本身的名稱,不包括.manifest副檔名。 如果資訊清單檔名稱不符合應用程式資訊清單中的名稱,則您會在 Windows Server 2003 系統事件記錄檔中看到下列訊息:「找不到應用程式資訊清單中名稱屬性的相依元件 [value of name屬性],且上次錯誤是未安裝在您的系統上。」如果元件資訊清單中的name元素不正確,您將會在 Windows Server 2003 系統事件記錄檔中看到下列訊息:「在資訊清單中找到的元件身分識別不符合所要求的元件身分識別」。

如果SideBySide.dll是以.NET Framework為基礎的元件,則我們已將元件資訊清單檔內嵌到SideBySide元件中作為 Win32 資源 (,而且我們會在 .NET 元件之後命名資訊清單檔,也就是SideBySide.manifest) 。 不過,由於元件載入器的搜尋順序,原生 COM 元件是 選擇性 的,可將資訊清單內嵌至模組。 在元件載入器尋找 [AssemblyName].manifest之前,它會尋找 [AssemblyName].dll, 並在其中搜尋類型為 RT_MANIFEST 的 Win32 資源。 資源內的組態必須具有符合[AssemblyName]AssemblyIdentity元素,以及應用程式資訊清單AssemblyIdentity參考中的其他屬性。

不過,如果找到 [AssemblyName].dll不包含 相符的資訊清單,則元件載入機制會停止, 而且不會 繼續尋找 [AssemblyName].manifest。 在撰寫本文時,這是 Windows XP 上的元件載入器 (這種情況會顯示其一般訊息:「應用程式組態不正確」) ,但在 Windows Server 2003 上則不會。 在 Windows Server 2003 上,即使它符合模組的名稱,搜尋 會繼續,而且 尋找資訊清單檔案。 不過,我鼓勵您不要依賴此行為。 相反地,為了確保您持續支援這兩個平臺,建議您盡可能將元件資訊清單內嵌在元件中作為RT_MANIFEST資源。 如果不可行,您必須為元件資訊清單檔案提供與相同資料夾中任何模組不同的名稱。 或者,將 COM 元件放在元件資訊清單資料夾的子資料夾中,並在元件資訊清單的 檔案 專案中參考這個子路徑。

assemblyIdentity元素會定義元件的識別。 針對原生 COM 元件,其 名稱 或其 版本 屬性都不需要符合任何實體檔案的元件,雖然最好套用某種一致性。

file元素是comClasstypelibcomInterfaceProxyStub元素的父代。 其目的是要找出組成 元件的實體檔案。 如果 檔案 元素 的名稱 屬性未正確參考檔案系統中的檔案, 則 CoCreateInstance 會傳回對應至「未註冊類別」或「找不到指定的模組」的 HRESULT。 因此,您可以在不同的資料夾中安裝不同版本的 COM 元件, 名稱 屬性可以包含路徑。 在 Windows XP SP2 上,路徑可以是相對或絕對路徑,而且可以在檔案系統中的任何位置參考資料夾。 Windows Server 2003 需要參考應用程式根資料夾或子資料夾的路徑,而且如果您嘗試違反此規則,則會在 Windows Server 2003 系統事件記錄檔中看到下列訊息:「資訊清單或原則檔 [元件資訊清單檔名] 中的語法錯誤 [...]值 [...] 無效。」

comClass元素只有一個必要屬性:clsid。 如果應用程式嘗試啟動未註冊的 coclass,其 CLSID 未列在元件資訊清單的 comClass 元素中, CoCreateInstance 會傳回具有值REGDB_E_CLASSNOTREG (0x80040154) 的 HRESULT,其郵件內文為「未註冊類別」。

如前所述,當用戶端和伺服器存在於不同的 Apartment 中時,需要typelib comInterface[External]ProxyStub元素,因此只有在處理這些元素時,才會看到下列錯誤。 這些元素中的組態錯誤會導致 CoCreateInstance 傳回對應至訊息「未註冊程式庫」、「載入類型程式庫/DLL 時發生錯誤」或「不支援這類介面」的 HRESULT。 如果您看到上述任何訊息,請仔細檢查您的 GUID,並確定所有必要屬性都存在。 您可以在平臺 SDK 中找到資訊清單檔架構。

現在讓我們將注意力轉向應用程式資訊清單檔案。 如需範例,請回頭查看步驟 6。 應用程式資訊清單 的格式必須為 [application filename].manifest。 因此,在逐步解說中,它會命名為 client.exe.manifest,讓它清楚知道每當client.exe載入至進程時應該讀取。 如果未正確完成此動作,CoCreateInstance 會傳回值為 REGDB_E_CLASSNOTREG (0x80040154) 的 HRESULT,也就是「未註冊類別」的郵件內文。

應用程式資訊清單中最重要的元素是dependentAssembly/assemblyIdentity元素。 這個專案是元件資訊清單中對等專案的參考,而兩者必須 完全相符。 確保這麼做的好方法是從元件資訊清單複製元素,並將其貼到此處。 如果有任何差異,您會在 Windows Server 2003 系統事件記錄檔中看到下列訊息:「在資訊清單中找到的元件身分識別不符合所要求的元件身分識別」。

結論

無註冊 COM 是一種技術,可從相依于 Windows 登錄的相依性中啟用 COM 元件,進而使使用這些元件的應用程式不需要專用伺服器。 它可讓具有相同 COM 元件不同版本相依性的應用程式共用基礎結構,並在.NET Framework版本設定和部署機制的回應中並存載入這些各種 COM 元件版本。

本文將逐步引導您示範原生用戶端應用程式以 Visual C++ 和 Visual Basic 6.0 和 Managed 用戶端撰寫的原生用戶端應用程式,以無註冊方式啟用原生 COM 元件。 其中說明機制的運作方式,並加上一些可能的組態錯誤底線,以及如何進行疑難排解。

深入閱讀

 

關於作者

Steve White 是一位應用程式開發顧問,在 Microsoft UK 的開發人員頂級支援小組中工作。 他支援使用 Visual C#、Windows Forms 和 ASP.NET 進行開發的客戶。 他的 部落格 提供有關他對於音樂、視覺效果和程式設計興趣的詳細資訊。

Leslie Muller 是一位技術工作者,與 Credit Suisse First Boston 研究 & 開發小組合作。 Leslie 擁有 12 年的開發人員和技術架構師經驗,在金融服務、技術創公司、工業自動化和工業自動化等環境中工作。 當他沒有進行程式設計或進行研究時,他喜歡使用雪球、雪球,以及盡可能在極端環境中,對馬達車輛執行稍微不雅的動作,例如加拿大或岩石。