![]() | ![]() | Guía de programación de GNOME | ![]() |
---|
Para bibliotecas estables y ya liberadas, es muy importante intentar preservar la compatibilidad binaria en las revisiones mayores del código. Los desarrolladores de bibliotecas deben intentar no alienar a los usuarios realizando de forma frecuente cambios que sean binariamente incompatibles en bibliotecas que son de producción. Por supuesto, en aquellas bibliotecas que se encuentran en desarrollo o están etiquetadas como no finalizadas puedes realizar tantos cambios como se necesiten.
Una biblioteca típicamente exporta un número de interfaces. Estas incluyen nombres de rutinas, las firmas o prototipos de estas rutinas, variables globales, estructuras, campos de estructura (tanto los tipos como su significado), el significado de los valores enumerados y la semántica de los archivos que la biblioteca crea. Mantener compatibilidad binaria significa que estas interfaces no cambiarán. Puedes añadir nuevas interfaces sin romper la compatibilidad binaria, pero no puedes cambiar las ya existentes, ya que los programas antiguos no se ejecutarán correctamente.
Esta sección incluye un número de ideas acerca de como lograr que el código de una biblioteca sea compatible con sus versiones anteriores.
Mantener la compatibilidad binaria significa que los programas
y archivos objetos que fueron compilados con una versión
previa del código continuarán funcionando sin recompilar aún
cuando la biblioteca sea reemplazada. Es posible encontrar
una descripción detallada en el nodo info
(libtool)Interfaces
de la documentación
de GNU libtool.
En el sistema GNOME es una tarea muy común crear un nuevo GtkObject para crear una estructura cuyo primer miembro corresponde a la clase de la cual hereda este objeto y posteriormente un número de variables de instancia que se añaden después del primer miembro.
Este esquema de diseño normalmente lleva a que el código sea difícil de actualizar y cambiar: si se quiere extender un objeto donde se necesita mantener su estado interno, los programadores necesitan recurrir a varios hacks para mantener el mismo tamaño de las estructuras. Esto hace difícil agregar nuevos campos a las estructuras públicas sin romper la compatibilidad binaria, ya que los campos de la estructura pueden cambiar y el tamaño de las estructuras pueden variar.
Por consiguiente, se sugiere una estrategia que puedan adoptar los desarrolladores en el momento de crear nuevos objetos. Esta estrategia asegurará la compatibilidad binaria y al mismo tiempo mejorará la legibilidad y mantenibilidad del código.
La idea es que, para un objeto dado, el programador debiera crear tres archivos: el primero que contiene el contrato entre la biblioteca y el usuario (esto es el archivo de encabezado que se instala en el sistema); el segundo contiene la implementación del objeto; y el tercer archivo contiene la definición de la estructura para los campos privados o internos que no necesitan estar en la estructura pública.
La API pública de la estructura podría incluir un puntero a un elemento privado, el cual podría apuntar a los datos privado de la instancia. Las rutinas de implementación podría dereferenciar este punto para acceder a los datos privados; por supuesto, el puntero apunta a la estructura que se localiza privadamente.
Por ejemplo, imagina que se crea el objeto GnomeFrob. La implementación del widget será dividido en tres archivos:
Tabla 1. Archivos que muestran el widget de ejemplo GnomeFrob
Archivo | Contenido |
---|---|
gnome-frob.h | API pública de GnomeFrob |
gnome-frob.c | Implementación del objeto Gnomefrob |
gnome-frob-private.h | Datos privados de la implementación de GnomeFrob. Debieras querer usar esto si planea compartir los datos privados a través de varios archivos C. Si limitas el uso de la información privada a sólo un archivo, no necesita esto, ya que al definir la estructura en el archivo de implementación se logrará el mismo efecto. |
Ejemplo 1. Ejemplo de la API pública de gnome-frob.h
typedef struct _GnomeFrob GnomeFrob; typedef struct _GnomeFrobPrivate GnomeFrobPrivate; struct _GnomeFrob { GtkObject parent_object; int public_value; GnomeFrobPrivate *priv; } GnomeFrob; GnomeFrob *gnome_frob_new (void); void gnome_frob_frobate (GnomeFrob *frob);
Ejemplo 3. Ejemplo de la implementación de
gnome-frob.c
.
void gnome_frob_frobate (GnomeFrob *frob) { g_return_if_fail (frob != NULL); g_return_if_fail (GNOME_IS_FROB (frob)); frob->priv->frobbed = TRUE; }
Fíjese en el uso de prototipos de estructura: esto permite que
el compilador realice más verificaciones en tiempo de
compilación respecto a los tipos que estan siendo empleados.
Este esquema es útil en algunas situaciones, particularmente en el caso en el cual vale la pena almacenar un puntero extra para una instancia del objeto. Si esto no fuera posible, el programador necesitaría recurrir a otros artilugios para evitar este problema.
El propósito de tener un archivo de encabezado privado extra para las estructuras privadas es permitir que las clases derivadas usen esta información. Por supuesto, una buena práctica de programación indicaría que debiera proveerse de interfaces o métodos de acceso precisamente para que el programador no tenga que lidiar con estructuras privadas. Se debiera intentar alcanzar un balance entre buena práctica y pragmatismo cuando se crean estructuras privadas y métodos de acceso públicos.
Podrían aparecer algunos problemas, por ejemplo, cuando se ha enviado un versión del código y que está siendo ampliamente usado y se desea extenderlo. Hay dos soluciones para esto.
La primera opción es encontrar un campo puntero en la
estructura pública que pueda hacerse privada y
reemplazar el puntero a la parte privada de la
estructura. Esto preserva el tamaño de la estructura
ya que, para los propósitos de GNOME, se puede
asumir que los punteros son todos del mismo tamaño.
Se puede hacer que este campo apunte a la parte privada
de la estructura que será ubicada por la función que
originalmente creaba la estructura pública. Es
importante que este campo no sea llamado
private
, se trata de
una palabra reservada de C++ y creará problemas cuando
el archivo de encabezado sea usado desde programas
fuentes C++ — mejor llámalo
priv
. Por supuesto, este
tipo de cambios sólo funcionará si el antiguo campo
puntero solamente era usado para propósitos internos;
si los usuarios de la biblioteca habían tenido acceso
a dicho campo para cualquier propósito, será necesario
buscar otro campo o buscar una solución diferente.
Si la estructura original fue derivada de
GtkObject y no hay campos punteros
que puedan ser reemplazados, puedes usar la facilidad
de datos de objetos de GTK+. Esto permite asociar punteros
a datos arbitrarios a objetos. Usa la función
gtk_object_set_data()
para adjuntar
valores o estructuras a un objeto y cuando requieras usarlos
la función gtk_object_get_data()
permitirá recuperar dichos valores. Esto puede ser empleado
para adjuntar una estructura privada a un objeto GTK+.
Esto no es tan eficiente como el enfoque anterior, pero podría
no importar para el dominio particular de la aplicación. Si
los datos serán accedidos frecuentemente, se pueden usar
quarks en vez de cadenas para agregar y obtenerlos a través
de las funciones
gtk_object_set_data_by_id()
y
gtk_object_get_data_by_id()
,
respectivamente.
<< Localización | Cómo modificar el código de otros >> |