Поделиться через


Вызов BITS из .NET и C# с помощью ссылочных библиотек DLL

Одним из способов вызова классов COM BITS из программы .NET является создание ссылочного DLL-файла на основе файлов IDL (язык определения интерфейсов) в пакете Windows SDK с помощью инструментов MIDL и TLBIMP. Ссылочная библиотека DLL — это набор оболочек для классов COM-классов BITS; затем можно использовать классы-оболочки непосредственно в .NET.

Альтернативой использованию автоматически созданных ссылочных библиотек DLL является использование сторонней оболочки .NET BITS из GitHub и NuGet. Эти оболочки часто имеют более естественный стиль программирования .NET, но они могут отстать от изменений и обновлений в интерфейсах BITS.

Создание файлов-оболочек DLL

Файлы BITS IDL

Вы начнете с набора IDL-файлов BITS. Это файлы, которые полностью определяют COM-интерфейс BITS. Файлы находятся в каталоге комплектов Windows и называются файлами bits версии.idl (например, bits10_2.idl), за исключением файла версии 1.0, который просто именуется Bits.idl. При создании новых версий BITS также создаются новые файлы IDL BITS.

Вы также можете изменить копию файлов IDL пакета SDK BITS, чтобы использовать функции BITS, которые не преобразуются автоматически в эквиваленты .NET. Возможные изменения файлов IDL рассматриваются позже.

Файлы IDL BITS включают несколько других файлов IDL по ссылке. Они также вложенные так, что при использовании одной версии она включает в себя все более ранние версии.

Для каждой версии BITS, которую вы хотите использовать в программе, потребуется одна ссылка на библиотеку DLL для этой версии. Например, если вы хотите написать программу, которая работает на BITS 1.5 и выше, но имеет дополнительные функции при наличии BITS 10.2, необходимо преобразовать как файлы bits1_5.idl, так и bits10_2.idl-файлы.

Служебные программы MIDL и TLBIMP

Программа MIDL (язык определения интерфейса Майкрософт) преобразует файлы IDL, описывающие COM-интерфейс BITS в файл TLB (библиотека типов). Средство MIDL зависит от программы CL (препроцессора C) для правильного чтения языкового файла IDL. Программа CL является частью Visual Studio и устанавливается при включении компонентов C/C++ в установку Visual Studio.

Программа MIDL обычно создает набор файлов C и H (языковой код C и заголовок языка C). Вы можете подавить эти дополнительные файлы, отправив выходные данные на устройство NUL. Например, установка переключателя /dlldata NUL: предотвращает создание файла dlldata.c. В приведенных ниже примерах команд показано, какие переключатели должны иметь значение NUL:.

Программа TLBIMP (импорт библиотек типов) считывает TLB-файл и создает соответствующий ссылочный DLL-файл.

Примеры команд для MIDL и TLBIMP

Это пример полного набора команд для создания набора ссылочных файлов. Возможно, вам потребуется изменить команды на основе вашей установки Visual Studio и Windows SDK, а также на основе компонентов BITS и целевых версий ОС, на которые вы нацеливаетесь.

В примере создается каталог для размещения ссылочных DLL-файлов и создается переменная среды BITSTEMP, указывающая на этот каталог.

Затем примеры команд запускают файл vsdevcmd.bat, созданный установщиком Visual Studio. Этот 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

После выполнения этих команд у вас будет набор ссылочных DLL-библиотек в каталоге BITSTEMP.

Добавление ссылочных библиотек DLL в проект

Чтобы использовать ссылку DLL в проекте C#, откройте проект C# в Visual Studio. В обозревателе решений щелкните правой кнопкой мыши ссылки и нажмите кнопку "Добавить ссылку". Затем нажмите кнопку "Обзор", а затем нажмите кнопку "Добавить". Перейдите в каталог со ссылкой на библиотеки DLL, выберите их и нажмите кнопку "Добавить". В окне диспетчера ссылок будут проверяться ссылочные библиотеки DLL. Нажмите кнопку "ОК".

Теперь в проект добавлены эталонные DLL-файлы BITS.

Сведения в ссылочных DLL-файлах будут внедрены в окончательную программу. Вам не нужно отправлять ссылочные dll-файлы с помощью программы; вам просто нужно отправить .EXE.

