miércoles, 9 de junio de 2010

¿Cómo poner ejemplos de código fuente?

Desde que empecé a escribir, poner en los blogs mis codigos fuentes era un problema por lo extenso que pueden llegar a ser sentía que podían incluso diluir la idea que quería transmitir.

Busqué entonces la forma de ponerlos en un tipo de area de texto con scrolling, la mejor que hallé fue usando el tag <div> de HTML:

<div style="overflow: auto; width: 750px; height: 250px; background-color: lightgray;">
... codigo fuente ... en cualquier lenguaje: C, C++, Java ...
</div>


Sin embargo, sólo esto no conserva la identación, tan necesaria para entender fácilmente el codigo fuente, opté por poner dentro del div, un "pre":

<div style="overflow: auto; width: 750px; height: 250px; background-color: lightgray;"><pre>
... codigo fuente ... en cualquier lenguaje: C, C++, Java ...
</pre></div>

El resultado es algo así como lo que sigue:
#include "mivector.h"
#include <stdio.h>

double vec1[3];
double vec2[3];

void inicializar(void) {
puts(">>>> colocar aqui código inicialización");
for (int i=0; i<3; ++i) {
vec1[i] = 2.0;
vec2[i] = 2.0;
}
}

void finalizar(void) {
puts("colocar aqui código finalización >>>>");
}

double productoEscalar(const double* p_vector1, const double* p_vector2) {
double result = 0;
for (int i=0; i<3; ++i)
result += p_vector1[i] * p_vector2[i];
return result;
}

void productoVectorial(const double* p_vector1, const double* p_vector2, double* result) {
result[0] = p_vector1[1]*p_vector2[2]-p_vector1[2]*p_vector2[1];
result[1] = -p_vector1[0]*p_vector2[2]+p_vector1[2]*p_vector2[0];
result[2] = p_vector1[0]*p_vector2[1]-p_vector1[1]*p_vector2[0];
}

Espero les sirva ;-)

domingo, 6 de junio de 2010

Usando Literales String en Unicode en C++

Me metí en un problema, pensé que podría tratar el tema brevemente, pero no, no es así...

Después que leí un poco me convencí de lo miserablemente ignorante que soy. Todo este cuento de los encodings para las cadenas de caracteres (strings), de ASCII con sus code pages, de los Wide Characters, de los multibyte characters, de unicode con sus UTF-8, UTF-16, UTF-32, de los Locales, del UCS (Universal Caracter Set) con sus UCS-2 y UCS-4. ¡Que sopa de letras!. En el continente americano y buena parte de Europa, por lo general no vamos mucho más allá de manejar los caracteres ASCII Latin-1 (ISO-8859-1) o el Windows-1252.

En fin me pregunté, ¿como manejar los nuevos encodings en Linux con C++?, si, me refiero a los encodings del estándard Unicode: UTF-8, UTF-16, UTF-32, no piensen que los voy a explicar aquí, para eso pongo las referencias, además los aburriría.

Al día de hoy Linux soporta UTF-8 de forma nativa, es por ello que hacer un programa en C++ que trabaje con este encoding es lo más directo que hay. Para demostrar que estoy usando Unicode usaré junto con los clásicos caracteres ASCII, caracteres griegos, cirílicos y una simpática flecha que pondré como ejemplo de una referencia a un Code Point.

Empecemos por declarar un string clásico de lenguaje C (const char*) usando UTF-8 dentro de nuestro código fuente:

Archivo: unicode.cpp:
const char *str_utf8 = "Hola mundo Unicode UTF-8: Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";

A manera de ejemplo puse unos code points de Unicode (para que los excépticos vean que sí estamos usando unicode). Esta cadena es de unos 64 caracteres de longitud (ojo dije caracteres, no dije bytes). En el nuevo estándar de C++, el prefijo "u8" se usa para indicar que el encoding de una cadena es UTF-8, "u" (minúscula) para cadenas UTF-16 y "U" (mayúscula) para UTF-32. Sin embargo, requeriría al menos de la versión 4.5.x del compilador de GNU para hacer uso de todos ellos, así que mantengamos el ejercicio simple: si tu ambiente linux soporta UTF-8, estos ejemplos deberían funcionar.

El primer experimento que haremos sera este:

#include <iostream>
#include <cstring>
#include <string>

using namespace std;

const char *str_utf8 = "Hola mundo Unicode UTF-8: Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";

int main(int argc, char **argv) {
cout << "String clásico C: " << endl
<< " " << str_utf8 << endl
<< " longitud según strlen: " << strlen(str_utf8) << endl;

string cpp_string(str_utf8);
cout << "String de la STL de C++:" << endl
<< " " << cpp_string << endl
<< " longitud según strlen: " << cpp_string.length() << endl;

return 0;
}


Deberán compilar con la linea de comando:

%> g++ unicode.cpp -o unicode

Si tienen los fonts correctamente instalados en su sistema, podrán ver los caracteres griegos, cirílicos y demás al ejecutar este programa en consola, de lo contrario les recomiendo los instalen (después de todo leen esto porque Unicode les ha dado curiosidad). Si ya leyeron de UTF-8 se podrán explicar al ejecutar esta línea, por qué la rutina de C para determinar la longitud de un string no sirve. strlen cuenta bytes y una cadena UTF-8 consta de caracteres "multi-bytes", un solo caracter puede tener entre 1 y 4 bytes. strlen reporta entonces una longitud que está por encima de la correcta y la clase std::string de librería STL de C++ aun no resuelve este problema.

Conociendo un poco de UTF-8, no es dificil deducir una rutina para contar los caracteres. Cada caracter compatible con ASCII es de la forma binaria "0xxxxxxx". Cada caracter multibyte empieza con la forma binaria "11xxxxxx" y los subsiguientes bytes que le corresponden son de la forma binaria "10xxxxxx", de modo que sólo nos interesan estos dos bits iniciales. Podemos hacer entonces un "and" binario (&) para decidir si contamos el byte como un caracter o no:

int utf8_strlen(const char *s) {
int j=0;
for (int i=0; s[i]; ++i)
if ((s[i] & 0xC0) != 0x80) j++;
return j;
}


Recordar que el hexadecimal 0xC0 es en binario "11000000", mientras que 0x80 es "10000000".

Nos encontramos nuevamente en el escenario de siempre... vamos a querer hacer una clase StringUTF8. Les pido que la busquen antes de hacerla.

Por otro lado algunos diríamos, usemos un std::wstring:

#include <iostream>
#include <cwchar>
#include <string>

using namespace std;

const wchar_t *wstr = L"Hola mundo Unicode UTF-8: Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";

int main(int argc, char **argv) {
wstring cpp_wstring(wstr);
wcout << "String de la STL de C++:" << endl
<< " " << cpp_wstring << endl
<< " longitud según strlen: " << cpp_wstring.length() << endl;

return 0;
}



Esto resuelve el problema de la longitud que es contada correctamente. Sin embargo el despliegue en consola del string no es correcto. No se pueden visualizar los caracteres pues la consola es UTF-8. Este encoding con wide-chars, caracteres anchos, se corresponde con el estandard UCS-4, que equivale a UTF-32. En este caso cada caracter es de 4 bytes, por ello es facil contar su longitud.

El último de los encondings que queda es el UTF-16. Sólo el nuevo estándar de C++ permite representar literales string en UTF-16 directamente en el código fuente de un programa (con el prefijo "u" frente a un string). De hecho, aprovecho esta sección para indicar cómo sería la declaración de los literales UTF en cada caso:

// UTF-8: prefijo "u8"
const char *str_utf_8 = u8"Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";
// UTF-16: prefijo "u" (minúscula)
const char16_t *str_utf_16 = u"Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";
// UTF-32: prefijo "U" (mayúscula)
const char32_t *str_utf_32 = U"Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";


