Extensões da impressora
Importante
A plataforma de impressão moderna é o meio preferido do Windows para se comunicar com as impressoras. 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 Plataformade impressão moderna e o Guia de design do aplicativo de suporte de impressão.
Os aplicativos de extensão de impressora dão 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 runtimes externos além 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 setup.exe ou em um pacote MSI e anunciado na experiência do 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 for 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\<nome da fila de impressão>\PrinterDriverData\PrinterExtensionUrl.
E se uma empresa optar por bloquear completamente as extensões de impressora, isso pode ser feito por meio de uma política 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 conhecer. Essas áreas de foco são mostradas na lista a seguir.
Registro
Ativando eventos
Manipulador de eventos OnDriverEvent
Preferências de impressão
Notificações de 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 dão 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 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 de 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.
Depois que o serviço PrintNotify for iniciado, ele verificará se há chaves do Registro no caminho [OfflineRoot] e processará todos os registros ou cancelamentos de registro pendentes. Depois que todos os registros ou cancelamentos pendentes forem concluídos, as chaves do Registro serão excluídas em tempo real. Se você estiver usando um script ou processo iterativo para colocar chaves do Registro, talvez seja necessário recriar a chave \[PrinterExtensionID] toda vez que especificar uma chave \[PrinterDriverId]. Chaves incompletas ou malformadas não são excluídas.
Este registro só é necessário na primeira instalação. O exemplo a seguir mostra o formato correto da chave do Registro usado para registrar extensões de impressora.
Observação
[Raiz Offline] é 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:\Arquivos de Programas\Fabrikam\pe.exe" para os {PrinterDriverID1Guid} e {PrinterDriverID2Guid} PrinterDriverIDs, com as preferências da impressora e os motivos das 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 evento, é ú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 do 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 neste caso.
Quando a extensão de impressora se registra para o PrinterDriverID atual, ela 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}"
Ativando eventos
Em tempo de execução, as extensões de impressora devem habilitar o disparo de eventos para o PrinterDriverID atual. Esse é 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 de impressora.
Portanto, o aplicativo deve criar um novo PrinterExtensionManager para o PrinterDriverID atual, registrar um delegado 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 ngen para gerar uma imagem nativa durante a instalação. Para saber mais, confira Gerador de imagens nativas.
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 de DriverEvent
Depois que um manipulador OnDriverEvent for registrado e os eventos forem habilitados, se a extensão da impressora tiver sido iniciada para lidar com preferências de impressão ou notificações de impressora, o manipulador será invocado. 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 da 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 ruim do usuário associada a falhas ou extensões de impressora lentas, o Windows implementará um tempo limite se EnableEvents não for chamado dentro de 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 anexado 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 aparecer, volte para o Visual Studio e selecione Depurar>Anexar ao Processo e escolha o nome do processo. Por fim, volte para sua MessageBox e selecione OK para retomar. Isso garantirá que você veja exceções e atinja todos os pontos de interrupção a partir desse ponto.
Novos ReasonIds podem ter suporte 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á ser encerrado normalmente.
Preferências de impressão
As preferências de impressão são orientadas 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 um pouco 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, confira Visão geral de vinculação de dados.
Para maximizar o desempenho, a Microsoft recomenda que as chamadas para GetPrintCapabilities só sejam feitas quando for necessário atualizar o documento PrintCapabilities.
À medida que um usuário faz escolhas usando os controles ComboBox associados a dados, o objeto PrintTicket é atualizado automaticamente. Quando o usuário finalmente clica em OK, uma cadeia de validação e conclusão assíncrona é iniciada. Esse padrão assíncrono é usado extensivamente para evitar que tarefas de execução longa ocorram em threads de interface do usuário e causem travamentos na interface do usuário de preferências de impressão ou no aplicativo que está imprimindo. Veja a seguir uma lista das etapas usadas para processar as alterações do PrintTicket depois que o usuário clica em OK.
O PrintSchemaTicket é validado de forma assíncrona usando o método IPrintSchemaTicket::ValidateAsync.
Quando a validação assíncrona é concluída, o CLR (Common Language Runtime) invoca o método PrintTicketValidateCompleted.
Se a validação foi 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.
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 chamará 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 for chamado de 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 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 manipulador de eventos a seguir é invocado. Esse manipulador de eventos também tem uma implementação de status de tinta simulada, que pode ser útil para o 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 de impressora
As notificações da impressora são invocadas exatamente da mesma maneira 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 lidar com 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 – Isso carrega o XML Bidi que fez com que o evento fosse disparado.
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 disparado. Para obter mais informações sobre respostas XML bidi, consulte Esquemas de solicitação e resposta bidi.
Gerenciar 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 do 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 executado, 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 de Exemplo de Extensão de Impressora é um aplicativo .NET, o que significa que ele é integrado a uma IL (linguagem intermediária) que deve ser compilada em tempo de execução no formato apropriado para a arquitetura do 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 da á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, usando métodos assíncronos sempre que possível para garantir a capacidade de resposta. As extensões de impressora não devem ter dependência de chamadas de rede ou Bidi para criar o estado da janela inicial 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. Por fim, 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 do processo invocado. Portanto, você deve ter em mente o comportamento da janela ao desenvolver uma extensão de impressora:
- A propriedade WindowParent de IPrinterExtensionEventArgs especifica o identificador para a janela que invocou o aplicativo.
- A propriedade WindowModal de IPrinterExtensionEventArgs especifica se uma extensão de impressora (no modo de preferências de impressão) deve ser executada modalmente.
O Exemplo de Extensão de Impressora demonstra como criar uma interface do usuário que geralmente é iniciada como a janela superior. Mas, em alguns casos, a interface do usuário não será mostrada em primeiro plano, como quando o processo que fez com que a interface do usuário fosse invocada está em execução 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 para o primeiro plano piscando o ícone na barra de tarefas.
Artigos relacionados
Esquemas de solicitação e resposta Bidi
Visão geral da vinculação de dados
Melhorando o desempenho de inicialização dos aplicativos da área de trabalho