May 2010 - Artículos

Suele ser una pregunta bastante habitual en los pocos foros dedicados a C++ que hay, y vamos a intentar dar aquí una respuesta contundente y razonada de por qué ocurre eso y cómo solucionarlo.

La pregunta sobre la ejecución de un programa hecho en C, C++ o C++/CLI en otro equipo (en general en el del cliente final) suele tener algunas variantes en cuanto al texto mostrado, entre las que está:

  • Salta una ventana con “Error 135”.
  • Salta una ventana o aparece un registro en el Visor de Eventos que dice algo como “No se pudo iniciar la aplicación; la configuración en paralelo no es correcta.”.
  • Salta un error con un mensaje similar a “La aplicación no se pudo ejecutar porque ha sido incorrectamente instalada”.
  • En casos más raros, y para programas creados con versiones antiguas de Visual C++, suele aparecer algún mensaje del tipo “Falta ordinal <número> en <nombre>.DLL”.
  • Aparece un mensaje que dice “No se encuentra el archivo <nombre>.DLL.”

En resumen, el mensaje, a veces muy críptico, confunde por completo al programador novel y al no tan bisoño porque a veces, reconozcámoslo, el texto es todo un galimatías.

Existen varios motivos por los cuales se puede presentar el problema, pero principalmente están todos englobados dentro del mismo concepto: el ordenador destino carece de algunos ficheros que son necesarios para la ejecución de nuestra aplicación.

***

Debug vs Release. Cuando nosotros creamos un proyecto en Visual Studio, este se crea en modo Debug por defecto. Esto quiere decir que se está compilando de una forma un poco “especial”, ya que tanto el compilador como el enlazador insertan una serie de funciones extra que nos van a permitir muchas veces detectar un error en nuestro código con solo ejecutar el programa, y a veces incluso hasta en tiempo de compilación.

También se enlaza con una serie de bibliotecas modificadas que nos van a permitir, de nuevo, encontrar errores de forma mucho más rápida.

Por ejemplo, si nuestro programa tiene una fuga de memoria (es decir, una variable dinámicamente asignada pero nunca liberada), nos aparecerá un mensaje en la ventana de “Output” de Visual Studio por cada una de las variables que queden sin liberar al cierre de la aplicación. Aparecerá un número y los bytes que no se han recobrado. Siguiendo unos pasos que describiré la semana que viene, es posible encontrar de forma automática el new correspondiente al delete que se ha omitido. De esta forma algo que puede llevar varias horas o días, Microsoft nos lo soluciona en segundos.

Otra cosa que también se detecta fácilmente son los desbordamientos de búfer y el uso de variables no asignadas, casi por el mismo procedimiento descrito en el párrafo anterior. Nunca antes había sido tan fácil encontrar este tipo de problemas.

Las compilaciones Debug comprueban otras muchas cosas, como aserciones en el código de MFC para detectar pifias que de otra manera podrían terminar en un pete de la aplicación bastante difícil de solucionar.

Una vez tenemos nuestra aplicación funcionando correctamente sin mensajes de error en la ventana de “Output”, debemos pasar al modo Release, que se hace mediante un desplegable en la barra de herramientas de Visual Studio o con las opciones del proyecto.

Debemos entonces volver a comprobar nuestra aplicación para que no tenga errores. En general el paso de Debug a Release se suele hacer de forma bastante poco traumática, pero hay veces que el cambio hace que nuestra aplicación no funcione.

No hay problema, todavía podemos depurar en Release, pero el flujo del programa puede resultar extraño porque veremos saltos sin mucho sentido y ejecuciones de código raras, ya que el código está optimizado y el compilador de Visual C++ es muy bueno en eso. Aquí debemos tener cuidado a la hora de depurar, porque lo que veamos en las ventanas de inspección seguro que no es lo que esperamos. La forma de asegurarnos que una variable es lo que es, es utilizar la función OutputDebugString(), que nos imprimirá lo que enviemos en la ventana de “Output” de Visual Studio.

