Impresoras

Monitorear cola de impresión en Windows

En este artículo te voy a enseñar a monitorear la cola de impresión de una impresora de Windows para saber cuando un trabajo ha sido agregado, básicamente vas a escuchar el evento de cuando alguien envía un trabajo de impresión a una impresora.

Esto va a servir para obtener una notificación cada vez que un documento es enviado para ser impreso en cualquier impresora, y a partir de ahí poder realizar otras acciones, sin importar el lugar desde donde el trabajo de impresión fue enviado.

Vamos a usar las APIs de Windows, específicamente OpenPrinter, FindFirstPrinterChangeNotification y FindNextPrinterChangeNotification usando C++, pero me imagino que esto que haremos puede hacerse desde cualquier lenguaje de programación como C#, Python, etcétera.

Abrir impresora

Comencemos viendo el modo de obtener un handler para la impresora, para ello invocamos a la función OpenPrinter. Toma en cuenta que en este caso el nombre de la impresora se está leyendo desde la línea de comandos.

char nombreDeImpresora[MAX_PATH];
std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
std::cin.getline(nombreDeImpresora, MAX_PATH);
HANDLE manejadorDeImpresora = NULL;
PRINTER_DEFAULTS pd;
ZeroMemory(&pd, sizeof(pd));
pd.DesiredAccess = PRINTER_ACCESS_USE;
if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
{
    std::cerr << "Error al abrir la impresora." << std::endl;
    return 1;
}

Debido a que solo vamos a revisar la cola de impresión desde C++, solo necesitamos el permiso PRINTER_ACCESS_USE, si quieres puedes revisar la documentación oficial para revisar otro tipo de derechos.

Obtener identificador de objeto para monitoreo

Una vez que tenemos el handle de la impresora podemos invocar a FindFirstPrinterChangeNotification, aquí pasamos una bandera indicando las condiciones bajo las cuales queremos ser notificados.

En este caso solo se necesita saber cuando alguien envía un trabajo de impresión a una impresora, así que con PRINTER_CHANGE_ADD_JOB basta.

HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
    manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);

if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
{
    std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
    return 1;
}

std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";

Es importante mencionar que FindFirstPrinterChangeNotification puede devolver INVALID_HANDLE_VALUE en caso de no poder obtener el acceso al manejador, esto puede ocurrir por permisos o cosas similares, por ello es que debemos manejar esa opción.

Monitorear cola de impresión

Llegamos al punto más importante: saber cuando alguien envía un trabajo de impresión a una impresora en Windows.

Para ello encerramos el comportamiento en un ciclo infinito y esperamos con WaitForSingleObject para esperar la señal, que en otras palabras será la notificación de que se ha agregado un trabajo de impresión a la cola.

while (true)
{
    if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
    {
        std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;

        if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
        {
            std::cerr << "Error al esperar mas notificaciones" << std::endl;
            break;
        }
    }
    else
    {
        std::cerr << "Error al esperar la notificacion" << std::endl;
        break;
    }
}

La línea más importante está dentro del if, que es en donde imprimimos que se ha agregado un trabajo a la cola de impresión. Aquí puedes hacer más cosas además de imprimir, pues puedes estar seguro de que alguien envió una impresión a la impresora previamente indicada.

Finalmente basta con cerrar los manejadores y objetos:

if (identificadorDeObjetoParaMonitoreo != NULL)
{
    FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
}
if (manejadorDeImpresora != NULL)
{
    ClosePrinter(manejadorDeImpresora);
}

Código completo

Ya he explicado cómo ser notificado cuando alguien envía una impresión a una impresora, ahora veremos el código completo. Recuerda que estamos trabajando con la Windows API así que debes incluir el encabezado windows.h.

El código completo queda así:

#include <windows.h>
#include <iostream>

int main()
{
    char nombreDeImpresora[MAX_PATH];
    std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
    std::cin.getline(nombreDeImpresora, MAX_PATH);
    HANDLE manejadorDeImpresora = NULL;
    PRINTER_DEFAULTS pd;
    ZeroMemory(&pd, sizeof(pd));
    pd.DesiredAccess = PRINTER_ACCESS_USE;
    if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
    {
        std::cerr << "Error al abrir la impresora." << std::endl;
        return 1;
    }

    HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
        manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);

    if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
    {
        std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
        return 1;
    }

    std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";
    while (true)
    {
        if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
        {
            std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;

            if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
            {
                std::cerr << "Error al esperar mas notificaciones" << std::endl;
                break;
            }
        }
        else
        {
            std::cerr << "Error al esperar la notificacion" << std::endl;
            break;
        }
    }

    if (identificadorDeObjetoParaMonitoreo != NULL)
    {
        FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
    }
    if (manejadorDeImpresora != NULL)
    {
        ClosePrinter(manejadorDeImpresora);
    }

    return 0;
}

Personalmente lo he compilado con g++ así: g++ monitorear.cpp -lwinspool -o monitorear.exe

Me parece que también se puede compilar con Visual Studio, y también recuerdo que puedes invocar a estas API desde otros lenguajes de programación.

El código es sencillo, pero me ha llevado un tiempo el investigar, hacer pruebas y colocar todo en una prueba de concepto.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

5 días hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.