Bugleanos… o la historia de un bug que no lo es

bool Desde que apareció el primer bug de la historia, los desarrolladores los venimos sufriendo con frecuencia. Siempre que nos enfrentamos a un comportamiento anómalo, pasamos por varias fases. En una primera fase asumimos que hemos cometido un error, depuramos y le damos vueltas a asunto hasta que, varias madiciones depués, pasamos a la siguiente fase. No puede ser mi fallo, el error tiene que estar en las librerías que estoy utilizando. Y pasamos a buscar un ‘workarround’… Total no nos queda otro remedio. A menudo es más dificil conseguir que el autor de la librería corrija el bug que simplemente asumir que sea o no nuestro bug, nosotros lo estamos sufriendo y a nosotros nos compete corregirlo.


De todos modos siempre conviene recordar que ‘el printf no está roto’, máxima mencionada en el excelente The pragmatic programmer, que nos recuerda que, es más probable que el error sea debido a nuestro desconocimiento o a nuestro código que a la librería que estamos utilizando. Sobre todo si es una librería de tan amplísimo uso con el Framework de .Net.


También es cierto a que a veces ocurre que los diseñadores de la librería han tenido que tomar decisiones de diseño que contradicen lo que suponemos lógico desde nuestro puesto de vista, lo que nos puede llevar a proclamar al mundo equivocadamente que hemos encontrado un bug, uno que lleva seis años sin corregirse, uno que se podría corregir con una simple línea de código… como hace poco clamaba mi vecino de blog en Geeks.ms, Rafael Ontivero. Quizás también se deba a que nuestro ego como desarrolladores crece mucho cuando encontramos un bug en una pieza de software que sabemos que ha sido escrita por excelentes ingenieros y desarrolladores… no se puede negar que es tentador el quejarse y el darle ‘bombo’ a la situación… pero siempre conviene recordar que: ‘el printf no esta roto’… (aunque también es cierto que algunas implementaciones del runtime de C, han sufrido un printf roto).


¿Pero que es lo que llevo a Rafael a pensar que se encontraba ante un bug que hacía que PInvoke no fuese capaz de hacer correctamente el marshalling de los tipos booleanos?: Desconocer la decisión de diseño que en su día se tomo a la hora de en la implementación de P/Invoke mapear ‘booleanos’ de lenguajes nativos como C y C++ a booleanos de .Net.


Nótese que cuando hablo de ‘booleanos’ de C y C++ lo pongo entre comillas. El motivo es que en C no existe un tipo de datos booleano, por lo tanto los desarrolladores de C hemos usado un viejo truco desde hace mucho tiempo: definer BOOL o BOOLEAN como tipos booleanos. Por ejemplo cuando programamos en entornos Windows nos encontramos que en WinDef.h tenemo la siguiente definición de BOOL: typedef int BOOL; y en WinNT.h typedef BYTE BOOLEAN; el resultado final es el que tenemos dos tipos ‘booleanos’ con diferente tamaño. Esto en lo que se refiere a C, por que con la aparición de C++ la situación se embroyo aun un poquito más, puesto que C++ define un tipo boolano nativo… cuyo tamaño depende de como lo haya decidido implementar quien implementa el compilador…


Resumiendo: No hay manera humana de saber el tamaño que un tipo ‘booleano’ tendrá en el mundo nativo. Esto sin duda se convirtio en una paradoja de dificil solución para los desarrolladores que implementarón P/Invoke: ¿A cúal de todos los tipos nativos booleanos mapeamos el tipo booleano de .Net por defecto? La idea es que cuando tenemos una declaración de este estilo:

[DllImport(«dllNativa.dll»)]
extern public static bool FuncionQueDevuelveBool();

Se puede corresponder con una variedad de declaraciones nativas:

extern «C» DLLNATIVA_API bool FuncionQueDevuelveBool(void);
extern «C» DLLNATIVA_API BOOL FuncionQueDevuelveBool(void);

extern «C» DLLNATIVA_API BOOLEAN FuncionQueDevuelveBool(void);

La cuestión es que como ya hemos indicado cada uno de esos posibles tipos booleanos tiene un tamaño diferente. Pues bien cuando los desarrolladores de P/Invoke decidieron a que tipo mapear el tipo bool en las declaraciones con DllImport decidieron mapearlo a un tipo de 4 bytes (generalmente BOOL en C es un tipo de 4 bytes) como podemos ver en la MSDN. ¿Cúal es el motivo de esta decisión? Pues no tengo la certeza pero todo apunta a que el motivo es que las fuciones del API de Windows que devuelven un valor booleano lo hacen usando BOOL que como ya hemos dicho, es un typedef de int y por tanto tiene un tamaño de 4 bytes. Sin embargo el tamaño de System.Boolean es un byte y es el marshalling de tipos de PInvoke el que hace la necesaria conversión, pero siempre asumiendo esos tamaños. Si nuestra función nativa utiliza alguno de los ‘booleanos’ nativos que tienen un tamaño diferente de 4 bytes (bool o BOOLEAN son ambos de un byte), el marshalling por defecto no nos sirve.


La solución es simple. Como bien apuntaba Rafael, podemos usar el atributo MarshalAs para decorar la declaración de nuestra función .Net equivalente a la función nativa especificando así el tipo no manejado que subyace bajo el booleano nativo:

[DllImport(«dllNativa.dll»)]
[return: MarshalAs(UnmanagedType.I1)]
extern public static bool FuncionQueDevuelveBool();

En este caso por ejemplo se indica que el tipo booleano que estamos usando es de un byte (I1 == Integer de un byte).


Decir que usar FxCop hubiese librado al amigo Rafael de sufrir este desagradable ‘no bug’. FxCop incorpora una regla MarkBooleanPInvokeArgumentsWithMarshalAs que nos avisa de que lo más adecuado es especificar explicitamente el tamaño del tipo cuando hacemos marshalling de tipos booleanos en P/Invoke.


Comentar por último que lo aquí expuesto también se aplica al manejo de booleanos con COM Interop pues también se produce el proceso de marshalling.


Con esto queda aclarado el extraño caso de los ‘bugleanos’…

10 comentarios sobre “Bugleanos… o la historia de un bug que no lo es”

  1. Bueno el printf no esta malogrado (pues no esta roto… malogrado pero enterito) pero si el scanf.

    Me acuerdo que en una versiones de Borland habia que añadir una funcion extra para «enseñarle» al compilado a hacer ciertas cosas al procesar flotantes, suerte que mi jefa de laboratorio me lo conto en plena practica que si no me hubiera vuelto loco…

  2. «Lo más grande del tipo bool es que si te equivocas tan sólo estás a un bit de la respuesta correcta»

    Realmente, no es cierto pero… qué geek queda soltar esa frase en medio de una charla.

    Corolario: Al mal tiempo, buena cara 🙂

    Un abrazo maestro!

  3. uuh un Caza»Cazadores de Bugs» jeje… buena aclaracion….muy buena:)….y que opinas del que yo puse ???? en elVS 2008… me gustaria ver tu comentario..

    Salu2

    Ddaz

  4. Enesto: Ya comento que hay printf rotos… pero la idea debajo de ese principio es que este o no roto el problema es tuyo y que es mucho más adecuado pensar que el bug es tuyo que es de la librería base. En ‘The pragamatic programmer’ lo explican mejor… pero esa es la esencia.

    Miguel: En el caso que comentamos podemos estar a 1, 2 o 4 bytes… según tipo ‘booleano’ elegido y plataforma de compilación… ejejjejej… pero la frase mola 😛

    Ddaz: Creo que los bugs de VS son de otra naturaleza que los de .Net y mucho más frecuentes… trataré de sacar un rato para mirar en detalle el bug que comentabas…

    Un saludo a todos y gracias por los comentarios!

  5. Pues yo he de romper una lanza en favor de Rafael…

    Si no «airease» con esa vehemencia los «no bugs», muchos se quedarían (entre los que a veces me incluyo) sin saber estos interesantes detalles, que puede que sean raros, pero tenerlos en el «utility-belt» está muy bien para corregir comportamientos extraños.

    Eso sí, he de decir que las formas restan credibilidad a los argumentos. 😉

    Gracias Rodrigo por la excelente explicación 😉

    Saludos!

  6. Aupa Augusto!!!
    Comparto lo que dices. Rafael comenta temas interesantes y se pega con cosas de bajo nivel que no son muy habituales pero que exprimen .Net… pero las formas le pierden un poco…

    Un saludo!

Deja un comentario

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