En general, los errores que se presentan en Release pero no en Debug se suelen deber a temas de sincronización entre hilos y a tiempos, porque el ejecutable va mucho más rápido, por lo que es por donde debemos empezar a buscar los problemas ya que los punteros locos los hemos solventado en Debug.

***

Ya llego, ya. Uno de los problemas que podemos tener al ejecutar nuestro programa en otro ordenador es que estemos intentándolo con una versión Debug. Aparte de que la licencia de uso de Visual C++ nos lo impide, en la máquina de destino faltan una serie de elementos que son necesarios para el ejecutable en modo Debug, y es por eso por lo que obtenemos el error. En nuestra máquina de desarrollo no falla porque dichos elementos están presentes en el sistema.

Nota: la licencia no nos impide ejecutar código Debug en una máquina cliente, lo que nos impide es distribuir como final una compilación Debug. Por experiencia personal a veces es necesario instalar toda la parafernalia de depuración remota en la máquina cliente para poder solucionar errores que no se presentan ni en los ordenadores de desarrollo ni en los de pruebas.

***

Runtimes. Es muy posible que, pensando que estamos con C++, creamos que no existen runtimes al más puro estilo de Visual Basic. Pues bien, sí que existen.

C y C++ son lenguajes agnósticos en cuanto a biblioteca o, en otras palabras, el lenguaje no exige ningún tipo de biblioteca para funcionar, por oposición a Java o a C# que necesitan de toda una parafernalia asociada a ellos para actuar.

Pero todos sabemos que C tiene funciones como printf(), strlen() y demás, funciones que no ofrecen ni Win32 ni ningún otro sistema operativo. A C++ le pasa lo mismo, tiene la STL y compañía (y no vamos a entrar en detalles si es biblioteca de C++, STL o la madre que lo parió).

Eso tiene que ir en algún sitio, y va en una serie de bibliotecas que suelen acompañar a los compiladores y, aunque nadie nos obliga a utilizarlas (yo de hecho tengo algunos programas que no las usan y sólo usan Win32 puro y duro), lo habitual es que lo hagamos.

En Visual C++ y en la mayoría de los compiladores hay dos formas de tener bibliotecas: estáticas y dinámicas. Las estáticas son ficheros .LIB que se añaden a las opciones de nuestro compilador y se integran dentro de nuestro ejecutable. Las dinámicas suelen ser ficheros DLL que se asocian a nuestro ejecutable a través de otro fichero .LIB que contiene una indicación de lo que hay en cada DLL (ver mi entrada Todo lo que quisiste saber sobre las DLL y no te atreviste a preguntar (II) ).

En general, en Visual C++, y si no se indica otra cosa, la compilación y enlazado se realiza a través de la versión en DLL del motor de tiempo de ejecución. ¿Por qué se hace así por defecto? Pues porque teniendo el runtime en DLL, Windows Update puede actualizarlo si se produjera algún fallo de seguridad. Si el código que reside en la DLL a actualizar está en nuestro ejecutable, la única manera de hacerlo sería que instaláramos la actualización en nuestro compilador y recompiláramos el programa.

Podemos cambiar en las opciones del compilador para enlazar estática o dinámicamente. Fijaros en la imagen adjunta:

image

Podemos elegir tanto versiones debug como release de las DLL así como de las estáticas. MFC también tiene la misma posibilidad:

image

Para no liarnos, debemos enlazar con las versiones Debug cuando estemos en compilación Debug y lo mismo en Release.

No obstante yo os recomiendo que dejéis la opción DLL porque así vuestra aplicación será más segura.

Cuando ejecutemos un programa con enlace dinámico en un ordenador que no tenga instalado el runtime adecuado también nos saltará un mensaje de error de los descritos arriba. Cuando se producía un error de este tipo con los programas escritos en Visual C++ 6 y anteriores, saltaba una ventana que te indicaba que no se había encontrado el fichero tal. Y cuando lo añadías te saltaba otra con un nuevo fichero hasta que habías copiado todos. Pero a partir de las versiones 2002 de Visual C++, se muestran los citados mensajes crípticos, cosa que realmente no entiendo, con lo fácil que es colocar en el stub de arranque (lo que se enlaza antes que tu programa) un mensaje diciendo que no se encuentra el runtime o que éste es incorrecto. Inescrutabilidades de la gente de Microsoft.

