Chamando BITS do .NET e C# usando DLLs de referência

Uma maneira de chamar as classes BITS COM de um programa .NET é criar um arquivo DLL de referência começando com os arquivos IDL (Interface Definition Language) do BITS no SDK do Windows, usando as ferramentas MIDL e TLBIMP. A DLL de referência é um conjunto de wrappers de classe para as classes BITS COM; em seguida, você pode usar as classes wrapper diretamente do .NET.

Uma alternativa ao uso de DLLs de referência criadas automaticamente é usar um wrapper .NET BITS de terceiros do GitHub e do NuGet. Esses wrappers geralmente têm um estilo de programação .NET mais natural, mas podem ficar atrás das alterações e atualizações nas interfaces do BITS.

Criando as DLLs de referência

Arquivos IDL do BITS

Você começará com o conjunto de arquivos IDL do BITS. Estes são arquivos que definem totalmente a interface BITS COM. Os arquivos estão localizados no diretório Windows Kits e são chamados bitsversion.idl (por exemplo, bits10_2.idl), exceto para o arquivo versão 1.0 que é apenas Bits.idl. À medida que novas versões do BITS são criadas, novos arquivos IDL do BITS também são criados.

Você também pode modificar uma cópia dos arquivos IDL do SDK BITS para usar recursos do BITS que não são convertidos automaticamente em equivalentes do .NET. Possíveis alterações no arquivo IDL são discutidas mais adiante.

Os arquivos IDL do BITS incluem vários outros arquivos IDL por referência. Eles também aninham, de modo que, se você usar uma versão, ela inclui todas as versões inferiores.

Para cada versão do BITS que você deseja direcionar em seu programa, você precisará de uma DLL de referência para essa versão. Por exemplo, se você deseja escrever um programa que funcione no BITS 1.5 e superior, mas tenha recursos adicionais quando o BITS 10.2 estiver presente, será necessário converter os arquivos bits1_5.idl e bits10_2.idl.

Utilitários MIDL e TLBIMP

O utilitário MIDL (Microsoft Interface Definition Language) converte os arquivos IDL que descrevem a interface BITS COM em um arquivo TLB (Type Library). A ferramenta MIDL depende do utilitário CL (pré-processador C) para ler corretamente o arquivo de idioma IDL. O utilitário CL faz parte do Visual Studio e é instalado quando você inclui recursos C/C++ na instalação do Visual Studio.

O utilitário MIDL normalmente criará um conjunto de arquivos C e H (código de idioma C e cabeçalho de idioma C). Você pode suprimir esses arquivos extras enviando a saída para o dispositivo NUL:. Por exemplo, definir a opção /dlldata NUL: suprimirá a criação de um arquivo dlldata.c. Os comandos de exemplo abaixo mostram quais opções devem ser definidas como NUL:.

O utilitário TLBIMP (Type Library Importer) lê em um arquivo TLB e cria o arquivo DLL de referência correspondente.

Comandos de exemplo para MIDL e TLBIMP

Este é um exemplo do conjunto completo de comandos para gerar um conjunto de arquivos de referência. Talvez seja necessário modificar os comandos com base na instalação do Visual Studio e do SDK do Windows e com base nos recursos do BITS e nas versões do sistema operacional que você está direcionando.

O exemplo cria um diretório para colocar os arquivos DLL de referência e cria uma variável de ambiente BITSTEMP para apontar para esse diretório.

Os comandos de exemplo executam o arquivo vsdevcmd.bat criado pelo instalador do Visual Studio. Esse arquivo BAT configurará seus caminhos e algumas variáveis de ambiente para que os comandos MIDL e TLBIMP sejam executados. Ele também configura as variáveis WindowsSdkDir e WindowsSDKLibVersion para apontar para os diretórios mais recentes do SDK do Windows.

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

Depois que esses comandos forem executados, você terá um conjunto de DLLs de referência no diretório BITSTEMP.

Adicionando as DLLs de referência ao seu projeto

Para usar uma DLL de referência em um projeto C#, abra seu projeto C# no Visual Studio. No Gerenciador de Soluções, clique com o botão direito do mouse em Referências e clique em Adicionar Referência. Em seguida, clique no botão Procurar e, em seguida, no botão Adicionar. Navegue até o diretório com as DLLs de referência, selecione-as e clique em Adicionar. Na janela Gerenciador de referência, as DLLs de referência serão verificadas. Em seguida, clique em OK.

As DLLs de referência do BITS agora são adicionadas ao seu projeto.

As informações nos arquivos DLL de referência serão incorporadas ao seu programa final. Você não precisa enviar os arquivos DLL de referência com seu programa; você só precisa enviar o .EXE.

Você pode alterar se as DLLs de referência estão incorporadas no EXE final. Use a propriedade Embed Interop Types para definir se as DLLs de referência serão incorporadas ou não. Isso pode ser feito por referência. O padrão é True para incorporar as DLLs.

Modificando arquivos IDL para código .NET mais completo

Os arquivos IDL (Microsoft Interface Definition Language) do BITS podem ser usados inalterados para criar o arquivo DLL BackgroundCopyManager. No entanto, a DLL de referência .NET resultante estará faltando algumas uniões intraduzíveis e tem nomes difíceis de usar para algumas estruturas e enums. Esta seção descreverá algumas das alterações que você pode fazer para tornar a DLL do .NET mais completa e fácil de usar.

Nomes ENUM mais simples

Os arquivos IDL do BITS normalmente definem valores de enum da seguinte maneira:

    typedef enum
    {
            BG_AUTH_TARGET_SERVER = 1,
            BG_AUTH_TARGET_PROXY
    } BG_AUTH_TARGET;

