Compartir a través de


Extensiones de impresora

Importante

Se recomienda usar el controlador de clase de bandeja de entrada IPP de Microsoft, junto con las aplicaciones de soporte técnico de impresión (PSA), para personalizar la experiencia de impresión en Windows 10 y 11 para el desarrollo de dispositivos de impresora.

Para obtener más información, consulte la Guía de diseño de aplicaciones de soporte técnico de impresión.

Las aplicaciones de extensión de impresora admiten preferencias de impresión y notificaciones de impresora cuando los usuarios ejecutan aplicaciones existentes en el escritorio de Windows.

Las extensiones de impresora se pueden compilar en cualquier lenguaje compatible con COM, pero están optimizadas para compilarse con Microsoft .NET Framework 4. Las extensiones de impresora se pueden distribuir con un paquete de controladores de impresión, si son compatibles con XCopy y no tienen dependencias en entornos de ejecución externos distintos de los incluidos en el sistema operativo, por ejemplo, .NET. Si la aplicación de extensión de impresora no cumple estos criterios, podría distribuirse en un setup.exe o en un paquete MSI y anunciarse en la experiencia de fase de dispositivo de la impresora mediante la directiva PrinterExtensionUrl especificada en el manifiesto v4. Cuando una aplicación de extensión de impresora se distribuye a través de un paquete MSI, tiene la opción de agregar el controlador de impresión al paquete o dejarla fuera y distribuir el controlador por separado. PrinterExtensionUrl se muestra en la experiencia de preferencias de impresora.

Los administradores de TI tienen algunas opciones para administrar la distribución de extensiones de impresora. Si la aplicación se empaqueta en un setup.exe o MSI, los administradores de TI pueden usar herramientas de distribución de software estándar, como Microsoft Endpoint Configuration Manager, o pueden incluir las aplicaciones en su imagen estándar del sistema operativo. Los administradores de TI también pueden invalidar printerExtensionUrl especificado en el manifiesto v4, si editan HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\nombre> de <la cola de impresión\PrinterDriverData\PrinterExtensionUrl.

Y si una empresa decide bloquear por completo las extensiones de impresora, esto se puede hacer a través de una directiva de grupo denominada "Configuración del equipo\Plantillas administrativas\Impresoras\No permitir que los controladores de impresora v4 muestren aplicaciones de extensión de impresora".

Creación de una extensión de impresora

El ejemplo de extensión de impresora en GitHub muestra cómo compilar una extensión de impresora mediante C#. Para permitir el uso compartido de código entre las aplicaciones de dispositivo UWP y las extensiones de impresora, este ejemplo usa dos proyectos: PrinterExtensionLibrary (una C) y ExtensionSample (una extensión de impresora que depende de PrinterExtensionLibrary).

Los fragmentos de código que se muestran en este artículo se toman de la solución PrinterExtensionSample. Si va a compilar una extensión de impresora en C, C++ o algún otro lenguaje basado en COM, los conceptos son similares, pero las API deben coincidir con las especificadas en PrinterExtension.IDL, que se incluye en el Kit de controladores de Windows. Los comentarios de código de PrinterExtensionLibrary del documento de ejemplo también incluyen comentarios de código que indican la interfaz COM subyacente a la que corresponde un objeto determinado.

Al desarrollar una extensión de impresora, hay seis áreas principales de enfoque que debe tener en cuenta. Estas áreas de enfoque se muestran en la lista siguiente.

  • Registro

  • Habilitación de eventos

  • Controlador OnDriverEvent

  • Preferencias de impresión

  • Notificaciones de impresora

  • Administración de impresoras

Registro

Las extensiones de impresora se registran con el sistema de impresión especificando un conjunto de claves del Registro o especificando la información de la aplicación en la sección PrinterExtensions del archivo de manifiesto v4.

Hay GUID especificados que admiten cada uno de los distintos puntos de entrada para las extensiones de impresora. No es necesario usar estos GUID en el archivo de manifiesto v4, pero debe conocer los valores GUID para usar el formato del Registro para la instalación del controlador v4. En la tabla siguiente se muestran los valores GUID de los dos puntos de entrada.