En principio Windows trae de serie todos los runtimes instalados. Pero claro, sólo hasta la fecha en la que el sistema operativo salió. Con el tiempo la gente de Microsoft empieza a compilar los parches y las actualizaciones con versiones nuevas de Visual C++ (sí, Windows y Office están compilados con los mismos compiladores que usamos nosotros, o casi, ya que Microsoft suele utilizar unas versiones internas estables algo más antiguas, pero en muchos momentos nos mantenemos sincronizados y, por ejemplo, Windows 7 está compilado con el mismo compilador que viene con el SDK de su versión, o eso dicen).

Conforme Microsoft va usando versiones más modernas, estas se van instalando en Windows cuando pasamos Windows Update o instalamos alguno de sus productos, por lo que a veces nuestro programa falla al ejecutarse en un ordenador y lo hace en otro casi igual pero que por ejemplo tiene una versión más moderna de Office o de otro producto.

***

Cuando llegamos a uno de estos errores debemos instalar lo que Microsoft llama el Visual C++ Redistributable y que podemos bajar de su Web de descargas. Eso sí, nos tenemos que bajar la versión adecuada a la versión de Visual C++ y a la plataforma (32 ó 64 bits) que estemos usando. Algunos redistributables son:

  • Visual Studio 2005 (x86, x64)
  • Visual Studio 2005 Service Pack 1 (x86, x64)
  • Visual Studio 2008 (x86, x64)
  • Visual Studio 2008 Service Pack 1 (x86, x64)
  • Visual Studio 2010 (x86, x64)

Una vez instalado, la próxima vez que Windows Update haga una pasada, actualizará todos los problemas de seguridad que puedan tener.

Una cosa curiosa de todo esto es el “WinSxS”, que está en la carpeta C:\Windows\winsxs. Si nos metemos ahí dentro veremos una buena espuerta de DLL y otros ficheros agrupados por carpetas. Debemos hacerlo con cuidado porque si tocamos algo ahí sin querer quizás tengamos que reinstalar Windows porque es una carpeta muy sensible para la estabilidad del sistema. Ahí dentro es donde están todas las versiones de todos los runtimes que hemos instalado y/o usado a lo largo del tiempo, con todas sus actualizaciones de seguridad y demás parches. Microsoft utiliza una serie de heurísticas no documentadas para determinar qué versión conectar con qué programa, y si una actualización de algún tipo hará que el programa falle y por tanto deberá seguir enlazándolo con la versión anterior en lugar de la nueva. Y sí, también con nuestros programas, y resulta curioso porque a todos los efectos la aplicación está viendo una serie de DLL situadas en C:\Windows\System32.

***

C++/CLI y .NET. Sólo nos queda ya la última posibilidad. Si estamos usando C++/CLI y .NET debemos tener instalada la versión adecuada en el ordenador de destino. A veces el programador novel no sabe que está usándolo, pero si utilizas Forms y demás, seguro que lo estás haciendo.

Aquí las apuestas suben, porque a toda la complicación anterior añadimos el .NET. En general en este caso solemos tener un “Error 135” (pero no siempre), que quiere decir que el cargador del sistema no entiende la firma del ejecutable y no sabe qué entorno de ejecución aplicar.

Una vez instalada la versión adecuada del .NET, si nos sigue saltando algún error como los del principio, debemos aplicar lo mismo que hasta ahora. Es decir, pasar a Release (aunque el .NET no lo exige, ciertas partes del propio C++/CLI sí) e instalar el runtime adecuado o cambiar a compilación estática.

A veces no podremos pasar a compilación estática porque por ejemplo la opción “/clr:pure” lo impide y tendremos que instalar el runtime por narices. También puede ocurrir que hagamos dos programas y uno nos exija el runtime y el otro no. Dependerá de qué estemos usando en C++/CLI y si empleamos interoperación por atributos, del tipo IJW (usar C++ y C++/CLI de forma mixta), ambas o ninguna. Ver mi entrada ¿Qué es C++ y qué es C++/CLI?).

