Obtener el estado de la impresora a través de .NET

Entre la ingente cantidad de carencias que adolece el .NET Framework, una de las más hirienes tanto para los desarrolladores de sistema/escritorio como casi para cualquier otro desarrollador es poder obtener el estado de una impresora para evitar enviar inútilmente un trabajo a su cola o, simplemente, para informar de manera elegante de que no tiene papel, éste está atascado o simplemente que el dispositivo se encuentra en estado de «Off Line».


Partimos del punto de que mediante Win32 es muy fácil obtener dicho estado (desde el punto de vista del desarrollo Win32, claro); no será, por tanto, demasiado complicado poder obtenerlo mediante Interop, y menos aún desde C++/CLI. Veamos primero el ejemplo y estudiémoslo después (por favor, que los puristas del C++ se armen de valor y no desmayen al ver el código mostrado hasta leer las explicaciones. No me hago responsable de taquicardias ni de ingresos por síncopes.)



using namespace System;
using namespace System::Runtime::InteropServices;


static bool IsPrinterOk(String ^printerName)
{
    bool bResult=false;
    HANDLE hPrinter;
    IntPtr pPrinterName=Marshal::StringToHGlobalUni(printerName);
    WCHAR *pChar=(WCHAR *)pPrinterName.ToPointer();
    if(::OpenPrinter(pChar,&hPrinter,NULL)==0)
        goto fin;


    //PUNTO A
    DWORD bytesNeeded;
    ::GetPrinter(hPrinter,2,0,0,&bytesNeeded);
    PRINTER_INFO_2 *pf=(PRINTER_INFO_2 *)GlobalAlloc(GPTR, bytesNeeded);
    if(::GetPrinter(hPrinter,2,(LPBYTE)pf,bytesNeeded,&bytesNeeded)==0)
        goto fin;


    if(pf->cJobs!=0)
        goto fin;



    if(pf->Status!=0)
        goto fin;


    bResult=true;


fin:
    ClosePrinter(hPrinter);
   
return bResult;
}


Lo primero que llama la atención es que se trata de un método suelto nativo insertado en cualquier sitio de nuestro proyecto (En mi caso, PrinterStatus.h). Ventajas del Interop del C++/CLI.


Lo segundo son los gotos. Ya dije que los puristas se tomaran esto con calma. ¿Qué hemos ahorrado con añadir los goto? Pues tener que poner llaves en los if y tener que cerrar la impresora hasta cuatro veces, así como salir del programa por cinco sitios. (Aquí podríamos detallar una guerra purista sobre si es coherente salir de un método por un solo sitio o por varios, y qué ventajas tendría sobre una implementación con goto o con cadenas de if anidados). También ahorramos tiempo de ejecución. todos esos if se solcionan en ensamblador con un salto condicional corto, del estilo JNE o JE.


Además, cualquier otro código escrito aquí con la misma funcionalidad, si el compilador es bueno, terminaría ejecutando algo como lo hemos escrito, de forma que nosotros le ahorramos algo de trabajo al optimizador.


La segunda cuestión interesante está en el PUNTO A. Quizás escandalicen esas dos llamadas a GetPrinter, pero es una técnica habitual en Win32 cuando no se conoce el tamaño de un buffer. Personalmente no es que esté de acuerdo con ella, pero así es como lo han pensado los gurús de MS, y tampoco es que sea una mala idea, aunque poco ortodoxa. Quizás hubiera sido de desar que la llamada a GetPrinter asignara un bloque estático que luego el usuario debiera liberar (ya sé que esto es bastante peligroso), o simplemente devolver un buffer de sólo lectura y que el usuario lo copie si lo va a modificar.


El código se explica por sí solo. Primero convertimos el String pasado a un puntero nativo, luego abrimos la impresora. Si no podemos hacerlo es que o no tenemos permisos o falla algo, de modo que tampoco podremos imprimir ni hacer nada más. El segundo bloque solicita datos del tipo PRINTER_INFO_2, que es donde se encuentra almacenada la información que nos interesa. Quien desee profundizar en este tema, que se vaya a la MSDN y mire dichas estructuras y la llamada a función GetPrinter. Los últimos pasos comprueban los campos que nos interesan (en mi caso es lo único que necesito) y devolvemos cierto si la impresora no tiene trabajos pendientes y su estado es el correcto.


Otra cosa es que el driver de la impresora esté bien hecho y la información devuelta sea la correcta, lo que no siempre acontece, o más bien casi nunca.


Más bugs para el C++/CLI
Como es habitual y estoy acostumbrado ya, con cada cosa nueva que pruebo con C++/CLI, me suelo encontrar a veces no con un bug, sino con varios. En este caso, y sólo en el trozo de código anteror, he tenido la enorme suerte de encontrarme con una cosa muy curiosa.


El código de arriba está situado en un fichero h. ¿Por qué? La primera versión tenía la definición de la función global IsPrinterOk en un h y la implementación en su correspondiente cpp, pero el compilador se veía incapaz de encontrar la implementación al método citado. Pero si redefinía un cuerpo tanto en el fichero cabecera como en el cpp, el compilador me decía que la función tenía dos implementaciones idénticas.


Luego se me quejan si empiezo a evaluar seriamente pasarme al lado oscuro mundo Open Source. Allí al menos sé que no voy a tener soporte, que me voy a encontrar cantidad de bugs que nadie va a solucionar, y a que todo está manga por hombro. Pero al menos parto desde el punto de vista de que ya sé qué me voy a encontrar, y no como aquí, en el que me encuentro en la misma situación… pero sin saberlo de antemano.


Y siempre me quedará, claro está, la opción de intentar solucionármelo yo mismo, cosa que, siendo casi una falacia evidente pero una posible realidad, dentro del mundo propietario no lo es de partida.


Y uno para mi
El código de arriba contiene al menos un bug muy pero que muy serio. ¿Quién lo va a adivinar? Un gallinfante de regalo. 🙂

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *