Compartilhar via


Extensões de impressora

Importante

Recomendamos que você use o driver de classe de caixa de entrada IPP da Microsoft juntamente com PSA (Aplicativos de Suporte à Impressão) para personalizar a experiência de impressão no Windows 10 e 11 para o desenvolvimento de dispositivos de impressora.

Para obter mais informações, consulte o Guia de design do aplicativo de suporte à impressão.

Os aplicativos de extensão de impressora oferecem suporte a preferências de impressão e notificações de impressora quando os usuários executam aplicativos existentes na área de trabalho do Windows.

As extensões de impressora podem ser criadas em qualquer linguagem compatível com COM, mas são otimizadas para serem criadas usando o Microsoft .NET Framework 4. As extensões de impressora podem ser distribuídas com um pacote de driver de impressão, se forem compatíveis com XCopy e não tiverem dependências de tempos de execução externos diferentes daqueles incluídos no sistema operacional, por exemplo, .NET. Se o aplicativo de extensão de impressora não atender a esses critérios, ele poderá ser distribuído em um pacote setup.exe ou MSI e anunciado na experiência Device Stage da impressora usando a diretiva PrinterExtensionUrl especificada no manifesto v4. Quando um aplicativo de extensão de impressora é distribuído por meio de um pacote MSI, você tem a opção de adicionar o driver de impressão ao pacote ou deixá-lo de fora e distribuir o driver separadamente. O PrinterExtensionUrl é mostrado na experiência de preferências da impressora.

Os administradores de TI têm algumas opções para gerenciar a distribuição de extensões de impressora. Se o aplicativo estiver empacotado em um setup.exe ou MSI, os administradores de TI poderão usar ferramentas de distribuição de software padrão, como o Microsoft Endpoint Configuration Manager, ou poderão incluir os aplicativos em sua imagem padrão do sistema operacional. Os administradores de TI também podem substituir o PrinterExtensionUrl especificado no manifesto v4, se editarem HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\<print queue name>\PrinterDriverData\PrinterExtensionUrl.

E se uma empresa optar por bloquear completamente as extensões de impressora, isso poderá ser feito por meio de uma diretiva de grupo chamada "Configuração do Computador\Modelos Administrativos\Impressoras\Não permitir que drivers de impressora v4 mostrem aplicativos de extensão de impressora".

Criando uma extensão de impressora

Quando você está desenvolvendo uma extensão de impressora, há seis áreas principais de foco que você deve estar ciente. Essas áreas de foco são mostradas na lista a seguir.

  • Registro

  • Habilitando eventos

  • Manipulador OnDriverEvent

  • Preferências de impressão

  • Notificações da impressora

  • Gerenciar impressoras

Registro

As extensões de impressora são registradas no sistema de impressão especificando um conjunto de chaves do Registro ou especificando as informações do aplicativo na seção PrinterExtensions do arquivo de manifesto v4.

Há GUIDs especificados que oferecem suporte a cada um dos diferentes pontos de entrada para extensões de impressora. Você não precisa usar esses GUIDs no arquivo de manifesto v4, mas você deve saber os valores GUID para usar o formato do Registro para a instalação do driver v4. A tabela a seguir mostra os valores de GUID para os dois pontos de entrada.

Entry Point GUID
Preferências de impressão {EC8F261F-267C-469F-B5D6-3933023C29CC}
Notificações da impressora {23BB1328-63DE-4293-915B-A6A23D929ACB}

As extensões de impressora instaladas fora do driver de impressora precisam ser registradas usando o registro. Isso garante que as extensões de impressora possam ser instaladas independentemente do status do spooler ou do módulo de configuração v4 na máquina cliente.

Assim que o serviço PrintNotify for iniciado, ele verificará se há chaves do Registro no caminho [OfflineRoot] e processará quaisquer registros ou cancelamentos pendentes. Uma vez que quaisquer registros pendentes ou cancelamentos de registros são concluídos, as chaves do Registro são excluídas em tempo real. Se você estiver usando um script ou um processo iterativo para colocar chaves do Registro, talvez seja necessário recriar a chave \[PrinterExtensionID] sempre que especificar uma chave \[PrinterDriverId]. Chaves incompletas ou malformadas não são excluídas.

Este registo só é necessário na primeira instalação. O exemplo a seguir mostra o formato de chave do Registro correto usado para registrar extensões de impressora.

Observação

[OfflineRoot] é usado como abreviação 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 exemplo, o conjunto de chaves a seguir registraria uma extensão de impressora com o {PrinterExtensionIDGuid} PrinterExtensionID e um caminho totalmente qualificado para o executável "C:\Program Files\Fabrikam\pe.exe" para os {PrinterDriverID1Guid} e {PrinterDriverID2Guid} PrinterDriverID2Guid, com as preferências da impressora e os motivos de notificações da impressora.