***

Instalador. Y siempre, siempre, podremos crearnos un instalador, que añadirá todos los runtimes y demás zarandajas de forma que llevando todo eso al ordenador cliente, y una vez instalado, nuestro programa funcionará perfectamente.

El problema aquí es que mucha gente no quiere instaladores por la basura que introducen en el sistema, y hay empresas que exigen que los programas no necesiten ni instalación ni runtimes.

También, a veces, hacer un instalador para un programa sencillito que nos han pedido puede ser matar moscas a cañonazos y tardamos más tiempo haciendo el instalador que el programa. Además, a veces hacer un instalador es un oficio en sí.

[There is an English version of this post here]

Si estás usando Codejock ToolkitPro en Visual Studio 2010 te habrás dado cuenta de que no se añaden las rutas por defecto de Codejock en los proyectos de VC porque VS 2010 ha cambiado la forma en la que se controlan las rutas por defecto de C++.

En versiones previas estaban incluidas en las opciones del entorno, pero en esta nueva versión se incluyen en cada proyecto, por lo que el instalador de Codejock no puede ponerlas en el lugar adecuado, y cuando intentas compilar un programa obtienes errores en relación a ficheros no encontrados.

Resolver esto es muy sencillo y vamos a explicar cómo hacerlo.

1. Abre un proyecto de C++. Lo primero es abrir cualquier proyecto de C++ con Visual Studio. No importa si es uno de Codejock o no. Simplemente ábralo, vaya a “View -> Property Manager” expanda “Debug | Win32” y/o “Debug | x64” (o sus equivalentes en Release):

image

2. Elija “Microsoft.Cpp.Win32.user” o “Microsofr.Cpp.x64.user”, haga clic con el botón derecho del ratón y seleccione “Properties”. Apunte a “VC++ Directories”:

image

3. Ahora es el momento de añadir las rutas. Despliegue cada elemento y añada una nueva ruta:

image

4. Dependiendo de si las propiedades que ha elegido son Win43 o x64, las rutas pueden diferir, pero no mucho. La tabla siguiente explica qué rutas se añaden y dónde se añaden en relación a la ruta base de instalación de Codejock, que para la útlima versión que tengo instalada está en C:\Program Files (x86)\Codejock Software\MFC\Xtreme ToolkitPro v13.3.1:

Win32 (x86 project)

Option

Path

Executable

Bin\vc100

Include

Source

Reference

Lib\vc100

Library

Lib\vc100

Source

Source

Exclude

[nada]

Win32 (x86 project)

Option

Executable

Bin\vc100x64

Include

Source

Reference

Lib\vc100x64

Library

Lib\vc100x64

Source

Source

Exclude

[nada]

5. Necesitamos realizar un último paso antes de cerrarlo todo. Tenemos que ir a “Common Properties -> Resources -> General” y añadir a “Additional Include Directories” la misma ruta que en Source tanto en Win32 como en x64:

image

6. Finalmente tenemos que cerrarlo todo y aceptar cuando se nos pregunte sobre hojas de propiedades al cerrar Visual Studio.

Después de hacer esto ya no es necesario repetirlo porque las opciones se han guardado en archivos XML almacenados en la ruta C:\Users\<username>\AppData\Local\Microsoft\MSBuild\v4.0 y serán usados por cualquier proyecto de C++.

Sólo se necesita un paso final más si quiere ejecutar programas hechos con Codejock como biblioteca DLL: tiene que añadir ambas rutas de la opción de “Executable” al Path global de su ordenador, lo que se puede hacer a través de “Advanced systems Settings” en “Computer Options”.

con no comments
Archivado en: ,

[Hay una versión de esta entrada en Español aquí].

If you are using CodeJock ToolkitPro in Visual Studio 2010 you can observe that it does not add the default Codejock paths to the VC projects because VS 2010 has changed the way controls the default C++ paths.