Punto de entrada GUID
Preferencias de impresión {EC8F261F-267C-469F-B5D6-3933023C29CC}
Notificaciones de impresora {23BB1328-63DE-4293-915B-A6A23D929ACB}

Las extensiones de impresora instaladas fuera del controlador de impresora deben registrarse mediante el Registro. Esto garantiza que las extensiones de impresora se pueden instalar independientemente del estado del colador o del módulo de configuración v4 en el equipo cliente.

Una vez que se inicie el servicio PrintNotify, comprobará las claves del Registro en la ruta de acceso [OfflineRoot] y procesará los registros pendientes o anulará el registro. Una vez completados los registros pendientes o anulados, las claves del Registro se eliminan en tiempo real. Si usa un script o un proceso iterativo para colocar claves del Registro, es posible que tenga que volver a crear la clave \[PrinterExtensionID] cada vez que especifique una clave \[PrinterDriverId]. Las claves incompletas o con formato incorrecto no se eliminan.

Este registro solo es necesario en la primera instalación. En el ejemplo siguiente se muestra el formato de clave del Registro correcto que se usa para registrar extensiones de impresora.

Nota

[OfflineRoot] se usa como abreviatura para HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\OfflinePrinterExtensions.

[OfflineRoot]
    \[PrinterExtensionId] {GUID}
           AppPath=[PrinterExtensionAppPath] {String}
           \[PrinterDriverId] {GUID}
                  \[PrinterExtensionReasonGuid]
(default) = ["0"|"1"] {REG_SZ 0:Unregister, 1:Register}
                  \…
                  \[PrinterExtensionReasonGuidN]
           \[PrinterDriverId2]
                  \[PrinterExtensionReasonGuid2.1]
                  \…
                  \[PrinterExtensionReasonGuid2.Z]
           …
           \[PrinterDriverIdM]
    \[PrinterExtensionId2]
    …
    \[PrinterExtensionIdT]

Por ejemplo, el siguiente conjunto de claves registraría una extensión de impresora con el {PrinterExtensionIDGuid} PrinterExtensionID y una ruta de acceso completa al ejecutable "C:\Program Files\Fabrikam\pe.exe" para el archivo ejecutable {PrinterDriverID1Guid} y {PrinterDriverID2Guid} PrinterDriverIDs, con las preferencias de impresora y los motivos de las notificaciones de impresora.

[OfflineRoot]
    \{PrinterExtensionIDGuid}
           AppPath="C:\Program Files\Fabrikam\pe.exe"
           \{PrinterDriverID1Guid}
                 \{EC8F261F-267C-469F-B5D6-3933023C29CC}
            (default) = "1"
                 \{23BB1328-63DE-4293-915B-A6A23D929ACB}
            (default) = "1"
           \{PrinterDriverID1Guid}
                 \{EC8F261F-267C-469F-B5D6-3933023C29CC}
            (default) = "1"
                 \{23BB1328-63DE-4293-915B-A6A23D929ACB}
            (default) = "1"

Para desinstalar la misma extensión de impresora, se debe especificar el siguiente conjunto de claves.

[OfflineRoot]
    \{PrinterExtensionIDGuid}
           AppPath="C:\Program Files\Fabrikam\pe.exe"
           \{PrinterDriverID1Guid}
                 \{EC8F261F-267C-469F-B5D6-3933023C29CC}
            (default) = "0"
                 \{23BB1328-63DE-4293-915B-A6A23D929ACB}
            (default) = "0"
           \{PrinterDriverID1Guid}
                 \{EC8F261F-267C-469F-B5D6-3933023C29CC}
            (default) = "0"
                 \{23BB1328-63DE-4293-915B-A6A23D929ACB}
            (default) = "0"