Para que el compilador de GNU reconozca esos literales, tendrían que compilar su código con la opción para el nuevo estándar:
g++ -std=c++0x unicode.cpp -o unicode

Para las versiones del compilador que aun no soportan el nuevo estándar, se pueden declarar cadenas UTF-16. Recuerden a Java, una tecnología construida en C/C++, siempre ha ofrecido un soporte nativo a UTF-16. Veamos cómo se define en JNI (Java Native Interface) un caracter:

typedef unsigned short jchar;

Dado que no tenemos un soporte nativo para la declaración de un literal UTF-16, la única forma que quedaría sería entonces mediante un arreglo de jchar's inicializado:

#include <iostream>

using namespace std;

typedef unsigned short jchar;

const jchar jstr[] = {'H','o','l','a',' ','m','u','n','d','o',' ','U','n','i','c','o','d','e',' ','U','T','F','-','8',':',' ',
'G','r','i','e','g','o','s',' ',0x279c,' ',0x3a3,' ',0x3a5,' ',0x398,' ',0x3a9,'.',' ',
'C','i','r',0xED,'l','i','c','o','s',' ',0x279c,' ',0x402,' ',0x409,' ',0x40a,' ',0x416, 0};

int main(int argc, char **argv) {
int i;
for (i=0; jstr[i] & 0x00FF; ++i)
cout << static_cast<char> (jstr[i]);
cout << endl << "i:" << i << endl;

return 0;
}

Parece obvio por qué JNI usa funciones para convertir strings de Java a strings de C/C++.

Un último punto que quiero tocar, también muy superficialmente, es el tema de escribir estos literales en archivos. Lo primero que noté es que el locale de C/C++ afecta la forma en la que el literal se escribe en disco. Como el tema de los Locales es como para escribir otro artículo, sólo usaré el locale por defecto, en este escenario el wide string se convierte a UTF-8 cuando se utiliza el wofstream de C++, las cadenas UTF-8 se escriben sin problemas. Para obligar la escritura cruda de wide strings UTF-32 y de las cadenas UTF-16, se debe recurrir a las primitivas básicas de lenguaje C:

#include <iostream>
#include <string>
#include <cstdio>
#include <fstream>

using namespace std;

typedef unsigned short jchar;

const wchar_t *wstr = L"Hola mundo Unicode UTF-8: Griegos \u279c Σ Φ Θ Ω. Cirílicos \u279c Ђ Љ Њ Ж";
const jchar jstr[] = {'H','o','l','a',' ','m','u','n','d','o',' ','U','n','i','c','o','d','e',' ','U','T','F','-','8',':',' ',
'G','r','i','e','g','o','s',' ',0x279c,' ',0x3a3,' ',0x3a5,' ',0x398,' ',0x3a9,'.',' ',
'C','i','r',0xED,'l','i','c','o','s',' ',0x279c,' ',0x402,' ',0x409,' ',0x40a,' ',0x416, 0};

int main(int argc, char **argv) {
locale::global( locale( "" ) ) ;

wofstream ofile("test_utf_8.utf", ios_base::out | ios_base::trunc);
if (ofile.bad()) {
cout << "No se pudo abrir el archivo\n";
}
else {
ofile << wstr;
ofile.close();
}

wstring cpp_str(wstr);
FILE *cf_output = fopen("test_utf32.utf", "w");
fwrite(reinterpret_cast<const void*> (wstr), sizeof(wchar_t), cpp_str.length(), cf_output);
fclose(cf_output);

cf_output = fopen("test_utf_16.utf", "w");
fwrite(reinterpret_cast<const void*> (jstr), sizeof(jchar), cpp_str.length(), cf_output);
fclose(cf_output);

return 0;
}


Para verificar el tipo de estos archivos generados, lo pueden hacer con un editor hexadecimal como Bless o un editor unicode como EditPad Lite. Para este último, necesitarán Wine para poder ejecutarlo en Linux, pues es una aplicacion Win32.

Referencias

domingo, 16 de mayo de 2010

Librerías Compartidas en Linux con el Compilador C de GNU

Uno de los aprendizajes que más impactó mi forma de programar han sido las librerías compartidas de linux (dynamic shared objects), con esta entrada sólo quiero dejarnos lo más básico de cómo crearlas y usarlas.

Haré ejemplos muy cortos, quizá de poca utilidad práctica, pero espero dejar sentados los mínimos conceptos que necesitamos manejar para el 80% de los casos reales. Empezaré creando una librería para dos operaciones con vectores, el producto escalar y el producto vectorial. Exportaremos lo que se puede exportar de un librería compartida, variables y funciones:

Archivo mivector.h:
#ifndef _vector_h
#define _vector_h

double productoEscalar(const double* p_vector1, const double* p_vector2);
void productoVectorial(const double* p_vector1, const double* p_vector2, double* result);

#endif


Archivo mivector.c
#include "mivector.h"
#include <stdio.h>

double vec1[3];
double vec2[3];

void inicializar(void) {
puts(">>>> colocar aqui código inicialización");
for (int i=0; i<3; ++i) {
vec1[i] = 2.0;
vec2[i] = 2.0;
}
}

void finalizar(void) {
puts("colocar aqui código finalización >>>>");
}

double productoEscalar(const double* p_vector1, const double* p_vector2) {
double result = 0;
for (int i=0; i<3; ++i)
result += p_vector1[i] * p_vector2[i];
return result;
}

void productoVectorial(const double* p_vector1, const double* p_vector2, double* result) {
result[0] = p_vector1[1]*p_vector2[2]-p_vector1[2]*p_vector2[1];
result[1] = -p_vector1[0]*p_vector2[2]+p_vector1[2]*p_vector2[0];
result[2] = p_vector1[0]*p_vector2[1]-p_vector1[1]*p_vector2[0];
}


Copien este código, los dos archivos deben estar en la misma carpeta, deberan compilarlos usando la siguiente linea de comando:

%> gcc -Wl,-init=inicializar,-fini=finalizar,-soname=libmivector.so -o libmivector.so -std=c99 -fpic -shared -shared-libgcc mivector.c


No deberiamos dejar los parámetros de esta linea de comando sin explicar:

  • -Wl
    Especificar las opciones al linkeditor desde la línea de comando del compilador
    La opción -init, permite especificar el nombre de una función que deberá ser llamada en el momento en el que un proceso carga la librería
    La opción -fini permite especificar el nombre de una función que deberá ser llamada en el momento en el que un proceso descarga la librería
    La opción -soname asigna el nombre especificado de la librería dentro del archivo compilado

  • -o <nombre librería>
    El producto de la compilación quedará en un archivo del nombre especificado en el parámetro de eta opción

  • -std=c99
    Usar el estandar C99 de lenguaje C al momento de compilar el código

  • -fpic
    Es una opción para optimizar el código ejecutable relativo al acceso a la tabla global de desplazamientos (GOT), donde se registran todos los nombres de las funciones y variables que se pueden usar

  • -shared
    Produce como salida una librería compartida (un archivo .so)

  • -shared-libgcc
    Usar la librería libgcc que viene como una librería compartida



Pueden usar el comando "file" de linux para comprobar la naturaleza de esta archivo
%>file libmivector.so


Ahora veamos como usar la librería, existen dos escenarios:

  • Si queremos que la librería se cargue implícitamente al momento de invocar el programa principal

  • Si el programador debe cargar explícitamente la librería y localizar las funciones a invocar


Los dos escenarios tienen utilidad, el primero acopla más la librería al código del programa principal, pero es más optima y fácil de programar, la segunda es mas tediosa de programar pero desacopla al programa principal de la librería y permite añadirle extensiones sin necesidad de recompilarlo.

Carga Implícita de la librería
El programa principal asume que los nombres en la librería están disponibles. Veamos cómo sería:

Archivo ppal1.c
#include <stdio.h>
#include "mivector.h"

extern double vec1[3];
extern double vec2[3];
double result[3];

int main(int argc, char** argv) {
printf("Producto Escalar: %f\n", productoEscalar(vec1, vec2));
productoVectorial(vec1, vec2, result);
{
int i;
puts("Producto Vectorial");
for (i=0; i<3; ++i)
printf("[%d]=%f\n", i, result[i]);
}
return 0;
}


Compilen este programa con la siguiente linea de comando:
gcc ppal.c -lmivector -L. -o ppal


Expliquemos nuevamente las opciones usadas en la linea de comando:

  • -L Especifica las rutas desde donde deberán buscarse los archivos de librerías compartidas. En este caso sólo el directorio actual

  • -o Especifica el nombre del archivo producto de la compilacion-linkedición.

  • -lmivector
    Buscar en las rutas especificadas por la opcion "-L" una libreria compartida que tenga el nombre "libmivector.so".



Esto genera el programa ejecutable "ppal", sin embargo al ejecutarlo, ocurre lo siguiente:
%> ./ppal
./ppal: error while loading shared libraries: libmivector.so: cannot open shared object file: No such file or directory


Para resolver este problema debemos indicar al sistema operativo la ruta donde debe buscar por las librerías compartidas, esto se hace mediante la variable de ambiente LD_LIBRARY_PATH:
%> export LD_LIBRARY_PATH=.

Asumiendo que nuestro shell es Bash, le estamos diciendo que nuestra libreria compartida "mivector" se encuentra en el directorio actual.

Ahora si:
%> ./ppal
>>>> colocar aqui código inicialización
Producto Escalar: 12.000000
Producto Vectorial
[0]=0.000000
[1]=0.000000
[2]=0.000000
colocar aqui código finalización >>>>


Carga Explícita de la librería
En este caso nuestro programa principal no se enlazó con la librería y por lo tanto no conoce nada de la misma ni de los símbolos que la misma exporta, por lo tanto debemos buscarlos para poder usarlos:

Archivo ppal2.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

double *vec1;
double *vec2;
double result[3];

double (*productoEscalar)(const double* p_vector1, const double* p_vector2);
void (*productoVectorial)(const double* p_vector1, const double* p_vector2, double* result);

int main(int argc, char** argv) {
void* module=dlopen("libmivector.so", RTLD_LAZY);
if (!module) {
puts("no se pudo encontar la librería \"mivector\"");
exit(1);
}

productoEscalar = dlsym(module, "productoEscalar");
productoVectorial = dlsym(module, "productoVectorial");

vec1 = dlsym(module, "vec1");
vec2 = dlsym(module, "vec2");

printf("Producto Escalar: %f\n", productoEscalar(vec1, vec2));

productoVectorial(vec1, vec2, result);
{
int i;
puts("Producto Vectorial");
for (i=0; i<3; ++i)
printf("[%d]=%f\n", i, result[i]);
}

dlclose(module);
return 0;
}


Para abrir la librería se usa la función dlopen, una vez abierta debemos asegurarnos de cerrarla con dlclose.
Nótese cómo el código fuente no incluye la cabecera de la librería "mivector" y la dirección de los símbolos, ya sean variables o funciones, deben obtenerse explícitamente con la función dlsym para poder usarlos puesto que no se enlazaron estáticamente al ejecutable.

Compilar con la siguiente línea de comando:
%> gcc ppal2.c -ldl -o ppal

Nótese como el programa no se enlaza con la librería "mivector", por otro lado, ¿qué es esa librería "libdl.so"?, se trata de la librería que permite cargar las librerías y obtener las direcciones de sus símbolos.

En este caso deben también asignar un valor adecuado a la variable de ambiente LD_LIBRARY_PATH, de modo que la función dlopen pueda encontrar la librería "mivector", de lo contrario el programa terminará su ejecución.

Referencias:

Seguidores