In previous versions they were locked into options. In this new version they are in a per project basis. Then Codejock installer cannot put them in the right place and when you try to compile a program you get some errors about files not found.

It is very easy to solve this problem and we are going to explain it here.

1. Open a C++ project. First thing to do is to open any C++ project with Visual Studio. Does not matter if it is not a Codejock one. Only open it and go to View -> Property Manager and expand “Debug | Win32” and/or “Debug | x64” (or Release equivalent ones):

image

2. Select “Microsoft.Cpp.Win32.user” or “Microsofr.Cpp.x64.user”, right click and select “Properties”. Point in “VC++ Directories”:

image

3. Now is time of add the paths. Drop down each element and add a new path:

image

4. Depending if the project Properties options selected is x86 or x64, path may differ, but not much. Next table explains what paths add in what options relative to Codejock base installation path, that for my last version is (C:\Program Files (x86)\Codejock Software\MFC\Xtreme ToolkitPro v13.3.1):

Win32 (x86 project)

Option

Path

Executable

Bin\vc100

Include

Source

Reference

Lib\vc100

Library

Lib\vc100

Source

Source

Exclude

[none]

Win32 (x86 project)

Option

Executable

Bin\vc100x64

Include

Source

Reference

Lib\vc100x64

Library

Lib\vc100x64

Source

Source

Exclude

[none]

5. A final step is needed after close all. We need to go to “Common Properties -> Resources -> General” and add to “Additional Include Directories” the same path as in Source and in both Win32 and x64:

image

6. Finally we need to close all and accept when asking about saving inherited user properties on Visual Studio close.

After doing this you do not need to repeat it anymore because the options are saved in XML files stored in C:\Users\<username>\AppData\Local\Microsoft\MSBuild\v4.0 and used by any C++ project.

One final step is needed if you want to run Codejock built software linked in DLL files: add both Win32 and x64 bin paths to global path. It can be done via “Advanced systems Settings” in “Computer Options”.

con 1 comment(s)
Archivado en: ,

Bartomeu es un chavalote que suele andar por algunos foros y news, tanto respondiendo a la gente como haciendo él mismo preguntas, y el otro día presentó una idea bastante buena en el grupo de desarrollo de las news de Lechado.

Muchas veces nos encontramos con el problema de extraer un byte de una variable más larga para realizar diferentes operaciones con él, y muchas veces también nos liamos un poco con tanto hexadecimal y operador lógico… pero dejemos que sea él mismo quien nos lo explique:

El otro día tuve un momento de inspiración y, buscando otra cosa, inventé una nueva solución para un problema clásico que no necesitaba solucionar en ese momento.

El problema es cómo conseguir sólo el segundo byte de una variable long. (El problema es análogo para conseguir cualquier otro byte). Hay tres soluciones clásicas, (suponiendo que L es el long):

1º solución: La matemática. En tiempo de ejecución

char B1 = (L/256)%256; // falla si L<0

2º solución: La lógica. En tiempo de ejecución

char B2 = (L&0xff00)>>8;

3º solución: Con union. En tiempo de compilación

union {
    long L;
    char B[4];
} U;

U.L = L;
char B3 = U.B[1];

Obliga a crear una union y a grabar los datos por un campo y leerlos por otro

4ª solución: ¡¡Nueva!! En tiempo de compilación

char B4 = reinterpret_cast<char *>(&L)[1];

A primera vista puede intimidar. Pero el codigo generado por el compilador es exactamente el mismo que se genera en la instrucción "B3=U.B[1]" y te ahorras tener que definir ninguna union ni tienes que usar la instrucción previa "U.L = L".

Tiene la ventaja añadida que sirve para cualquier tipo de L. Con esa instrucción obtienes el segundo byte de cualquier cosa.

Además, por si fuera poco, tambien sirve como operador a la izquierda;

reinterpret_cast<char *>(&L)[1] = 0;

Ahora el segundo byte de L es cero, los otros bytes siguen igual.

Este uso del reinterpret_cast no lo he visto en ningún otro sitio y no se si he tenido una buena idea o tiene algún inconveniente que no se ver.

con 3 comment(s)
Archivado en: