lunes, 15 de junio de 2009

Variables automáticas y persistentes. Tipos valor y tipos referencia. La pila y el montón.

En este artículo, y los posteriores hasta completar una serie de varios, voy a hablar sobre las variables automáticas, y las variables persistentes. El manejo de la pila y el montón. Como se trabaja con estos dos almacenes de datos desde C++, como se hace desde la plataforma .Net, y como desde C++/CLI.

Los tipos de dato en C++/CLI

En C++/CLI tenemos acceso a los tipos de datos de C++ (int, long, char, etc.) y a los tipos de datos que proporciona la librería de clases base (BCL) de .Net (System.Int32, System.Long, System.Int16, System.String, etc.).

En C++ los datos pueden ser instancias de datos atómicos, o bien instancias de datos definidos por el usuario, estructuras (struct) o clases (class). Mientras que en .Net, todos los datos, son instancias de una estructura o de una clase. Dicho más claramente y con un ejemplo concreto. El tipo int de C++, es un tipo atómico. Mientras que el tipo System.Int32 de .Net (del cual instanciamos en C# con la palabra reservada int, o en Visual Basic mediante Integer), es una estructura. Con sus propiedades y métodos. De la misma forma, el tipo DateTime de .Net, también es una estructura, y el tipo String es una clase.

La pila y el montón de C++

En C++, a la hora de crear un dato variable, podemos hacerlo de dos formas, en cuanto a en que zona de memoria va a crearse, bien directamente en la pila, bien reservando memoria dinámicamente en el montón y obteniendo un puntero a dicha variable en la pila. Veamos más detenidamente ambos casos.

Supongamos que deseamos crear una variable de tipo entero en la pila. Procederíamos de la forma.

int nValor1;

El compilador reservara el espacio necesario para almacenar un valor numérico dentro del rango de los enteros. La memoria ocupada por la variable, será liberada cuando el ámbito en el cual la variable existe, finalice su ejecución.

Supongamos que deseamos crear la misma variable en el montón, de forma dinámica. En primer lugar, declararíamos una variable de tipo puntero al tipo de dato que nos interese, en este caso int. Posteriormente, mediante el operador new reservaremos memoria de forma dinámica en el montón, obteniendo el puntero a dicha zona de memoria. Como se ve en el siguiente código.

// Se reserva espacio en la pila para almacenar un puntero a entero
int *nValor2;
// Se crea una variable para almacenar un entero en el monton, y
// apuntamos a ella desde nValor2

nValor2 = new int;
// Guardamos el valor entero 20 en la memoria apuntada por nValor2
*nValor2 = 20;

La memoria ocupada por la variable nValor2, también será liberada cuando el ámbito de la misma finalice. Pero, recordemos, nValor2 es una variable puntero. Su contenido, es la dirección de memoria en la cual se encuentra alojada otra variable. Dicha variable, recordemos, alojada en el montón, es persistente, y su ciclo de vida durara desde su creación, hasta la finalización del programa. Por tanto, la liberación de la variable que se encuentra alojada en el montón, es responsabilidad directa, y única, del programador. Para lo cual deberá utilizar el operador delete. Veamos el siguiente código de ejemplo donde se nos ilustrara todo esto.

void FuncionEjemplo(int intParametro)
{
// ptrEntero es una varible local. Contendra la dirección de
// memoria de una variable de tipo entero. Cuando la ejecución
// de la funcion FuncionEjemplo finalice, la memoria
// ocupada por ptrEntero se liberara, pero no la ocupada
// por la variable a la que apunta.

int *ptrEntero;
// intResultado es una variable local. Su contenido sera
// un valor numerico entero. Cuando finalice la ejecución
// de la función FuncionEjemplo, la memoria ocupada por
// la variable intResultado sera liberada de forma automatica.

int intResultado;

// Aqui se crea una variable de tipo entero en el monton
// y se obtiene la dirección de memoria donde se crea, //guardandose
// en ptrEntero.
ptrEntero = new int;

// Vamos a sumar en contenido de la variable intParametro, con
// el contenido de la variable alojada en el monton a la cual
// apunta ptrEntero. Su resultado se alamacenara en
// intResultado.
intResultado = intParametro + *ptrEntero;

// Retornamos el contenido de intResultado y se finaliza la
// ejecución de la
// función FuncionEjemplo. Las variables intParametro, ptrEntero
// e intResultado
// seran eliminadas y la memoria que ocupan liberada de forma
// automatica.
// Pero no sera asi para la memoria apuntada por la variable
// ptrEntero, que se encuentra
// en el monton. Si el programador no se encarga de liberar
// dicha memoria, existira
// indefinidamente hasta la finalización del programa

return intResultado;
}

En el ambito de la anterior función, existen tres variables locales alojadas en la pila (a las variables que se alojan en la pila tambien se les conoce como variables automaticas. Debido a que su finalización se produce de forma automatica, al finalizar su ambito). Estas son, el parametro intParametro, y las variables locales ptrEntero e intResultado.

Dentro de la función se utiliza el operador new para construir en el monton una variable de tipo entero y guardar la dirección de memoria donde esta ha sido creada en la variable ptrEntero.

Cuando finaliza la ejecución de la función, todas la variables locales, y la variable encargada de recoger el parametros, son eliminadas y su memoria liberada. Pero la variable que se creo en el monton, mediante el operador new, no es liberada. Las variables almacenadas en el monton, son persistentes, y su liberación solo se produce cuando el programador utiliza el operador delete. Este es un error muy frecuente en las aplicaciones desarrolladas en C++. El programador “olvida” liberar la memoria ocupada por las variables persistentes. Como consecuencia de ello, tendremos una porción de la memoria ocupada innecesariamente. Y, ademas, dicha memoria es inaccesible, ya que, al finalizar la ejecución de la función, la variable puntero que contenia la referencia a la misma, fue eliminada. Por tanto, toda referencia a dicha variable ha sido eliminada.

Es muy frecuente en la programación en C++ crear las intancias de clases, objetos, en el monton. Ya que es mucho mas efectivo manejar referencias a los objetos (punteros) que a los objetos en si mismo. sobre todo en los pasos de parametro a funciones.

Tipos valor y tipos referencia en .Net

Cuando Sun Microsystem desarrollo el lenguaje Java, tubo en cuenta el problema de la creación y liberación de objetos en el monton. Como hemos visto en el apartado anterior, en el desarrollo de aplicaciones en C++, es muy frecuente crear las instancias de clase en el monton, y tambien es muy frecuente olvidarse de liberar la memoria ocupada por las variables alojadas en el monton.

Java es un lenguaje 100% orientado a objetos. Y sus diseñadores pensaron que las variables, todas, deberian ser instancias de clases, es decir, todos los tipos de datos deberian ser clases (al contrario que ocurre en C++, donde los tipos atomicos no son clases). Toda instancia de una clase (objeto/variable) debia crearse en el monton, y obtener una referencia al mismo en la pila. Pero querian liberar de la tarea de reservar y liberar memoria al programador. De forma que no se produjesen los mencionados problemas de consumo innecesario de memoria.

Para ello, la maquina virtual de Java, dispone de un proceso interno denominado recolector de basura que examina que objetos se encuentran en el monton, y cuales de ellos han dejado de ser referenciados desde la pila. Librando la memoria ocupada por estos objetos. De esta forma el lenguaje Java libera al programador de la tarea de liberar de forma manual a los datos persistentes.

Microsoft, al desarrollar la plataforma .Net, dio una vuelta de tuerca a esta idea. Como hemos dicho, es habitual en C++ crear las intancias de clase en el monton. Pero no lo es la creación de variables de tipos atomicos. Mientras que en Java todas las variables se crean en el monton, Microsoft quiso que el desarrollo de una aplicación .Net se aproximara mas a como actuaria un programador C++. De forma que, los datos de “poco peso” se creasen en la pila, y los datos mas “pesados” se creasen en el monton.

Por ello, en la plataforma .Net existen datos tipo valor y tipo referencia. Los datos tipo valor son aquellos que cuando los creamos se alojan en la pila (denominada pila administrada) y los tipo referencia se crean en el monton (llamado monton administrado). Concretamente, toda variable instanciada de una estructura, es un tipo valor (se creara en la pila) y toda variabla instanciada de una clase, es de tipo referencia (se creara en el monton). Poniendo un ejemplo concreto, el tipo System.Int32 es una estructura.
Así que cuando en C# creamos una variable de tipo int o en Visual Basic creamos una variable de tipo Integer, estamos instanciando un dato de la estructura System.Int32 de la BCL (librería de clases base), y por tanto la variable se alojara en la pila.

Por el contrario el tipo System.String es una clase. Asi que cuando creamos una variable de tipo string estamos alojandola en el monton.

Hasta aquí, he comentado como se maneja la pila y el montón desde C++. Cual es el proposito de alojar variables en uno u otro almacen de datos. Que errores son los mas comunes en este tipo de tareas, y como la plataforma .Net pretende solucionar este tipo de errores mas comunes.

En el proximo articulo veremos como trabajamos con estos almacenes de datos con C++/CLI y que diferencias y similitudes tenemos con respecto al C++ tradicional y con el resto de lenguajes de la plataforma .Net.

miércoles, 3 de junio de 2009

Primeros pasos en C++/CLI

En este articulo voy a hacer una introducción a algunos conceptos básicos de C++/CLI. A fin de que queden algunas cuestiones básicas claras de cara a otros artículos más avanzados que tengo en mente.

C++/CLI es una extensión del lenguaje C++ desarrollada por Microsoft e implementada por primera vez en el compilador Visual C++ incluida con Visual Studio 2008. Es decir, el lenguaje esta soportado a partir de la versión 2.0 del .Net Framework. Debemos entender que C++/CLI no es C++, sino una extensión del lenguaje. Es decir, se trata de C++ más características añadidas. C++, como lenguaje, sigue su propia evolución. De hecho, se espera próximamente una publicación del nuevo estandard del lenguaje.

¿Y que son estas extensiones que Microsoft ha añadido a su compilador de C++, y para que sirven?. Nada más fácil de responder. C++/CLI permite compilar código C++ a código manejado que se ejecuta sobre la plataforma .Net. Es decir, mediante el compilador de Visual C++ podremos desarrollar tanto aplicaciones nativas, aquellas que se ejecutan directamente sobre el S.O., como aplicaciones destinadas a ejecutarse sobre el .Net Framework. Y, lo mas interesante, en una aplicación C++/CLI, podremos mezclar tanto código nativo, como código manejado. Curiosamente, y sin mucho esfuerzo por nuestra parte, ambos mundos van a "entenderse" de una manera pacifica.

¿Que nos permite esto?. El objetivo de Microsoft es brindarnos una herramienta que nos permita enlazar código manejado, con código nativo. La librería de .Net, abarca muchas posibilidades, pero no todas (aunque, claro esta, en cada versión cada vez son menos las cosas que quedan fuera). En ocasiones, nuestra aplicación deberá realizar tareas cuya funcionalidad no esta implementada en la BCL (librería de clases .Net). Pero si que el API de Windows nos proporciona funciones para realizarlas. Una posible solución al problema seria acceder a las funciones del API mediante llamadas pInvoke. El problema de este acercamiento es que, nuestro código, tendría partes nativas, y su ejecución escaparía al control del runtime de .Net. La recomendación, desde Microsoft, es que las llamadas a código nativo queden "envueltas" en clases creadas en C++/CLI, y utilicemos dichas clases en nuestras aplicaciones. Dicho de otra manera, mas sencilla, C++/CLI, nos permite parsear llamadas al API de Windows.

Echa esta introducción, algo extensa, vamos a entrar en faena viendo algún ejemplo.

Vamos a comenzar creando una "tradicional" aplicación nativa. Para simplificar al máximo, vamos a crear una aplicación de consola (es decir, sin interfaz grafica de usuario). Para ello, abrimos Visual Studio (yo utilizo Visual Studio 2005, Visual Studio 2008 es perfectamente valido, pero no Visual Studio 2003). Desplegamos el menú Archivo->Nuevo->Proyecto. En el cuadro de dialogo, en Tipo de proyecto desplegamos la rama de Visual C++, seleccionamos el nodo Win32 y en el recuadro Plantillas seleccionamos Aplicación de consola Win32. Damos el nombre a nuestra solución, indicamos el directorio donde guardarla y pulsamos aceptar. En el asistente que aparece a continuación no tenemos porque tocar nada, las opciones por defecto nos son validas.

Bien, tenemos ya creado nuestro esqueleto de proyecto. Básicamente tenemos tres archivos .cpp, stdafx.cpp y stdafx.h. En mi caso, he llamado al proyecto ejemplo1. Asi que el primer archivo fuente se llama ejemplo1.cpp, a partir de ahora me deferiré a el, por comodidad, con este nombre. Abrimos, el contenido de este fichero, y borramos todo el código que el asistente nos ha generado. En su lugar creamos el código siguiente.

#include "stdafx.h"
#include
using namespace std
int main()
{
cout << "Hola mundo, desde código nativo" <<>
return 0;
}

Desplegamos el menú Generar->Generar Seguidamente abrimos una ventana de comandos y nos posicionamos en el directorio donde hayamos creado nuestra solución, dentro de el, veremos un directorio llamado Debug o Release dependiendo de en que modo hayamos compilado el programa. Entramos en el y veremos el ejecutable correspondiente a nuestra aplicación. Ejecutándolo, veremos el mensaje Hola mundo, desde código nativo, y volveremos al inductor de comandos.

Hasta aquí, lo que hemos echo no es mas que una aplicación (muy sencilla) C++ tradicional, que genera código ensamblador puro para la maquina con la que estemos trabajando. Ahora, vamos a ver de manera sencilla, como podemos generar código IL (Código intermedio o Código manejado) que se ejecuta sobre la maquina virtual del .Net Framework.

Volvamos a nuestro proyecto. Desplegamos el menú Proyecto->Propiedades de . En el cuadro de dialogo, a la izquierda, desplegamos la rama Propiedades de configuración y allí seleccionamos General. En el recuadro de la derecha, veremos una opción llamada Compatibilidad con el Common Language Runtime. Pinchando a la derecha de esta opción, vemos que se nos despliega una lista, con varias opciones. En este momento teniamos seleccionada la opción No es compatible con Common Language Runtime. Lo cual, hacia que nuestro compilador generase codigo ensamblador puro, codigo nativo. El resto de opciones, le diran al compilador que debe generar código IL, código manejado, compatible con el .Net Framework. Seleccionamos la segunda opción Compatible con Common Language Runtime (/clr). Esta opción permitirá que en nuestro programa se puedan realizar llamadas tanto a código nativo, como a código manejado. Aceptamos el cuadro de dialogo, y, ahora, si compilamos nuestra aplicación, estaremos generando código que se ejecutaría sobre el .Net Framework. Aunque realmente, no estamos haciendo uso de ningún elemento .Net. Vamos a cambiar esto.

En nuestro programa, para mostrar la salida de texto por consola, hemos usado la clásica clase C++ cout. En el mundo .Net, para mostrar texto por consola, utilizamos el método WriteLine de la clase Console. Bien, vamos a modificar nuestro código, para que nos muestre otro texto, pero a través de las clases de .Net. Nuestro código, quedaría como sigue.

#include "stdafx.h"
#include
using namespace std
int main()
{
cout << "Hola mundo, desde código nativo" << endl;
System::Console::WriteLine("Hola mundo, desde codigo manejado");
return 0;
}

Compilamos nuestro programa y lo ejecutamos. Veremos que el programa nos muestra, sin problemas, dos líneas de texto en pantalla. La primera línea la esta mostrando mediante llamadas a código nativo, mientras que la segunda, esta utilizando código manejado.

Vamos a ir un paso más allá. Vamos a crear un par de variables a fin de alojar valores dentro del conjunto de los enteros. En el mundo nativo, disponemos del tipo int. Mientras que en el mundo .Net, se nos proporciona el tipo System.Int32. Aunque normalmente al crear una variable de este tipo utilizamos un alias, int en C# o Integuer en Visual Basic. Vamos a crear una variable de cada tipo, dejando nuestro código como sigue.

#include "stdafx.h"
#include
using namespace std
int main()
{
int intValor1;
System::Int32 intValor2;
cout << "Hola mundo, desde código nativo" << endl;
System::Console::WriteLine("Hola mundo, desde codigo manejado");
return 0;
}

Tenemos que tener algo claro. Nuestro programa esta creando dos variables. Ambas variables son capaces de contener dos valores numéricos dentro del rango de los enteros de 32 bits. Pero, ¿donde se alojan dichas variables?. Esa es la clave. intValor1 es una variable "nativa". Con lo cual se creara en la pila (stack). Mientras que intValor2 es una variable "administrada". Es decir, una variable controlada por el .Net Framework. Con lo cual dicha variable se creara en la pila administrada, una memoria gestionada por el .Net Framework.

A todas luces, son variables cuya memoria ha sido reservada en "memorias" distintas. Imaginemos que queremos sumar el contenido de dichas variables, en principio, parecería imposible. Bien, pues no hay ningún problema, C++/CLI, se encargara de realizar, por nosotros, proxies entre una y otra zona de memoria de forma que podamos operar entre variables nativas y administradas.

Vamos a modificar nuestro programa, de forma que nos quede como sigue.

#include "stdafx.h"
#include
using namespace std;


int main()
{
System::Int32 intValor1;
int intValor2;
cout << "Hola Mundo, desde codigo nativo" << endl;
System::Console::WriteLine("Hola mundo, desde codigo manejado");
cout << "\n\rIntroduce un numero: ";
cin >> intValor1;
System::Console::Write("Introduce un segundo numero: ");
intValor2 = System::Int32::Parse(System::Console::ReadLine());
cout << intValor1 + intValor2 << endl;
System::Console::WriteLine(intValor1 + intValor2);
return 0;
}

Este rebuscadísimo programa. Solicita dos números, los almacena en sendas variables, las suma y muestra el resultado por pantalla. Seria el programa más simple del mundo, salvo el interés de que estamos haciendo esto con dos variables muy diferentes. Una variable nativa y una manejada. Además, para solicitar datos al usuarios, estamos usando tanto código nativo (cuando empleamos la clase cin) como código manejado (cuando llamamos al metodo ReadLine de la clase Console). Fijémonos, además, que cuando pedimos un valor para almacenarlo en intValor1, estamos utilizando código nativo, y la variable es manejada. Lo contrario hacemos con la variable nativa intValor2, que estamos solicitando un valor por pantalla utilizando código manejado. Por ultimo, mostramos el resultado en pantalla tanto usando código manejado como nativo, y sumamos el contenido de ambas variables sin problemas. Insisto en que todo este trabajo lo realiza el compilador de C++/CLI "por debajo", de forma transparente para nosotros.

Bueno, hasta aquí esta introducción a C++/CLI. Podéis dejar cualquier comentario referente a cualquier error que haya podido cometer, o para consultar cualquier duda

martes, 2 de junio de 2009

Libros - C# 3.0 y LINQ

Quisiera inaugurar mi blog haciendo una recomendación de un libro. Se trata de "C# 3.0 y LINQ". Escrito por Octavio Hernández.

El libro tiene ya algún tiempo. Pero quisiera recomendarle debido a que considero que el trabajo de profesionales como Octavio, bien merecen la pena la mención y el reconocimiento. Octavio, se de buena tinta, estuvo trabajando con las primeras betas de Linq, publicando artículos en la prensa especializada, como la revista
dotNetManía, y en su blog personal. Arriesgándose a escribir y a poner en el mercado un libro sobre el tema, el que nos ocupa, permitiendo a la comunidad de desarrolladores de habla hispana, tener acceso a información de primera mano sobre una tecnología novedosa e importante.

Podéis informaros más detalladamente sobre el libro
aquí