Dado que las extensiones de impresora se pueden ejecutar tanto en un contexto iniciado por el usuario como en un contexto iniciado por eventos, resulta útil poder determinar el contexto en el que funciona la extensión de impresora. Esto puede permitir que una aplicación no enumere el estado en todas las colas si se ha iniciado para una notificación o preferencias de impresión. Microsoft recomienda que las extensiones de impresora que se instalen por separado del controlador (por ejemplo, con msi o setup.exe) deben usar modificadores de línea de comandos en los accesos directos del menú Inicio o en la entrada AppPath que se ha rellenado en el Registro durante el registro. Puesto que las extensiones de impresora instaladas con el controlador se instalan en DriverStore, estas no se iniciarán fuera de las preferencias de impresión ni los eventos de notificaciones de impresora. Por lo tanto, no se admite la especificación de modificadores de línea de comandos en este caso.

Cuando la extensión de impresora se registra para el PrinterDriverID actual, debe incluir PrinterDriverID en AppPath. Por ejemplo, para una aplicación de extensión de impresora con el nombre printerextension.exey un valor PrinterDriverID de {GUID}, [PrinterExtensionAppPath] tendría el siguiente aspecto:

"C:\program files\fabrikam\printerextension.exe {GUID}"

Habilitación de eventos

En tiempo de ejecución, las extensiones de impresora deben habilitar el desencadenador de eventos para printerDriverID actual. Este es el PrinterDriverID que se pasó a la aplicación a través de la matriz args[] y permite al sistema de impresión proporcionar un contexto de evento adecuado para controlar razones como preferencias de impresión o notificaciones de impresora.

Por lo tanto, la aplicación debe crear un nuevo PrinterExtensionManager para el printerDriverID actual, registrar un delegado para controlar el evento OnDriverEvent y llamar al método EnableEvents con printerDriverID. El siguiente fragmento de código ilustra este enfoque.

PrinterExtensionManager mgr = new PrinterExtensionManager();
mgr.OnDriverEvent += OnDriverEvent;
mgr.EnableEvents(new Guid(PrinterDriverID1));

Si una aplicación no llama a EnableEvents en un plazo de 5 segundos, Windows expirará e iniciará una interfaz de usuario estándar. Para mitigar esto, las extensiones de impresora deben seguir los procedimientos recomendados de rendimiento más recientes, incluidos los siguientes:

  • Retrase tanto como sea posible la inicialización de la aplicación, hasta después de llamar a EnableEvents. Después de esto, priorice la capacidad de respuesta de la interfaz de usuario mediante métodos asincrónicos y no bloquee el subproceso de la interfaz de usuario durante la inicialización.

  • Use ngen para generar una imagen nativa durante la instalación. Para obtener más información, consulte Generador de imágenes nativas.

  • Use herramientas de medición de rendimiento para encontrar problemas de rendimiento al cargar. Para obtener más información, vea Herramientas de análisis de rendimiento de Windows.

Controlador DriverEvent

Después de registrar un controlador OnDriverEvent y se habilitan eventos, si la extensión de impresora se inició para controlar las preferencias de impresión o las notificaciones de impresora, se invocará el controlador. En el fragmento de código anterior, se registró un método denominado OnDriverEvent como controlador de eventos. En el siguiente fragmento de código, el parámetro PrinterExtensionEventArgs es el objeto que permite construir las preferencias de impresión y los escenarios de notificaciones de impresora. PrinterExtensionEventArgs es un contenedor para IPrinterExtensionEventArgs.

static void OnDriverEvent(object sender, PrinterExtensionEventArgs eventArgs)
{
    //
    // Display the print preferences window.
    //

    if (eventArgs.ReasonId.Equals(PrinterExtensionReason.PrintPreferences))
    {
        PrintPreferenceWindow printPreferenceWindow = new PrintPreferenceWindow();
        printPreferenceWindow.Initialize(eventArgs);

        //
        // Set the caller application's window as parent/owner of the newly created printing preferences window.
        //

        WindowInteropHelper wih = new WindowInteropHelper(printPreferenceWindow);
        wih.Owner = eventArgs.WindowParent;

        //
        // Display a modal/non-modal window based on the 'WindowModal' parameter.
        //

        if (eventArgs.WindowModal)
        {
            printPreferenceWindow.ShowDialog();
        }
        else
        {
            printPreferenceWindow.Show();
        }
    }

    //
    // Handle driver events.
    //

    else if (eventArgs.ReasonId.Equals(PrinterExtensionReason.DriverEvent))
    {
        // Handle driver events here.
    }
}

Para evitar una mala experiencia de usuario asociada con extensiones de impresora bloqueadas o lentas, Windows implementa un tiempo de espera si EnableEvents no se llama en un breve período de tiempo después de iniciar la aplicación. Para habilitar la depuración, este tiempo de espera se deshabilita si hay un depurador asociado al servicio PrintNotify.

Sin embargo, en la mayoría de los casos, todo el código relacionado con la aplicación en el que estamos interesados, se ejecuta durante o después de la devolución de llamada OnDriverEvent. Durante el desarrollo, también puede ser útil mostrar un cuadro de mensajes antes de iniciar una experiencia de preferencias de impresión o notificaciones de impresora desde la devolución de llamada OnDriverEvent. Cuando aparezca el cuadro de mensajes, vuelva a Visual Studio y seleccione Asociar>a proceso y elija el nombre del proceso. Por último, vuelva al cuadro de mensajes y seleccione Aceptar para reanudar. Esto garantizará que vea excepciones y alcance los puntos de interrupción desde ese punto en adelante.

Los nuevos ReasonIds se pueden admitir en el futuro. Como resultado, las extensiones de impresora deben comprobar explícitamente reasonID y no deben usar una instrucción "else" para detectar el último ReasonID conocido. Si se recibe un ReasonID y se desconoce, la aplicación debe salir correctamente.

Las preferencias de impresión se controlan mediante el objeto PrintSchemaEventArgs.Ticket. Este objeto encapsula los documentos PrintTicket e PrintCapabilities que describen las características y opciones de un dispositivo. Aunque el XML subyacente también está disponible, el modelo de objetos facilita el trabajo con estos formatos.

Dentro de cada objeto IPrintSchemaTicket o IPrintSchemaCapabilities hay características (IPrintSchemaFeature) y opciones (IPrintSchemaOption). Aunque las interfaces usadas para características y opciones son las mismas independientemente del origen, el comportamiento varía ligeramente como resultado del XML subyacente. Por ejemplo, los documentos PrintCapabilities especifican muchas opciones por característica, mientras que los documentos PrintTicket especifican solo la opción seleccionada (o predeterminada). De forma similar, los documentos PrintCapabilities especifican cadenas de visualización localizadas, mientras que los documentos PrintTicket no.

El ejemplo de extensión de impresora usa el enlace de datos para crear controles ComboBox para las preferencias de la impresora. Microsoft recomienda usar el enlace de datos, ya que facilita el mantenimiento del código al reducir la dispersión. Para obtener más información sobre el enlace de datos en WPF, vea Información general sobre el enlace de datos.

Para maximizar el rendimiento, Microsoft recomienda que las llamadas a GetPrintCapabilities solo se realicen cuando sea necesario actualizar el documento PrintCapabilities.

A medida que un usuario toma decisiones mediante los controles ComboBox enlazados a datos, el objeto PrintTicket se actualiza automáticamente. Cuando el usuario finalmente hace clic en Aceptar, comienza una cadena de validación y finalización asincrónicas. Este patrón asincrónico se usa ampliamente para evitar que se produzcan tareas de larga duración en subprocesos de interfaz de usuario y provocar bloqueos en la interfaz de usuario de preferencias de impresión o en la aplicación que está imprimiendo. A continuación se muestra una lista de los pasos que se usan para procesar los cambios de PrintTicket después de que el usuario haga clic en Aceptar.

  1. PrintSchemaTicket se valida de forma asincrónica mediante el método IPrintSchemaTicket::ValidateAsync .

  2. Cuando se completa la validación asincrónica, Common Language Runtime (CLR) invoca el método PrintTicketValidateCompleted.

    1. Si la validación se realizó correctamente, llama al método CommitPrintTicketAsync y CommitPrintTicketAsync llama al método IPrintSchemaTicket::CommitAsync . Y cuando se completa correctamente la actualización de PrintTicket, esto invoca el método PrintTicketCommitCompleted, que llama a un método cómodo que llama al método PrinterExtensionEventArgs.Request.Complete para indicar que las preferencias de impresión están completas y, a continuación, cierra la aplicación.

    2. De lo contrario, presenta la interfaz de usuario al usuario para controlar la situación de restricción.

