Compartir a través de


Llamar a BITS desde .NET y C# mediante archivos DLL de referencia

Una manera de llamar a las clases COM de BITS desde un programa .NET es crear un archivo DLL de referencia a partir de los archivos IDL de BITS (lenguaje de definición de interfaz) en Windows SDK, mediante las herramientas MIDL y TLBIMP . El archivo DLL de referencia es un conjunto de contenedores de clases para las clases COM de BITS; Después, puede usar las clases contenedoras directamente desde .NET.

Una alternativa al uso de archivos DLL de referencia creados automáticamente es usar un contenedor .NET BITS de terceros desde GitHub y NuGet. Estos envoltorios suelen tener un estilo de programación de .NET más natural, pero pueden quedarse atrás con los cambios y actualizaciones en las interfaces de BITS.

Creación de los archivos DLL de referencia

Archivos IDL de BITS

Comenzará con el conjunto de archivos IDL de BITS. Estos son archivos que definen completamente la interfaz COM de BITS. Los archivos se encuentran en el directorio Windows Kits y se denominan bitsversion.idl (por ejemplo, bits10_2.idl), excepto el archivo de la versión 1.0, que es solo Bits.idl. A medida que se crean nuevas versiones de BITS, también se crean nuevos archivos IDL de BITS.

También puede modificar una copia de los archivos IDL de BITS del SDK para usar características de BITS que no se convierten automáticamente en equivalentes de .NET. Los posibles cambios en el archivo IDL se describen más adelante.

Los archivos IDL de BITS incluyen otros archivos IDL por referencia. También anidan, de modo que si usa una versión, incluye todas las versiones inferiores.

Para cada versión de BITS a la que desea dirigirse en el programa, necesitará un archivo DLL de referencia para esa versión. Por ejemplo, si desea escribir un programa que funcione en BITS 1.5 y versiones posteriores, pero tiene características adicionales cuando BITS 10.2 está presente, debe convertir los archivos bits1_5.idl y bits10_2.idl.

Utilidades MIDL y TLBIMP

La utilidad MIDL (Lenguaje de definición de interfaz de Microsoft) convierte los archivos IDL que describen la interfaz COM de BITS en un archivo TLB (biblioteca de tipos). La herramienta MIDL depende de la utilidad CL (preprocesador de C) para leer correctamente el archivo de lenguaje IDL. La utilidad CL forma parte de Visual Studio y se instala al incluir características de C/C++ en la instalación de Visual Studio.

La utilidad MIDL normalmente creará un conjunto de archivos C y H (código de lenguaje C y encabezado del lenguaje C). Puede suprimir estos archivos adicionales enviando la salida al dispositivo NUL: . Por ejemplo, al establecer el conmutador /dlldata NUL: se suprimirá la creación de un archivo dlldata.c. Los comandos de ejemplo siguientes muestran qué interruptores deben establecerse en NUL:.

La utilidad TLBIMP (Importador de biblioteca de tipos) lee en un archivo TLB y crea el archivo DLL de referencia correspondiente.

Comandos de ejemplo para MIDL y TLBIMP

Este es un ejemplo del conjunto completo de comandos para generar un conjunto de archivos de referencia. Es posible que tenga que modificar los comandos en función de la instalación de Visual Studio y Windows SDK y en función de las características de BITS y las versiones del sistema operativo que tenga como destino.

En el ejemplo se crea un directorio para colocar los archivos DLL de referencia y se crea una variable de entorno BITSTEMP para que apunte a ese directorio.

A continuación, los comandos de ejemplo ejecutan el archivo vsdevcmd.bat creado por el instalador de Visual Studio. Este archivo BAT configurará las rutas de acceso y algunas variables de entorno para que se ejecuten los comandos MIDL y TLBIMP. También configura las variables WindowsSdkDir y WindowsSDKLibVersion para que apunten a los directorios de Windows SDK más recientes.

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

Una vez ejecutados estos comandos, tendrá un conjunto de archivos DLL de referencia en el directorio BITSTEMP.

Adición de los archivos DLL de referencia al proyecto

Para usar un archivo DLL de referencia en un proyecto de C#, abra el proyecto de C# en Visual Studio. En el Explorador de soluciones, haga clic con el botón derecho en Referencias y haga clic en Agregar referencia. A continuación, haga clic en el botón Examinar y, a continuación, en el botón Agregar. Vaya al directorio con los archivos DLL de referencia, selecciónelos y haga clic en Agregar. En la ventana Administrador de referencias, se comprobarán los archivos DLL de referencia. A continuación, haga clic en Aceptar.

Los archivos DLL de referencia de BITS ahora se agregan al proyecto.

La información de los archivos DLL de referencia se incrustará en el programa final. No es necesario enviar los archivos DLL de referencia con el programa; sólo tienes que enviar el .EXE.

Puede cambiar si los archivos DLL de referencia se insertan en el EXE final. Utilice la propiedad Incrustar tipos de interoperabilidad para establecer si se incrustarán o no los archivos DLL de referencia. Esto se puede hacer de forma individual para cada referencia. El valor predeterminado es True para insertar los archivos DLL.

Modificación de archivos IDL para obtener código .NET más completo

Los archivos IDL de BITS (lenguaje de definición de interfaz de Microsoft) se pueden usar sin cambios para convertir el archivo DLL backgroundCopyManager. Sin embargo, el archivo DLL de referencia de .NET resultante faltará algunas uniones intranslables y tiene nombres difíciles de usar para algunas estructuras y enumeraciones. En esta sección se describen algunos de los cambios que puede realizar para que el archivo DLL de .NET sea más completo y fácil de usar.

Nombres ENUM más sencillos