[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 a mesma extensão de impressora, o seguinte conjunto de chaves deve ser especificado.

[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"

Como as extensões de impressora podem ser executadas em um contexto iniciado pelo usuário e em um contexto iniciado por eventos, é útil poder determinar o contexto no qual sua extensão de impressora está operando. Isso pode permitir que um aplicativo, por exemplo, não enumere o status em todas as filas se ele tiver sido iniciado para uma notificação ou preferências de impressão. A Microsoft recomenda que as extensões de impressora instaladas separadamente do driver (por exemplo, com um MSI ou setup.exe) usem opções de linha de comando nos atalhos do menu Iniciar ou na entrada AppPath que foi preenchida no Registro durante o registro. Como as extensões de impressora instaladas com o driver são instaladas no DriverStore, elas não serão iniciadas fora das preferências de impressão ou dos eventos de notificações da impressora. Portanto, a especificação de opções de linha de comando não é suportada nesse caso.

Quando a extensão de impressora se registra para o PrinterDriverID atual, ele deve incluir o PrinterDriverID no AppPath. Por exemplo, para um aplicativo de extensão de impressora com o nome printerextension.exe e um valor PrinterDriverID de {GUID}, o [PrinterExtensionAppPath] seria semelhante ao exemplo a seguir:

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

Habilitando eventos

Em tempo de execução, as extensões de impressora devem habilitar o disparo de eventos para o PrinterDriverID atual. Este é o PrinterDriverID que foi passado para o aplicativo por meio da matriz args[] e permite que o sistema de impressão forneça um contexto de evento apropriado para lidar com motivos como preferências de impressão ou notificações da impressora.

Portanto, o aplicativo deve criar um novo PrinterExtensionManager para o PrinterDriverID atual, registrar um representante para manipular o evento OnDriverEvent e chamar o método EnableEvents com o PrinterDriverID. O trecho de código a seguir ilustra essa abordagem.

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

Se um aplicativo não chamar EnableEvents em 5 segundos, o Windows atingirá o tempo limite e iniciará uma interface do usuário padrão. Para atenuar isso, as extensões de impressora devem seguir as práticas recomendadas de desempenho mais recentes, incluindo o seguinte:

  • Atrase o máximo possível da inicialização do aplicativo, até depois de chamar EnableEvents. Depois disso, priorize a capacidade de resposta da interface do usuário usando métodos assíncronos e não bloqueando o thread da interface do usuário durante a inicialização.

  • Use o ngen para gerar uma imagem nativa durante a instalação. Para obter mais informações, consulte Native Image Generator.

  • Use ferramentas de medição de desempenho para encontrar problemas de desempenho no carregamento. Para obter mais informações, consulte Ferramentas de análise de desempenho do Windows.

Manipulador DriverEvent

Depois que um manipulador OnDriverEvent for registrado e os eventos forem habilitados, se a extensão de impressora tiver sido iniciada para manipular preferências de impressão ou notificações de impressora, o manipulador será chamado. No trecho de código anterior, um método chamado OnDriverEvent foi registrado como o manipulador de eventos. No trecho de código a seguir, o parâmetro PrinterExtensionEventArgs é o objeto que permite que as preferências de impressão e os cenários de notificações de impressora sejam construídos. PrinterExtensionEventArgs é um wrapper 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 uma experiência de usuário ruim associada a extensões de impressora lentas ou falhas, o Windows implementa um tempo limite se EnableEvents não for chamado em um curto período de tempo após o aplicativo ser iniciado. Para habilitar a depuração, esse tempo limite será desabilitado se houver um depurador conectado ao serviço PrintNotify .

Na maioria dos casos, no entanto, todo o código relacionado ao aplicativo no qual estamos interessados é executado durante ou após o retorno de chamada OnDriverEvent. Durante o desenvolvimento, também pode ser útil mostrar um MessageBox antes de iniciar uma experiência de preferências de impressão ou notificações de impressora a partir do retorno de chamada OnDriverEvent. Quando o MessageBox for exibido, volte para o Visual Studio e selecione Depurar>anexar ao processo e escolha o nome do seu processo. Finalmente, volte para sua MessageBox e selecione OK para continuar. Isso garantirá que você veja exceções e atinja quaisquer pontos de interrupção a partir desse ponto.

Novos ReasonIds podem ser suportados no futuro. Como resultado, as extensões de impressora devem verificar explicitamente o ReasonID e não devem usar uma instrução "else" para detectar o último ReasonID conhecido. Se um ReasonID for recebido e desconhecido, o aplicativo deverá sair normalmente.

As preferências de impressão são controladas pelo objeto PrintSchemaEventArgs.Ticket. Esse objeto encapsula os documentos PrintTicket e PrintCapabilities que descrevem os recursos e as opções de um dispositivo. Embora o XML subjacente também esteja disponível, o modelo de objeto facilita o trabalho com esses formatos.

Dentro de cada objeto IPrintSchemaTicket ou IPrintSchemaCapabilities há recursos (IPrintSchemaFeature) e opções (IPrintSchemaOption). Embora as interfaces usadas para recursos e opções sejam as mesmas, independentemente da origem, o comportamento varia ligeiramente como resultado do XML subjacente. Por exemplo, os documentos PrintCapabilities especificam muitas opções por recurso, enquanto os documentos PrintTicket especificam apenas a opção selecionada (ou padrão). Da mesma forma, os documentos PrintCapabilities especificam cadeias de caracteres de exibição localizadas, enquanto os documentos PrintTicket não.

Para obter mais informações sobre vinculação de dados no WPF, consulte Visão geral da vinculação de dados.

Para maximizar o desempenho, a Microsoft recomenda que as chamadas para GetPrintCapabilities só devem ser feitas quando for necessário atualizar o documento PrintCapabilities.

À medida que um usuário faz escolhas usando os controles ComboBox vinculados a dados, o objeto PrintTicket é atualizado automaticamente. Quando o usuário finalmente clica em OK, uma cadeia de validação e conclusão assíncronas começa. Esse padrão assíncrono é usado extensivamente para evitar que tarefas de longa execução ocorram em threads da interface do usuário e causem travamentos na interface do usuário das preferências de impressão ou no aplicativo que está imprimindo. A seguir está uma lista das etapas usadas para processar as alterações de PrintTicket depois que o usuário clica em OK.

  1. O PrintSchemaTicket é validado de forma assíncrona usando o método IPrintSchemaTicket::ValidateAsync.

  2. Quando a validação assíncrona for concluída, o CLR (Common Language Runtime) invocará o método PrintTicketValidateCompleted.

    1. Se a validação for bem-sucedida, ele chamará o método CommitPrintTicketAsync e CommitPrintTicketAsync chamará o método IPrintSchemaTicket::CommitAsync. E quando a atualização PrintTicket é concluída com êxito, isso invoca o método PrintTicketCommitCompleted, que chama um método de conveniência que chama o método PrinterExtensionEventArgs.Request.Complete para indicar que as preferências de impressão estão concluídas e, em seguida, fecha o aplicativo.

    2. Caso contrário, ele apresenta a interface do usuário ao usuário para lidar com a situação de restrição.

Se o usuário clicou em cancelar ou fechou a janela de preferências de impressão diretamente, a extensão da impressora chama IPrinterExtensionEventArgs.Request.Cancel com um valor HRESULT apropriado e uma mensagem para o log de erros.

Se o processo para a extensão da impressora tiver sido fechado e não chamado os métodos Complete ou Cancel conforme descrito nos parágrafos anteriores, o sistema de impressão voltará automaticamente a usar a interface do usuário fornecida pela Microsoft.

Para recuperar informações de status do dispositivo, as extensões de impressora podem usar o Bidi para consultar o dispositivo de impressão. Por exemplo, para mostrar o status da tinta ou outros tipos de status sobre o dispositivo, as extensões de impressora podem usar o método IPrinterExtensionEventArgs.PrinterQueue.SendBidiQuery para emitir consultas Bidi para o dispositivo. Obter o status Bidi mais recente é um processo de duas etapas que envolve a configuração de um manipulador de eventos para o evento OnBidiResponseReceived e a chamada do método SendBidiQuery com uma consulta Bidi válida. O trecho de código a seguir mostra esse processo de duas etapas.

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

Quando a resposta Bidi é recebida, o seguinte manipulador de eventos é chamado. Esse manipulador de eventos também tem uma implementação de status de tinta simulada, que pode ser útil para desenvolvimento quando um dispositivo não está disponível. O objeto PrinterQueueEventArgs inclui uma resposta HRESULT e uma resposta XML Bidi. Para obter mais informações sobre respostas XML Bidi, consulte Esquemas de solicitação e resposta 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)";
}