Si el usuario hace clic en cancelar o cerrar la ventana de preferencias de impresión directamente, la extensión de impresora llama a IPrinterExtensionEventArgs.Request.Cancel con un valor HRESULT adecuado y un mensaje para el registro de errores.

Si el proceso de la extensión de impresora se ha cerrado y no ha llamado a los métodos Complete o Cancel tal y como se describe en los párrafos anteriores, el sistema de impresión revertirá automáticamente al uso de la interfaz de usuario proporcionada por Microsoft.

Para recuperar la información de estado del dispositivo, las extensiones de impresora pueden usar Bidi para consultar el dispositivo de impresión. Por ejemplo, para mostrar el estado de entrada de lápiz u otros tipos de estado sobre el dispositivo, las extensiones de impresora pueden usar el método IPrinterExtensionEventArgs.PrinterQueue.SendBidiQuery para emitir consultas Bidi al dispositivo. Obtener el estado de Bidi más reciente es un proceso de dos pasos que implica configurar un controlador de eventos para el evento OnBidiResponseReceived y llamar al método SendBidiQuery con una consulta Bidi válida. En el fragmento de código siguiente se muestra este proceso de dos pasos.

PrinterQueue.OnBidiResponseReceived += new
EventHandler<PrinterQueueEventArgs>(OnBidiResponseReceived);
PrinterQueue.SendBidiQuery("\\Printer.consumables");

Cuando se recibe la respuesta bidi, se invoca el siguiente controlador de eventos. Este controlador de eventos también tiene una implementación de estado de entrada de lápiz simulada, que puede ser útil para el desarrollo cuando un dispositivo no está disponible. El objeto PrinterQueueEventArgs incluye tanto un HRESULT como una respuesta XML bidi. Para obtener más información sobre las respuestas XML de Bidi, consulte Esquemas de solicitud y respuesta de Bidi.

private void OnBidiResponseReceived(object sender, PrinterQueueEventArgs e)
{
    if (e.StatusHResult != (int)HRESULT.S_OK)
    {
        MockInkStatus();
        return;
    }

    //
    // Display the ink levels from the data.
    //

    BidiHelperSource = new BidiHelper(e.Response);
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs("BidiHelperSource"));
    }
    InkStatusTitle = "Ink status (Live data)";
}

Notificaciones de impresora

Las notificaciones de impresora se invocan exactamente de la misma manera que las preferencias de impresión. En el controlador OnDriverEvent, si IPrinterExtensionEventArgs indica que un ReasonID coincide con el GUID de DriverEvents, podemos crear una experiencia para controlar este evento.

El proyecto Printer Extension Sample no muestra una experiencia funcional de notificaciones de impresora, pero las siguientes variables son más útiles para controlar esto.

  • PrinterExtensionEventArgs.BidiNotification: incluye el XML de Bidi que provocó que el evento se desencadenara.

  • PrinterExtensionEventArgs.DetailedReasonId: contiene el GUID de eventID del archivo xml del evento del controlador.

El atributo más importante del objeto IPrinterExtensionEventArgs para las notificaciones es la propiedad BidiNotification. Esto lleva el XML de Bidi que provocó que se desencadenara el evento. Para obtener más información sobre las respuestas XML de Bidi, consulte Esquemas de solicitud y respuesta de Bidi.

Administración de impresoras

Para admitir el rol de una extensión de impresora como una aplicación que se puede usar como concentrador para administrar o mantener impresoras, es posible enumerar las colas de impresión para las que está registrada la extensión de impresora actual y obtener el estado de cada cola. Esto no se muestra en el proyecto PrinterExtensionSample, pero el siguiente fragmento de código se podría agregar al método Main de App.xaml.cs para registrar un controlador de eventos.

mgr.OnPrinterQueuesEnumerated += new EventHandler<PrinterQueuesEnumeratedEventArgs>(mgr_OnPrinterQueuesEnumerated);

