Solución a "Obtener el estado de la impresora a través de .NET", y más bugs (y II)

Visto el éxito obtenido, que nadie ha sido capaz de detectar el bug, ya sea porque no me leen, o porque ya ni se acuerden de Win32, voy a poner aquí la soución al mismo.


También debo dar las gracias a Bartomeu (ver los comentarios a la entrada equivalente al post anerior en mi otro blog, aquí) por haberme hecho notar cierto problema del que no me había dado cuenta; no es que fuera peligroso, pero sí un despiste.


El bug, que clama al cielo, es no liberar la memoria asignada con GlobalAlloc, por lo que cada vez que uno entra en la función, va asignando memoria que nunca llega a liberar. Lo que me hace  notar Bartomeu es que no se necesita ni un solo goto para realizar un código mucho más limpio (cosa que ya había implementado yo en mi propio código), amén de que no debo cerrar el handle si falla la llamada OpenPrinter, ya que no existe ningún manejador válido.


El código definitivo es el siguiente:



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)
        return false;


    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)
        if(pf->cJobs==0 && pf->Status==0)
            bResult=true;


    GlobalFree(pf);


    ClosePrinter(hPrinter);


    return bResult;
}


Observamos cómo ahora sí que se libera el puntero para albergar a PRINTER_STATUS_2.


Y ahora, más bugs
Es curioso esto del .NET, muy curioso, y conforme va pasando el tiempo le voy teniendo mucho más miedo. En un próximo post lo explicaré, y explicaré el que considero ya el summum de todos los bugs habidos y por haber…


Si recuerdan, en la anterior entrada, la función era estática. Pero si es estática, GlobalFree genera una excepción y tumba la aplicación por completo. No sé a qué pueda deberse, y perfiero no investigarlo.


¿Solución? Definirla como un método normal y corriente. Pero entonces, oh maravilla de todas las maravillas, el compilador me dice que el cuerpo de la función está redefinido. Bien. Si es estática no puedo ponerla en un CPP, si no lo es, tengo que ponerla en uno.


¿Y por qué me dice que el cuerpo de la función está redefinido? El tema es muy sencillo, y realmente podríamos catalogarlo como «feature» más que como bug, pero lo cierto es que el asunto nos da una idea bastante pobre de las interioridades del compilador.


El proceso de parseo de un proyecto C# es sencillo: se parsean todos los ficheros del proyecto uno detrás de otro como si fueran uno solo sobre una misma tabla de símbolos, y luego, en la siguiente pasada se resuelven los símbolos y se pasa todo a código objeto, que el enlazador juntará con poco más.


Esto en C++/CLI no se puede hacer así, ya que violamos el concepto de compilación separada. En C++ primero se parsean las parejas de ficheros cabecera/cuerpos, y luego se juntan y se enlazan. C++/CLI adopta una filosofía intermedia entre C# y el código nativo: primero se compilan las parejas, se juntan todas las tablas de símbolos de todas las parejas, y entonces se vuelven a parsear como si de C# se tratara. Por eso, en otros sitios, he dicho que el código generado con  C++/CLI es de mejor calidad que el de, por ejemplo, C#.


Lo que ocurre es, pues, que se obtiene dos veces un método IsPrinterOk, la primera cuando se parsea su propio fichero y la segunda cuando se junta todo. Y el compilador protesta en lugar de mirar si se trata del mismo código. Y también ese es el motivo por el cual algunos errores y warnings aparecen duplicados en un proyecto, aquellos que sólo se producen en la segunda pasada aparecen una sóla vez, y dos en los que se generan tanto en la primera como en la segunda.


Y eso nos da una ligera idea primero de los esfuerzos y de la complejidad del compilador de C++/CLI, y segundo de que no todo está claro y que el compilador es bastante chapucero.

5 comentarios sobre “Solución a "Obtener el estado de la impresora a través de .NET", y más bugs (y II)”

  1. Podrias explicar como puedo imprimir una pagina aspx, a la cual previamente le asigne una impresora por default, busco ejemplos y no lo he encontrado, solo en windows form.
    la idea es q el usuario imprima ya sin estar seleccionando a cada rato la impresora.
    muchas gracias

  2. Hola.
    Muchas felicitaciones por tu web.

    Mi duda o peticion es que ¿como puedo agregar ese procedimiento a mi rutina en visual basic .net?.

    Porfa me seria de mucha utilidad.
    soy novato en esto

    gracias de antemano.

  3. Sí.

    Mete la función en una clase estática:

    public static ref class PrinterStatus
    {
    public:
    static bool IsPrinterOk(String ^printerName)
    {
    //Poner el cuerpo de la función aquí.
    }
    };

    Créate una DLL de C++/CLI con visual studio, pega la función dentro de la clase y luego añades la DLL como un ensamblado .NET normal y corriente a tu proyecto.

  4. Pudieron compilar con C++/CLI?

    Cómo quedarian los archivos .cpp y el .h?

    Tengo que aclarar que intente compilarlo con Visual Studio 2002.

    Saludos.

  5. @Goncri, Visual Studio 2002 no soporta C++/CLI sino otro lenguaje completamente obsoletado llamado «managed C++»…

    Si puedes, actualizate al 2010 o al menos al 2008…

Responder a goncri Cancelar respuesta

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