Notificações da impressora

As notificações da impressora são invocadas exatamente da mesma forma que as preferências de impressão. No manipulador OnDriverEvent, se IPrinterExtensionEventArgs indicar que um ReasonID corresponde ao GUID DriverEvents, poderemos criar uma experiência para manipular esse evento.

As variáveis a seguir são mais úteis para lidar com uma experiência de notificações de impressora funcional.

  • PrinterExtensionEventArgs.BidiNotification – Carrega o XML Bidi que causou o disparo do evento.

  • PrinterExtensionEventArgs.DetailedReasonId – Contém o GUID eventID do arquivo xml de evento do driver.

O atributo mais importante do objeto IPrinterExtensionEventArgs para notificações é a propriedade BidiNotification. Isso carrega o XML Bidi que fez com que o evento fosse acionado. Para obter mais informações sobre respostas XML Bidi, consulte Esquemas de solicitação e resposta Bidi.

Gerenciando impressoras

Para dar suporte à função de uma extensão de impressora como um aplicativo que pode ser usado como um hub para gerenciar/manter impressoras, é possível enumerar as filas de impressão para as quais a extensão de impressora atual está registrada e obter o status de cada fila. Isso não é demonstrado no projeto PrinterExtensionSample, mas o trecho de código a seguir pode ser adicionado ao método Main de App.xaml.cs para registrar um manipulador de eventos.

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

Depois que as filas são enumeradas, o manipulador de eventos é chamado e as operações de status podem ocorrer. Esse evento é acionado periodicamente durante o tempo de vida do aplicativo para garantir que a lista de filas de impressão enumeradas esteja atualizada, mesmo que o usuário tenha instalado mais filas desde que foi aberta. Como resultado, é importante que o manipulador de eventos não crie uma nova janela sempre que for executada, e isso é mostrado no trecho de código a seguir.

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

Para executar tarefas de manutenção usando uma extensão de impressora, a Microsoft recomenda que a API WritePrinter herdada seja usada conforme descrito pelo pseudocódigo a seguir.

OpenPrinter
    StartDocPrinter
        StartPagePrinter
          WritePrinter
        EndPagePrinter
    EndDocPrinter
ClosePrinter

Práticas recomendadas de desempenho de extensão de impressora

Para garantir a melhor experiência do usuário, as extensões de impressora devem ser projetadas para carregar o mais rápido possível. O projeto Printer Extension Sample é um aplicativo .NET, o que significa que ele é criado em uma linguagem intermediária (IL) que deve ser compilada em tempo de execução no formato apropriado para a arquitetura de processador nativo. Durante a instalação, a Microsoft recomenda que as extensões de impressora sejam instaladas de acordo com as práticas recomendadas, para garantir que o aplicativo tenha sido compilado para a arquitetura nativa do sistema. Para obter mais informações sobre as práticas recomendadas de compilação e instalação de código, consulte Melhorando o desempenho de inicialização para seus aplicativos de área de trabalho.

A Microsoft também recomenda que as extensões de impressora adiem tarefas de inicialização, como carregar recursos, até que o método EnableEvents tenha sido chamado. Isso minimiza a probabilidade de o aplicativo chamar EnableEvents antes do tempo limite de 5 segundos para extensões de impressora.

Após a chamada OnDriverEvent, as extensões de impressora devem inicializar sua interface do usuário e desenhar o mais rápido possível, fazendo uso de métodos assíncronos sempre que possível para garantir a capacidade de resposta. As extensões de impressora não devem depender de chamadas de rede ou Bidi para criar o estado inicial da janela para preferências de impressão ou notificações de impressora.

À medida que o usuário faz escolhas usando a interface do usuário na tela que afeta o PrintTicket, a extensão da impressora deve usar o método IPrintSchemaTicket::ValidateAsync para validar as alterações o mais cedo possível. Finalmente, a extensão da impressora deve usar o método IPrintSchemaTicket::CommitAsync para confirmar as alterações de PrintTicket.

As extensões de impressora são sempre executadas fora do processo a partir do processo que as invocou. Portanto, você deve ter em mente o comportamento da janela ao desenvolver uma extensão de impressora:

O exemplo de extensão de impressora demonstra como criar uma interface do usuário que geralmente é iniciada como a janela mais alta. Mas, em alguns casos, a interface do usuário não será mostrada em primeiro plano, como quando o processo que causou a chamada da interface do usuário está sendo executado em um nível de integridade diferente ou quando o processo é compilado para uma arquitetura de processador diferente. Nesse caso, a extensão da impressora deve chamar FlashWindowEx para solicitar permissão do usuário para vir ao primeiro plano piscando o ícone na barra de tarefas.

Esquemas de solicitação e resposta Bidi

Visão geral da vinculação de dados

Melhorando o desempenho de inicialização para seus aplicativos de desktop

Gerador de imagem nativo

Interfaces de esquema de impressão

Ferramentas de Análise de Desempenho do Windows