Una vez enumeradas las colas, se llama al controlador de eventos y se pueden realizar operaciones de estado. Este evento se activa periódicamente durante la vigencia de la aplicación para asegurarse de que la lista de colas de impresión enumeradas está actualizada, incluso si el usuario ha instalado más colas desde que se abrió. Como resultado, es importante que el controlador de eventos no cree una nueva ventana cada vez que se ejecute y esto se muestra en el siguiente fragmento de código.

static void mgr_OnPrinterQueuesEnumerated(object sender, PrinterQueuesEnumeratedEventArgs e)
{
    foreach (IPrinterExtensionContext pContext in e)
    {
        // show status
    }
}

Para realizar tareas de mantenimiento mediante una extensión de impresora, Microsoft recomienda que la API WritePrinter heredada se use como se describe en el pseudocódigo siguiente.

OpenPrinter
    StartDocPrinter
        StartPagePrinter
          WritePrinter
        EndPagePrinter
    EndDocPrinter
ClosePrinter

Procedimientos recomendados de rendimiento de la extensión de impresora

Para garantizar la mejor experiencia del usuario, las extensiones de impresora deben diseñarse para cargarse lo más rápido posible. El proyecto Printer Extension Sample es una aplicación .NET, lo que significa que se integra en un lenguaje intermedio (IL) que se debe compilar en tiempo de ejecución en el formato adecuado para la arquitectura del procesador nativo. Durante la instalación, Microsoft recomienda que las extensiones de impresora se instalen según los procedimientos recomendados, para asegurarse de que la aplicación se ha compilado para la arquitectura del sistema nativo. Para obtener más información sobre los procedimientos recomendados de compilación e instalación de código, vea Mejorar el rendimiento de inicio para las aplicaciones de escritorio.

Microsoft también recomienda que las extensiones de impresora posponga tareas de inicialización, como cargar recursos hasta después de llamar al método EnableEvents. Esto minimiza la probabilidad de que la aplicación llame a EnableEvents antes del tiempo de espera de 5 segundos para las extensiones de impresora.

Después de la llamada a OnDriverEvent, las extensiones de impresora deben inicializar su interfaz de usuario y dibujar lo antes posible, haciendo uso de métodos asincrónicos siempre que sea posible para garantizar la capacidad de respuesta. Las extensiones de impresora no deben tener ninguna dependencia en las llamadas de red o bidi para crear el estado inicial de la ventana para las preferencias de impresión o las notificaciones de impresora.

A medida que el usuario toma decisiones mediante la interfaz de usuario en pantalla que afecta a PrintTicket, la extensión de impresora debe usar el método IPrintSchemaTicket::ValidateAsync para validar los cambios lo antes posible. Por último, la extensión de impresora debe usar el método IPrintSchemaTicket::CommitAsync para confirmar los cambios de PrintTicket.

Las extensiones de impresora siempre se ejecutan fuera del proceso desde el proceso que los invoca. Por lo tanto, debe tener en cuenta el comportamiento de las ventanas al desarrollar una extensión de impresora:

  • La propiedad WindowParent de IPrinterExtensionEventArgs especifica el identificador de la ventana que invocó la aplicación.
  • La propiedad WindowModal de IPrinterExtensionEventArgs especifica si se debe ejecutar una extensión de impresora (en modo de preferencias de impresión).

El ejemplo de extensión de impresora muestra cómo crear una interfaz de usuario que se inicia generalmente como la ventana superior. Sin embargo, en algunos casos, la interfaz de usuario no se mostrará en primer plano, como cuando el proceso que provocó que la interfaz de usuario se invoque se ejecuta en un nivel de integridad diferente o cuando el proceso se compila para una arquitectura de procesador diferente. En este caso, la extensión de impresora debe llamar a FlashWindowEx para solicitar permiso de usuario para llegar al primer plano parpadeando el icono en la barra de tareas.

Esquemas de solicitud y respuesta de Bidi

Información general sobre el enlace de datos

Mejora del rendimiento de inicio para las aplicaciones de escritorio

Generador de imágenes nativas

Interfaces de esquema de impresión

Ejemplo de extensión de impresora

Herramientas de análisis de rendimiento de Windows