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

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

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

Создание ссылочных БИБЛИОТЕК DLL

ФАЙЛЫ IDL BITS

Вы начнете с набора ФАЙЛОВ IDL BITS. Это файлы, которые полностью определяют COM-интерфейс BITS. Файлы находятся в каталоге Комплектов Windows и называются bits version.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: устройство. Например, установка NUL /dlldata: параметр подавляет создание файла 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.. Используйте свойство Embedded Interop Types, чтобы задать, будут ли внедрены ссылочные библиотеки 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). Строка); Метод.

Профсоюзы в структурах

Иногда профсоюзы, внедренные в структуры, не будут включены в структуру вообще. Например, в 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. Кроме того, передается имя задания (которое не должно быть уникальным) и тип скачивания, который является заданием загрузки. Также заполняется GUID BITS для идентификатора задания.

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

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

Версии BITS, приведение и запросInterface

Вы найдете, что часто приходится использовать раннюю версию объекта 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 с помощью правильного запросаInterface.

Если код может выполняться в системе, которая не поддерживает определенную версию 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.