Los archivos IDL de BITS suelen definir valores de enumeración como este:

    typedef enum
    {
            BG_AUTH_TARGET_SERVER = 1,
            BG_AUTH_TARGET_PROXY
    } BG_AUTH_TARGET;

El BG_AUTH_TARGET es el nombre del typedef; la enumeración propiamente dicha no está nombrada. Esto no suele causar problemas con código C, pero no se traduce bien para su uso con un programa .NET. Se creará automáticamente un nuevo nombre, pero podría parecerse a _MIDL___MIDL_itf_bits4_0_0005_0001_0001 en lugar de un valor comprensible para humanos. Para solucionar este problema, actualice los archivos MIDL para incluir un nombre de enumeración.

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

El nombre de enumeración puede ser el mismo que el nombre de typedef. Algunos programadores tienen una convención de nomenclatura en la que se mantienen diferentes (por ejemplo, colocando un carácter de subrayado antes del nombre de enumeración), pero esto solo confundirá las traducciones de .NET.

Tipos de cadena en uniones

Los archivos IDL de BITS pasan cadenas mediante la convención LPWSTR (puntero largo a cadena de caracteres anchos). Aunque esto funciona al pasar parámetros de función (como el método Job.GetDisplayName([out] LPWSTR *pVal), no funciona cuando las cadenas forman parte de uniones. Por ejemplo, el archivo bits5_0.idl incluye la unión 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;

El campo LPWSTR no se incluirá en la versión de .NET de la unión. Para corregirlo, cambie LPWSTR a WCHAR*. El campo resultante (denominado String) se pasará como intPtr. Convierta esto en una cadena mediante System.Runtime.InteropServices.Marshal.PtrToStringAuto(value). String); método.

Uniones en estructuras

A veces, las uniones incrustadas en estructuras no se incluirán en la estructura en absoluto. Por ejemplo, en el Bits1_5.idl, el BG_AUTH_CREDENTIALS se define de la siguiente manera:

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

El BG_AUTH_CREDENTIALS_UNION se define como una unión de la siguiente manera:

    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;

El campo Credenciales del BG_AUTH_CREDENTIALS no se incluirá en la definición de clase de .NET.

Tenga en cuenta que la unión se define para que siempre sea una BG_BASIC_CREDENTIALS independientemente del BG_AUTH_SCHEME. Dado que la unión no se usa como unión, podemos pasar un BG_BASIC_CREDENTIALS de la siguiente manera:

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

Uso de BITS en C#

La configuración de algunas instrucciones using en C# reducirá el número de caracteres que debe escribir para usar las distintas versiones de BITS. El nombre "BITSReference" procede del nombre del archivo DLL de referencia.

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

Ejemplo rápido: descargar un archivo

A continuación se muestra un fragmento de código de C# corto pero completo para descargar un archivo de una dirección 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

En este código de ejemplo, se crea un administrador de BITS denominado mgr. Corresponde directamente a la interfaz IBackgroundCopyManager .

Desde el administrador se crea un nuevo trabajo. El trabajo es un parámetro out en el método CreateJob. También se pasa el nombre de la tarea (que no tiene que ser único) y el tipo de descarga, que es una tarea de descarga. También se rellena un GUID de BITS para el identificador de trabajo.

Una vez creado el trabajo, agregue un nuevo archivo de descarga al trabajo con el método AddFile. Debe pasar dos cadenas, una para el archivo remoto (la dirección URL o el recurso compartido de archivos) y otra para el archivo local.

Después de agregar el archivo, ejecute la función Resume en la tarea para iniciarla. A continuación, el código espera hasta que el trabajo se encuentra en un estado final (ERROR o TRANSFERIDO) y luego finaliza.

Versiones de BITS, conversión de tipos y QueryInterface

Verá que a menudo tiene que usar una versión temprana de un objeto BITS y una versión más reciente en el programa.

Por ejemplo, al crear un objeto de trabajo, obtendrá un IBackgroundCopyJob (parte de la versión 1.0) de BITS incluso cuando lo esté haciendo con un objeto de administrador más reciente y un objeto IBackgroundCopyJob más reciente está disponible. Dado que el método CreateJob no acepta una interfaz para la versión más reciente, no se puede hacer directamente la versión más reciente.

Utiliza una conversión de .NET para convertir un objeto de tipo antiguo a un objeto de tipo más nuevo. La conversión llamará automáticamente a COM "QueryInterface" según corresponda.

En este ejemplo, hay un objeto BITS IBackgroundCopyJob denominado "job" y queremos convertirlo en un objeto IBackgroundCopyJob5 denominado "job5" para que podamos llamar al método GETProperty de BITS 5.0. Acabamos de convertir al tipo IBackgroundCopyJob5 como este:

var job5 = (BITS5.IBackgroundCopyJob5)job;

.NET inicializará la variable job5 mediante queryInterface correcta.

Si el código se puede ejecutar en un sistema que no admite una versión determinada de BITS, puede probar la conversión y detectar system.InvalidCastException.

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

Un problema común es cuando se intenta convertir en el tipo incorrecto de objeto. El sistema .NET no conoce la relación real entre las interfaces BITS. Si solicita el tipo incorrecto de interfaz, .NET intentará crearlo para usted y fallará con una InvalidCastException con HResult 0x80004002 (E_NOINTERFACE).

Trabajar con BITS Versiones 10_1 y 10_2

En algunas versiones de Windows 10 no puedes crear directamente un objeto BITS IBackgroundCopyManager mediante las interfaces 10.1 o 10.2. En su lugar, tendrá que usar varias versiones de los archivos de referencia dll backgroundCopyManager. Por ejemplo, puede usar la versión 1.5 para crear un objeto IBackgroundCopyManager y, a continuación, convertir los objetos de archivo o trabajo resultantes mediante las versiones 10.1 o 10.2.