使用參考 DLL 從 .NET 和 C# 呼叫 BITS

從 .NET 程式呼叫 BITS COM 類別的其中一種方式,就是使用 MIDLTLBIMP 工具,從 Windows SDK 中的 BITS IDL (介面定義語言) 檔案開始建立參考 DLL 檔案。 參考 DLL 是 BITS COM 類別的一組類別包裝函式;然後,您可以直接從 .NET 使用包裝函式類別。

使用自動建立參考 DLL 的替代方法是從 GitHubNuGet 使用第三方 .NET BITS 包裝函式。 這些包裝函式通常具有更自然的 .NET 程式設計樣式,但它們可能會落後於BITS介面中的變更和更新。

建立參考 DLL

BITS IDL 檔案

您將從一組 BITS IDL 檔案開始。 這些是完整定義 BITS COM 介面的檔案。 這些檔案位於 Windows Kits 目錄中,且命名為 bitsversion.idl (例如,bits10_2.idl),但 1.0 版檔案只是 Bits.idl。 建立新版本的 BITS 時,也會建立新的 BITS IDL 檔案。

您也可以修改 SDK BITS IDL 檔案的複本,以使用不會自動轉換成 .NET 對等專案的 BITS 功能。 稍後會討論可能的IDL檔案變更。

BITS IDL 檔案會依參考包含數個其他IDL檔案。 它們也會巢狀,因此如果您使用一個版本,它就會包含所有較低的版本。

針對您想要在程式中以為目標的每個 BITS 版本,您將需要該版本的一個參考 DLL。 例如,如果您想要撰寫可在BITS 1.5和更新上運作的程式,但在BITS 10.2存在時具有其他功能,則必須同時轉換bits1_5.idl和 bits10_2.idl 檔案。

MIDL 和 TLBIMP 公用程式

MIDL (Microsoft Interface Definition Language) 公用程式會將描述 BITS COM 介面的 IDL 檔案轉換成 TLB (類型連結庫) 檔案。 MIDL 工具取決於 CL 公用程式(C 預處理器)正確讀取 IDL 語言檔案。 CL 公用程式是 Visual Studio 的一部分,且會在 Visual Studio 安裝中包含 C/C++ 功能時安裝。

MIDL 公用程式通常會建立一組 C 和 H (C 語言代碼和 C 語言頭) 檔案。 您可以將輸出傳送至 NUL: 裝置,以隱藏這些額外的檔案。 例如,設定 /dlldata NUL:參數將會隱藏建立 dlldata.c 檔案。 下列範例命令顯示哪些參數應設定為 NUL:。

TLBIMP (類型連結庫匯入工具) 公用程式會在 TLB 檔案中讀取,並建立對應的參考 DLL 檔案。

MIDL 和 TLBIMP 的範例命令

這是用來產生一組參考檔的完整命令範例。 您可能需要根據 Visual Studio 和 Windows SDK 安裝,以及根據您的目標 BITS 功能和 OS 版本來修改命令。

此範例會建立目錄來放置參考 DLL 檔案,並建立環境變數 BITSTEMP 以指向該目錄。

然後,範例命令會執行 Visual Studio 安裝程式所建立的vsdevcmd.bat檔案。 此 BAT 檔案會設定您的路徑和一些環境變數,以便執行 MIDL 和 TLBIMP 命令。 它也會設定 WindowsSdkDir 和 WindowsSDKLibVersion 變數,以指向最新的 Windows SDK 目錄。

REM Create a working directory
REM You can select a different directory based on your needs.
SET BITSTEMP=C:\BITSTEMPDIR
MKDIR "%BITSTEMP%"

REM Run the VsDevCmd.bat file to locate the Windows
REM SDK directory and the tools directories
REM This will be different for different versions of
REM Visual Studio

CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\vsdevcmd.bat"

REM Run the MIDL command on the desired BITS IDL file
REM This will generate a TLB file for the TLBIMP command
REM The IDL file will be different depending on which
REM set of BITS interfaces you need to use.
REM Run the MIDL command once per reference file
REM that you will need to explicitly use.
PUSHD .
CD /D "%WindowsSdkDir%Include\%WindowsSDKLibVersion%um"

MIDL  /I ..\shared /out "%BITSTEMP%" bits1_5.idl /dlldata NUL: /header NUL: /iid NUL: /proxy NUL:
MIDL  /I ..\shared /out "%BITSTEMP%" bits4_0.idl /dlldata NUL: /header NUL: /iid NUL: /proxy NUL:
MIDL  /I ..\shared /out "%BITSTEMP%" bits5_0.idl /dlldata NUL: /header NUL: /iid NUL: /proxy NUL:
MIDL  /I ..\shared /out "%BITSTEMP%" bits10_1.idl /dlldata NUL: /header NUL: /iid NUL: /proxy NUL:
MIDL  /I ..\shared /out "%BITSTEMP%" bits10_2.idl /dlldata NUL: /header NUL: /iid NUL: /proxy NUL:

REM Run the TLBIMP command on the resulting TLB file(s)
REM Try to keep a parallel set of names.
TLBIMP "%BITSTEMP%"\bits1_5.tlb /out: "%BITSTEMP%"\BITSReference1_5.dll
TLBIMP "%BITSTEMP%"\bits4_0.tlb /out: "%BITSTEMP%"\BITSReference4_0.dll
TLBIMP "%BITSTEMP%"\bits5_0.tlb /out: "%BITSTEMP%"\BITSReference5_0.dll
TLBIMP "%BITSTEMP%"\bits10_1.tlb /out: "%BITSTEMP%"\BITSReference10_1.dll
TLBIMP "%BITSTEMP%"\bits10_2.tlb /out: "%BITSTEMP%"\BITSReference10_2.dll
DEL "%BITSTEMP%"\bits*.tlb
POPD

執行這些命令之後,您會在 BITSTEMP 目錄中有一組參考 DLL。

將參考 DLL 新增至專案

若要在 C# 專案中使用參考 DLL,請在 Visual Studio 中開啟您的 C# 專案。 在 方案總管 中,以滑鼠右鍵按兩下 [參考],然後按兩下 [新增參考]。 然後按下 [瀏覽] 按鈕,然後按下 [新增] 按鈕。 流覽至具有參考 DLL 的目錄,選取它們,然後按兩下[新增]。 在 [參考管理員] 視窗中,將會檢查參考 DLL。 然後按一下 [確定] 。

BITS 參考 DLL 現在會新增至您的專案。

參考 DLL 檔案中的資訊會內嵌至最終程式。 您不需要向程式寄送參考 DLL 檔案;您只需要寄送.EXE。

您可以變更參考 DLL 是否內嵌至最終 EXE。 使用 [內嵌 Interop 類型] 屬性來設定是否要內嵌參考 DLL。 這可以依據每個參考來完成。 預設值為 True 以內嵌 DLL。

修改IDL檔案以取得更完整的 .NET 程式代碼

BITS IDL (Microsoft 介面定義語言) 檔案可以保持不變,讓 BackgroundCopyManager DLL 檔案保持不變。 不過,產生的 .NET 參考 DLL 將會遺漏一些無法轉譯的等位,而且某些結構和列舉具有難以使用的名稱。 本節將說明一些您可以進行的變更,讓 .NET DLL 更完整且更容易使用。

更簡單的 ENUM 名稱

BITS IDL 檔案通常會定義如下的列舉值:

    typedef enum
    {
            BG_AUTH_TARGET_SERVER = 1,
            BG_AUTH_TARGET_PROXY
    } BG_AUTH_TARGET;

BG_AUTH_TARGET是 typedef 的名稱;實際的列舉未命名。 這通常不會造成 C 程式代碼的問題,但不適用於 .NET 程式。 系統會自動建立新的名稱,但看起來可能像是_MIDL___MIDL_itf_bits4_0_0005_0001_0001,而不是人類可讀取的值。 您可以藉由更新 MIDL 檔案以包含列舉名稱來修正此問題。

    typedef enum BG_AUTH_TARGET
    {
            BG_AUTH_TARGET_SERVER = 1,
            BG_AUTH_TARGET_PROXY
    } BG_AUTH_TARGET;

允許列舉名稱與 typedef 名稱相同。 有些程式設計人員的命名慣例會保留不同的命名慣例(例如,在列舉名稱前面加上底線),但這只會混淆 .NET 翻譯。

等位中的字串類型

BITS IDL 檔案會使用 LPWSTR 傳遞字串(寬字元字串的長指標)慣例。 雖然這會在傳遞函式參數時運作(例如 Job.GetDisplayName([out] LPWSTR *pVal) 方法),但是當字元串是聯集的一部分時,它將無法運作。 例如,bits5_0.idl 檔案包含BITS_FILE_PROPERTY_VALUE等位:

typedef [switch_type(BITS_FILE_PROPERTY_ID)] union
{
    [case( BITS_FILE_PROPERTY_ID_HTTP_RESPONSE_HEADERS )]
        LPWSTR String;
}
BITS_FILE_PROPERTY_VALUE;

LPWSTR 欄位不會包含在聯集的 .NET 版本中。 若要修正此問題,請將 LPWSTR 變更為 WCHAR*。 產生的欄位 (稱為 String) 會以 IntPtr 的形式傳遞。 使用 System.Runtime.InteropServices.Marshal.PtrToStringAuto(value)將此轉換成字符串。字串;方法。

結構中的聯集

有時候內嵌在結構中的等位完全不會包含在結構中。 例如,在 Bits1_5.idl 中,BG_AUTH_CREDENTIALS的定義如下:

    typedef struct
    {
        BG_AUTH_TARGET Target;
        BG_AUTH_SCHEME Scheme;
        [switch_is(Scheme)] BG_AUTH_CREDENTIALS_UNION Credentials;
    }
    BG_AUTH_CREDENTIALS;

BG_AUTH_CREDENTIALS_UNION定義為等位,如下所示:

    typedef [switch_type(BG_AUTH_SCHEME)] union
    {
            [case( BG_AUTH_SCHEME_BASIC, BG_AUTH_SCHEME_DIGEST, BG_AUTH_SCHEME_NTLM,
            BG_AUTH_SCHEME_NEGOTIATE, BG_AUTH_SCHEME_PASSPORT )] BG_BASIC_CREDENTIALS Basic;
            [default] ;
    } BG_AUTH_CREDENTIALS_UNION;

BG_AUTH_CREDENTIALS中的 [認證] 字段將不會包含在 .NET 類別定義中。

請注意,不論BG_AUTH_SCHEME為何,聯集都會定義為BG_BASIC_CREDENTIALS。 因為聯集不會當做等位使用,因此我們可以傳遞類似下列BG_BASIC_CREDENTIALS:

    typedef struct
    {
        BG_AUTH_TARGET Target;
        BG_AUTH_SCHEME Scheme;
        BG_BASIC_CREDENTIALS Credentials;
    }
    BG_AUTH_CREDENTIALS;

使用來自 C 的 BITS#

在 C# 中設定某些 using 語句會減少您需要輸入的字元數目,以使用不同的 BITS 版本。 名稱 「BITSReference」 來自參考 DLL 的名稱。

// Set up the BITS namespaces
using BITS = BITSReference1_5;
using BITS4 = BITSReference4_0;
using BITS5 = BITSReference5_0;
using BITS10_2 = BITSReference10_2;

快速範例:下載檔案