O BG_AUTH_TARGET é o nome do typedef; o enum real não é nomeado. Isso normalmente não causa problemas com o código C, mas não é bem traduzido para uso com um programa .NET. Um novo nome será criado automaticamente, mas pode parecer algo como _MIDL___MIDL_itf_bits4_0_0005_0001_0001 em vez de um valor legível por humanos. Você pode corrigir esse problema atualizando os arquivos MIDL para incluir um nome de enum.

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

O nome enum pode ser o mesmo que o nome typedef. Alguns programadores têm uma convenção de nomenclatura em que eles são mantidos diferentes (por exemplo, colocando um sublinhado antes do nome enum), mas isso só confundirá as traduções do .NET.

Tipos de cadeia de caracteres em uniões

Os arquivos IDL do BITS passam cadeias de caracteres usando a convenção LPWSTR (ponteiro longo para cadeia de caracteres largos). Embora isso funcione ao passar parâmetros de função (como o método Job.GetDisplayName([out] LPWSTR *pVal), ele não funciona quando as cadeias de caracteres fazem parte de uniões. Por exemplo, o arquivo bits5_0.idl inclui a união 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;

O campo LPWSTR não será incluído na versão .NET da união. Para corrigir isso, altere o LPWSTR para um WCHAR*. O campo resultante (chamado String) será passado como um IntPtr. Converta isso em uma cadeia de caracteres usando o System.Runtime.InteropServices.Marshal.PtrToStringAuto(value. Corda); método.

Sindicatos em estruturas

Às vezes, os sindicatos que estão embutidos em estruturas não serão incluídos na estrutura. Por exemplo, no Bits1_5.idl o BG_AUTH_CREDENTIALS é definido da seguinte forma:

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

A BG_AUTH_CREDENTIALS_UNION é definida como uma união da seguinte forma:

    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;

O campo Credenciais no BG_AUTH_CREDENTIALS não será incluído na definição de classe .NET.

Observe que a união é definida para ser sempre um BG_BASIC_CREDENTIALS independentemente do BG_AUTH_SCHEME. Como o sindicato não é usado como sindicato, podemos apenas passar um BG_BASIC_CREDENTIALS como este:

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

Usando BITS de C#

Configurar algumas instruções de uso em C# reduzirá o número de caracteres que você precisa digitar para usar as diferentes versões do BITS. O nome "BITSReference" vem do nome da DLL de referência.

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

Exemplo rápido: baixar um arquivo

Um trecho curto, mas completo, do código C# para baixar um arquivo de uma URL é fornecido abaixo.

    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

Neste código de exemplo, um gerenciador BITS chamado mgr é criado. Ele corresponde diretamente à interface IBackgroundCopyManager .

A partir do gestor é criado um novo emprego. O trabalho é um parâmetro out no método CreateJob. Também é passado o nome do trabalho (que não precisa ser exclusivo) e o tipo de download, que é um trabalho de download. Um GUID BITS para o identificador de trabalho também é preenchido.

Depois que o trabalho é criado, você adiciona um novo arquivo de download ao trabalho com o método AddFile. Você precisa passar duas cadeias de caracteres, uma para o arquivo remoto (a URL ou compartilhamento de arquivos) e outra para o arquivo local.

Depois de adicionar o arquivo, chame Resume no trabalho para iniciá-lo. Em seguida, o código aguarda até que o trabalho esteja em um estado final (ERRO ou TRANSFERIDO) e, em seguida, seja concluído.

Versões do BITS, Casting e QueryInterface

Você descobrirá que muitas vezes precisa usar uma versão anterior de um objeto BITS e uma versão mais recente em seu programa.

Por exemplo, ao criar um objeto de trabalho, você obterá um IBackgroundCopyJob (parte do BITS versão 1.0) mesmo quando estiver usando um objeto de gerenciador mais recente e um objeto IBackgroundCopyJob mais recente estiver disponível. Como o método CreateJob não aceita uma interface para a versão mais recente, você não pode criar diretamente a versão mais recente.

Use uma conversão do .NET para converter de um objeto de tipo mais antigo para um objeto de tipo mais recente. O cast chamará automaticamente um COM QueryInterface conforme apropriado.

Neste exemplo, há um objeto IBackgroundCopyJob do BITS chamado "job" e queremos convertê-lo em um objeto IBackgroundCopyJob5 chamado "job5" para que possamos chamar o método GetProperty do BITS 5.0. Nós apenas transmitir para o tipo IBackgroundCopyJob5 assim:

var job5 = (BITS5.IBackgroundCopyJob5)job;

A variável job5 será inicializada pelo .NET usando o QueryInterface correto.

Se o código puder ser executado em um sistema que não ofereça suporte a uma versão específica do BITS, você poderá tentar a conversão e capturar o System.InvalidCastException.

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

Um problema comum é quando você tenta lançar no tipo errado de objeto. O sistema .NET não sabe sobre a relação real entre as interfaces BITS. Se você pedir o tipo errado de interface, o .NET tentará fazer isso para você e falhará com um InvalidCastException e HResult 0x80004002 (E_NOINTERFACE).

Trabalhando com as versões 10_1 e 10_2 do BITS

Em algumas versões do Windows 10, você não pode criar diretamente um objeto BITS IBackgroundCopyManager usando as interfaces 10.1 ou 10.2. Em vez disso, você terá que usar várias versões dos arquivos de referência da DLL do BackgroundCopyManager. Por exemplo, você pode usar a versão 1.5 para criar um objeto IBackgroundCopyManager e, em seguida, converter o trabalho resultante ou objetos de arquivo usando as versões 10.1 ou 10.2.