Можно изменить, внедрены ли ссылочные библиотеки DLL в окончательный EXE.. Используйте свойство Встраивание типов взаимодействия, чтобы определить, будут ли включены ссылочные библиотеки DLL. Это можно делать для каждой отдельной ссылки. Значение по умолчанию — True для внедрения библиотек DLL.

Изменение файлов IDL для более полного кода .NET

Файлы IDL BITS (язык определения интерфейса Майкрософт) можно использовать без изменений, чтобы сделать DLL-файл BackgroundCopyManager. Однако результирующая библиотека DLL для .NET будет содержать некоторые непереводимые объединения и иметь трудные для использования названия для некоторых структур и перечислений. В этом разделе описаны некоторые изменения, которые можно внести, чтобы сделать библиотеку DLL .NET более полной и удобной.

Более простые имена ENUM

Файлы IDL BITS определяют значения перечисления обычно следующим образом:

    typedef enum
    {
            BG_AUTH_TARGET_SERVER = 1,
            BG_AUTH_TARGET_PROXY
    } BG_AUTH_TARGET;

BG_AUTH_TARGET — это имя типа; само перечисление не имеет имени. Обычно это не приводит к проблемам с кодом 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.

Типы строк в объединениях

Файлы IDL BITS передают строки с помощью соглашения 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 не будет включено в определение класса .NET.

Обратите внимание, что объединение всегда определяется как BG_BASIC_CREDENTIALS независимо от BG_AUTH_SCHEME. Поскольку объединение не используется в качестве объединения, мы можем просто передать параметр BG_BASIC_CREDENTIALS.

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

Применение технологии BITS в C#

Настройка некоторых операторов using в C# уменьшит количество символов, которые необходимо ввести для использования различных версий 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;

Краткий пример: скачивание файла

Ниже приведен краткий фрагмент кода C# для скачивания файла из URL-адреса.

    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

В этом примере кода создается диспетчер BITS с именем mgr. Он напрямую соответствует интерфейсу IBackgroundCopyManager.

Руководитель создает новую рабочую позицию. Задание является параметром out в методе CreateJob. Кроме того, передается имя задания (которое не должно быть уникальным) и тип скачивания, который является заданием загрузки. Также заполняется BITS GUID для идентификатора задания.

После создания задания вы добавите в задание новый файл скачивания с помощью метода AddFile. Необходимо передать две строки, одну для удаленного файла (URL-адреса или общей папки) и одну для локального файла.

После добавления файла вызовите команду Возобновить выполнение задания, чтобы запустить его. Затем код ожидает, пока задание не достигнет конечного состояния («ERROR» или «TRANSFERRED»), после чего оно считается завершённым.

Версии BITS, приведение и запрос интерфейса

Вы найдете, что часто приходится использовать раннюю версию объекта BITS и более новую версию в программе.

Например, при создании объекта задания вы получите IBackgroundCopyJob (часть BITS версии 1.0), даже если вы используете более современную версию управляющего объекта, и доступен более современный объект IBackgroundCopyJob. Так как метод CreateJob не принимает интерфейс для более последней версии, вы не можете напрямую сделать более последнюю версию.

Используйте приведение .NET для преобразования из старого объекта типа в новый объект типа. Каст автоматически вызовет COM QueryInterface соответствующим образом.

В этом примере есть объект BITS IBackgroundCopyJob с именем job, и мы хотим преобразовать его в объект IBackgroundCopyJob5 с именем job5, чтобы можно было вызвать метод GETProperty BITS 5.0. Мы просто приводим к типу IBackgroundCopyJob5 вот так:

var job5 = (BITS5.IBackgroundCopyJob5)job;

Переменная job5 будет инициализирована в .NET с помощью правильного интерфейса QueryInterface.

Если ваш код может выполняться на системе, которая не поддерживает конкретную версию 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 невозможно напрямую создать объект BITS IBackgroundCopyManager с помощью интерфейсов 10.1 или 10.2. Вместо этого вам потребуется использовать несколько версий ссылочных файлов библиотеки DLL BackgroundCopyManager. Например, можно использовать версию 1.5 для создания объекта IBackgroundCopyManager, а затем приведения результирующего задания или файловых объектов с помощью версий 10.1 или 10.2.