以下提供從 URL 下載檔案的簡短但完整 C# 代碼段。

    var mgr = new BITS.BackgroundCopyManager1_5();
    BITS.GUID jobGuid;
    BITS.IBackgroundCopyJob job;
    mgr.CreateJob("Quick download", BITS.BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out jobGuid, out job);
    job.AddFile("https://aka.ms/WinServ16/StndPDF", @"C:\Server2016.pdf");
    job.Resume();
    bool jobIsFinal = false;
    while (!jobIsFinal)
    {
        BITS.BG_JOB_STATE state;
        job.GetState(out state);
        switch (state)
        {
            case BITS.BG_JOB_STATE.BG_JOB_STATE_ERROR:
            case BITS.BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED:
                job.Complete();
                break;

            case BITS.BG_JOB_STATE.BG_JOB_STATE_CANCELLED:
            case BITS.BG_JOB_STATE.BG_JOB_STATE_ACKNOWLEDGED:
                jobIsFinal = true;
                break;
            default:
                Task.Delay(500); // delay a little bit
                break;
        }
    }
    // Job is complete

在此範例程式代碼中,會建立名為 mgr 的 BITS 管理員。 它直接對應至 IBackgroundCopyManager 介面。

從管理員建立新的作業。 作業是 CreateJob 方法上的 out 參數。 此外,傳入的是作業名稱(不需要是唯一的),也是下載類型,也就是下載作業。 作業標識碼的BITS GUID也會填入。

建立作業之後,您可以使用 AddFile 方法,將新的下載檔新增至作業。 您需要傳入兩個字串,一個用於遠端檔案(URL 或檔案共用),另一個用於本機檔案。

新增檔案之後,請在作業上呼叫 Resume 來啟動它。 然後程式代碼會等候作業處於最終狀態(ERROR 或 TRANSFERRED),然後完成。

BITS 版本、轉型和QueryInterface

您會發現,您通常必須使用早期版本的BITS對象和程式中的較新版本。

例如,當您建立作業物件時,即使使用較新的管理員物件,而且可以使用較新的IBackgroundCopyJob 物件,還是會取得IBackgroundCopyJob (BITS 1.0 版的一部分)。 因為 CreateJob 方法不接受較新版本的介面,因此您無法直接建立較新版本。

使用 .NET 轉換,從較舊的類型物件轉換成較新的類型物件。 轉換會視需要自動呼叫 COM QueryInterface。

在此範例中,有名為 「job」 的 BITS IBackgroundCopyJob 對象,我們想要將它轉換成名為 「job5」 的 IBackgroundCopyJob5 物件,以便我們可以呼叫 BITS 5.0 GetProperty 方法。 我們只是轉換成 IBackgroundCopyJob5 類型,如下所示:

var job5 = (BITS5.IBackgroundCopyJob5)job;

job5 變數將會使用正確的 QueryInterface,由 .NET 初始化。

如果您的程式代碼可能會在不支援特定 BITS 版本的系統上執行,您可以嘗試轉換並攔截 System.InvalidCastException。

BITS5.IBackgroundCopyJob5 job5 = null;
try
{
    job5 = (BITS5.IBackgroundCopyJob5)Job;
}
catch (System.InvalidCastException)
{
    ; // Must be running an earlier version of BITS
}

常見的問題是,當您嘗試轉換成錯誤的物件類型時。 .NET 系統不知道 BITS 介面之間的實際關聯性。 如果您要求錯誤的介面類型,.NET 會嘗試為您設定它,而且會因為 InvalidCastException 和 HResult 0x80004002 (E_NOINTERFACE) 而失敗。

使用 BITS 版本 10_1 和 10_2

在某些版本的 Windows 10 上,您無法使用 10.1 或 10.2 介面直接建立 BITS IBackgroundCopyManager 物件。 相反地,您必須使用多個版本的 BackgroundCopyManager DLL 參考檔案。 例如,您可以使用 1.5 版本建立 IBackgroundCopyManager 物件,然後使用 10.1 或 10.2 版本轉換產生的作業或檔案物件。