参照 DLL を使用した .NET および C# からの BITS への呼び出し

.NET プログラムから BITS COM クラスを呼び出す 1 つの方法は、MIDL ツールと TLBIMP ツールを使用して、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) という名前で、Bits.idl だけのバージョン 1.0 ファイルを除きます。 BITS の新しいバージョンが作成されると、新しい BITS IDL ファイルも作成されます。

また、SDK BITS IDL ファイルのコピーを変更して、同等の .NET に自動的に変換されない BITS 機能を使用することもできます。 IDL ファイルの変更については、後で説明します。

BITS IDL ファイルには、参照による他のいくつかの IDL ファイルが含まれています。 また、1 つのバージョンを使用する場合は、下位バージョンがすべて含まれるように、入れ子になります。

プログラムで対象とする BITS のバージョンごとに、そのバージョンの参照 DLL が 1 つ必要になります。 たとえば、BITS 1.5 以降で動作し、BITS 10.2 が存在する場合には追加機能を備えたプログラムを作成する場合は、bits1_5.idl ファイルと bits10_2.idl ファイルの両方を変換する必要があります。

MIDL および TLBIMP ユーティリティ

MIDL (Microsoft インターフェイス定義言語) ユーティリティは、BITS COM インターフェイスを記述する IDL ファイルを TLB (タイプ ライブラリ) ファイルに変換します。 MIDL ツールは、IDL 言語ファイルを正しく読み取るために CL ユーティリティ (C プリプロセッサ) に依存しています。 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 コマンドが実行できるようにパスといくつかの環境変数を設定します。 また、最新の Windows SDK ディレクトリを指すように WindowsSdkDir 変数と WindowsSDKLibVersion 変数も設定します。

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 がチェックされます。 OK をクリックします。

これで、BITS 参照 DLL がプロジェクトに追加されました。

参照 DLL ファイル内の情報は、最終的なプログラムに埋め込まれます。 参照 DLL ファイルをプログラムに同梱する必要はありません。.EXE を配布するだけです。

参照 DLL を最終的な EXE に埋め込むかどうかを変更できます。 [相互運用タイプの埋め込み] プロパティを使用して、参照 DLL を埋め込むかどうかを設定します。 これは、参照単位で実行できます。 DLL を埋め込む場合、既定値は True です。

より完全な .NET コードのための IDL ファイルの変更

BITS IDL (Microsoft Interface Definition Language) ファイルを変更せずに使用して、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.String); メソッドを使用してこれを文字列に変換します。

構造体内の共用体

構造体に埋め込まれている共用体が構造体にまったく含まれない場合があります。 たとえば、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 の 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 メソッドの出力パラメータです。 また、ジョブ名 (一意である必要はありません) とダウンロード ジョブであるダウンロード タイプも渡されます。 ジョブ識別子の BITS GUID も入力されます。

ジョブが作成されたら、AddFile メソッドを使用して新しいダウンロード ファイルをジョブに追加します。 リモート ファイル (URL またはファイル共有) 用とローカル ファイル用の 2 つの文字列を渡す必要があります。

ファイルを追加した後、ジョブで 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 を使用してキャストできます。