Introducción a Microsoft.NET
Microsoft.NET
Microsoft.NET es el
conjunto de tecnologías en las que Microsoft ha estado trabajando
durante algunos años con el objetivo de obtener una plataforma sencilla y
potente para distribuir el software en forma de servicios que puedan ser
suministrados remotamente y que puedan comunicarse y combinarse unos con otros
de manera totalmente independiente de la plataforma, lenguaje de programación y
modelo de componentes con los que hayan sido desarrollados. Ésta es la llamada plataforma .NET, y a los servicios
antes comentados se les denomina servicios
Web.
Para crear
aplicaciones para la plataforma .NET, tanto servicios Web como aplicaciones
tradicionales (aplicaciones de consola, aplicaciones de ventanas, servicios de
Windows NT, etc.), Microsoft ha publicado el denominado kit de desarrollo de
software conocido como .NET Framework
SDK, que incluye las herramientas necesarias tanto para su desarrollo como
para su distribución y ejecución y Visual
Studio.NET, que permite hacer todo la anterior desde una interfaz visual
basada en ventanas. Ambas herramientas puede descargarse gratuitamente desde http://www.msdn.microsoft.com/net,
aunque la última sólo está disponible para subscriptores MSDN Universal (los no
subscriptores pueden pedirlo desde dicha dirección y se les enviará gratis por
correo ordinario)
Common
Language Runtime (CLR)
El Common Language Runtime (CLR) es el núcleo de la plataforma
.NET. Es el motor encargado de gestionar la ejecución de las aplicaciones para
ella desarrolladas y a las que ofrece numerosos servicios que
simplifican su desarrollo y favorecen su fiabilidad y seguridad. Las
principales características y servicios que ofrece el CLR son:
- Modelo
de programación consistente: A todos los
servicios y facilidades ofrecidos por el CLR se accede de la misma forma:
a través de un modelo de programación orientado a objetos. Esto es una
diferencia importante respecto al modo de acceso a los servicios ofrecidos
por los algunos sistemas operativos actuales (por ejemplo, los de la
familia Windows), en los que a algunos servicios se les accede a través de
llamadas a funciones globales definidas en DLLs y a otros a través de
objetos (objetos COM en el caso de la familia Windows)
- Modelo
de programación sencillo: Con el CLR desaparecen
muchos elementos complejos incluidos en los sistemas operativos actuales
(registro de Windows, GUIDs, HRESULTS, IUnknown, etc.) El CLR no es que
abstraiga al programador de estos conceptos, sino que son conceptos que no
existen en la plataforma .NET
- Eliminación
del “infierno de las DLLs”: En la plataforma .NET
desaparece el problema conocido como “infierno de las DLLs” que se da en
los sistemas operativos actuales de la familia Windows, problema que
consiste en que al sustituirse versiones viejas de DLLs compartidas por
versiones nuevas puede que aplicaciones que fueron diseñadas para ser
ejecutadas usando las viejas dejen de funcionar si las nuevas no son 100%
compatibles con las anteriores. En la plataforma .NET las versiones nuevas
de las DLLs pueden coexistir con las viejas, de modo que las aplicaciones
diseñadas para ejecutarse usando las viejas podrán seguir usándolas tras
instalación de las nuevas. Esto, obviamente, simplifica mucho la
instalación y desinstalación de software.
- Ejecución
multiplataforma: El CLR actúa como una máquina
virtual, encargándose de ejecutar las aplicaciones diseñadas para la
plataforma .NET. Es decir, cualquier plataforma para la que exista una
versión del CLR podrá ejecutar cualquier aplicación .NET. Microsoft ha
desarrollado versiones del CLR para la mayoría de las versiones de
Windows: Windows 95, Windows 98, Windows ME, Windows NT 4.0, Windows 2000,
Windows XP y Windows CE (que puede ser usado en CPUs que no sean de la
familia x86) Por otro lado Microsoft ha firmado un acuerdo con Corel para
portar el CLR a Linux y también hay terceros que están desarrollando de
manera independiente versiones de libre distribución del CLR para Linux.
Asímismo, dado que la arquitectura del CLR está totalmente abierta, es
posible que en el futuro se diseñen versiones del mismo para otros
sistemas operativos.
- Integración
de lenguajes: Desde cualquier lenguaje para el
que exista un compilador que genere código para la plataforma .NET es
posible utilizar código generado para la misma usando cualquier otro
lenguaje tal y como si de código escrito usando el primero se tratase.
Microsoft ha desarrollado un compilador de C# que genera código de este
tipo, así como versiones de sus compiladores de Visual Basic (Visual
Basic.NET) y C++ (C++ con extensiones gestionadas) que también lo generan
y una versión del intérprete de JScript (JScript.NET) que puede
interpretarlo. La integración de lenguajes esta que es posible escribir
una clase en C# que herede de otra escrita en Visual Basic.NET que, a su
vez, herede de otra escrita en C++ con extensiones gestionadas.
- Gestión
de memoria: El CLR incluye un recolector de basura que evita que
el programador tenga que tener en cuenta cuándo ha de destruir los objetos
que dejen de serle útiles. Este recolector es una aplicación que se activa
cuando se quiere crear algún objeto nuevo y se detecta que no queda
memoria libre para hacerlo, caso en que el recolector recorre la memoria
dinámica asociada a la aplicación, detecta qué objetos hay en ella que no
puedan ser accedidos por el código de la aplicación, y los elimina para
limpiar la memoria de “objetos basura” y permitir la creación de otros
nuevos. Gracias a este recolector se evitan errores de programación muy
comunes como intentos de borrado de objetos ya borrados, agotamiento de
memoria por olvido de eliminación de objetos inútiles o solicitud de
acceso a miembros de objetos ya destruidos.
- Seguridad
de tipos: El CLR facilita la detección de errores
de programación difíciles de localizar comprobando que toda conversión de tipos
que se realice durante la ejecución de una aplicación .NET se haga de modo
que los tipos origen y destino sean compatibles.
- Aislamiento
de procesos: El CLR asegura que desde código
perteneciente a un determinado proceso no se pueda acceder a código o datos pertenecientes
a otro, lo que evita errores de programación muy frecuentes e impide que
unos procesos puedan atacar a otros. Esto se consigue gracias al sistema
de seguridad de tipos antes comentado, pues evita que se pueda convertir un objeto a un tipo de
mayor tamaño que el suyo propio, ya que al tratarlo como un objeto de
mayor tamaño podría accederse a espacios en memoria ajenos a él que
podrían pertenecer a otro proceso. También se consigue gracias a que no se
permite acceder a posiciones arbitrarias de memoria.
- Tratamiento
de excepciones: En el CLR todo los errores que se
puedan producir durante la ejecución de una aplicación se propagan de
igual manera: mediante excepciones. Esto es muy diferente a como se venía
haciendo en los sistemas Windows hasta la aparición de la plataforma .NET,
donde ciertos errores se transmitían mediante códigos de error en formato
Win32, otros mediante HRESULTs y otros mediante excepciones.
El CLR permite
que excepciones lanzadas desde código para .NET escrito en un cierto lenguaje
se puedan capturar en código escrito usando otro lenguaje, e incluye mecanismos
de depuración que pueden saltar desde código escrito para .NET en un
determinado lenguaje a código escrito en cualquier otro. Por ejemplo, se puede
recorrer la pila de llamadas de una excepción aunque ésta incluya métodos
definidos en otros módulos usando otros lenguajes.
·
Soporte multihilo: El CLR es capaz de trabajar
con aplicaciones divididas en múltiples hilos de ejecución que pueden ir
evolucionando por separado en paralelo o intercalándose, según el número de
procesadores de la máquina sobre la que se ejecuten. Las aplicaciones pueden
lanzar nuevos hilos, destruirlos, suspenderlos por un tiempo o hasta que les
llegue una notificación, enviarles notificaciones, sincronizarlos, etc.
·
Distribución transparente: El CLR ofrece la
infraestructura necesaria para crear objetos remotos y acceder a ellos de
manera completamente transparente a su localización real, tal y como si se
encontrasen en la máquina que los utiliza.
- Seguridad
avanzada: El CLR proporciona mecanismos para
restringir la ejecución de ciertos códigos o los permisos asignados a los
mismos según su procedendecia o el usuario que los ejecute. Es decir,
puede no darse el mismo nivel de confianza a código procedente de Internet
que a código instalado localmente o procedente de una red local; puede no
darse los mismos permisos a código procedente de un determinado fabricante
que a código de otro; y puede no darse los mismos permisos a un mismo
códigos según el usuario que lo esté
ejecutando o según el rol que éste desempeñe. Esto permite
asegurar al administrador de un sistema que el código que se esté
ejecutando no pueda poner en peligro la integridad de sus archivos, la del
registro de Windows, etc.
- Interoperabilidad
con código antiguo: El CLR incorpora los
mecanismos necesarios para poder acceder desde código escrito para la
plataforma .NET a código escrito previamente a la aparición de la misma y,
por tanto, no preparado para ser ejecutando dentro de ella. Estos
mecanismos permiten tanto el acceso a objetos COM como el acceso a
funciones sueltas de DLLs preexistentes (como la API Win32)
Como se puede
deducir de las características comentadas, el CLR lo que hace es gestionar la
ejecución de las aplicaciones diseñadas para la plataforma .NET. Por esta
razón, al código de estas aplicaciones se le suele llamar código gestionado, y al
código no escrito para ser ejecutado directamente en la plataforma .NET se le
suele llamar código no gestionado.
Microsoft Intermediate Language (MSIL)
Todos los
compiladores que generan código para la plataforma .NET no generan código
máquina para CPUs x86 ni para ningún otro tipo de CPU concreta, sino que
generan código escrito en el lenguaje intermedio conocido como Microsoft
Intermediate Lenguage (MSIL) El CLR da a las aplicaciones las sensación de que
se están ejecutando sobre una máquina virtual, y precisamente MSIL es el código
máquina de esa máquina virtual. Es decir, MSIL es el único código que es capaz
de interpretar el CLR, y por tanto cuando se dice que un compilador genera
código para la plataforma .NET lo que se está diciendo es que genera MSIL.
MSIL ha sido creado
por Microsoft tras consultar a numerosos especialistas en la escritura de
compiladores y lenguajes tanto del mundo académico como empresarial. Es un
lenguaje de un nivel de abstracción mucho más alto que el de la mayoría de los
códigos máquina de las CPUs existentes, e incluye instrucciones que permiten
trabajar directamente con objetos (crearlos, destruirlos, inicializarlos,
llamar a métodos virtuales, etc.), tablas y excepciones (lanzarlas, capturarlas
y tratarlas)
Ya se comentó que
el compilador de C# compila directamente el código fuente a MSIL, que Microsoft
ha desarrollado nuevas versiones de sus lenguajes Visual Basic (Visual
Basic.NET) y C++ (C++ con extensiones gestionadas) cuyos compiladores generan
MSIL, y que ha desarrollado un intérprete de JScript (JScript.NET) que genera
código MSIL. Pues bien, también hay numerosos terceros que han anunciado estar
realizando versiones para la plataforma .NET de otros lenguajes como APL, CAML,
Cobol, Eiffel, Fortran, Haskell, Java (J#), Mercury, ML, Mondrian, Oberon, Oz,
Pascal, Perl, Python, RPG, Scheme y Smalltalk.
La principal
ventaja del MSIL es que facilita la ejecución multiplataforma y la integración
entre lenguajes al ser independiente de la CPU y proporcionar un formato común
para el código máquina generado por todos los compiladores que generen código
para .NET. Sin embargo, dado que las CPUs no pueden ejecutar directamente MSIL,
antes de ejecutarlo habrá que convertirlo al código nativo de la CPU sobre la
que se vaya a ejecutar. De esto se encarga un componente del CLR conocido como
compilador JIT (Just-In-Time) o jitter que va convirtiendo dinámicamente el
código MSIL a ejecutar en código nativo según sea necesario. Este jitter se
distribuye en tres versiones:
- jitter
normal: Es el que se suele usar por defecto, y
sólo compila el código MSIL a código nativo a medida que va siendo
necesario, pues así se ahorra tiempo y memoria al evitarse tener que
compilar innecesariamente código que nunca se ejecute. Para conseguir
esto, el cargador de clases del CLR sustituye inicialmente las llamadas a
métodos de las nuevas clases que vaya cargando por llamadas a funciones
auxiliares (stubs) que se encarguen de compilar el verdadero código del
método. Una vez compilado, la llamada al stub es sustituida por una
llamada directa al código ya compilado, con lo que posteriores llamadas al
mismo no necesitarán compilación.
- jitter
económico: Funciona de forma similar al jitter
normal solo que no realiza ninguna optimización de código al compilar sino
que traduce cada instrucción MSIL por su equivalente en el código máquina
sobre la que se ejecute. Esta especialmente pensado para ser usado en
dispositivos empotrados que dispongan de poca potencia de CPU y poca
memoria, pues aunque genere código más ineficiente es menor el tiempo y
memoria que necesita para compilar. Es más, para ahorrar memoria este
jitter puede descargar código ya compilado que lleve cierto tiempo sin
ejecutarse y sustituirlo de nuevo por el stub apropiado. Por estas
razones, este es el jitter usado por defecto en Windows CE, sistema operativo que se suele incluir en
los dispositivos empotrados antes mencionados.
Otra utilidad del jitter económico es que facilita la adaptación de la
plataforma .NET a nuevos sistemas porque es mucho más sencillo de implementar
que el normal. De este modo, gracias a él es posible desarrollar rápidamente
una versión del CLR que pueda ejecutar aplicaciones gestionadas aunque sea de
una forma poco eficiente, y una vez desarrollada es posible centrarse en
desarrollar el jitter normal para optimizar la ejecución de las mismas.
- prejitter:
Se distribuye como una aplicación en línea de
comandos llamada ngen.exe mediante la que es posible compilar completamente cualquier
ejecutable o librería (cualquier ensamblado en general, aunque este
concepto se verá más adelante) que contenga código gestionado y
convertirlo a código nativo, de modo que posteriores ejecuciones del mismo
se harán usando esta versión ya compilada y no se perderá tiempo en hacer
la compilación dinámica.
La actuación de un
jitter durante la ejecución de una aplicación gestionada puede dar la sensación
de hacer que ésta se ejecute más lentamente debido a que ha de invertirse
tiempo en las compilaciones dinámicas. Esto es cierto, pero hay que tener en
cuenta que es una solución mucho más eficiente que la usada en otras
plataformas como Java, ya que en .NET cada código no es interpretado cada vez
que se ejecuta sino que sólo es compilado la primera vez que se llama al método
al que pertenece. Es más, el hecho de que la compilación se realice
dinámicamente permite que el jitter tenga acceso a mucha más información sobre
la máquina en que se ejecutará la aplicación del que tendría cualquier
compilador tradicional, con lo que puede optimizar el código para ella generado
(por ejemplo, usando las instrucciones especiales del Pentium III si la máquina
las admite, usando registros extra, incluyendo código inline, etc.) Además, como el recolector de basura de .NET mantiene
siempre compactada la memoria dinámica las reservas de memoria se harán más
rápido, sobre todo en aplicaciones que no agoten la memoria y, por tanto, no
necesiten de una recolección de basura. Por estas razones, los ingenieros de
Microsoft piensan que futuras versiones de sus jitters podrán incluso conseguir
que el código gestionado se ejecute más rápido que el no gestionado.
Metadatos
En la plataforma
.NET se distinguen dos tipos de módulos
de código compilado: ejecutables
(extensión .exe) y librerías de enlace dinámico (extensión
.dll generalmente) Ambos son
ficheros que contienen definiciones de tipos de datos, y la diferencia entre
ellos es que sólo los primeros disponen de un método especial que sirve de
punto de entrada a partir del que es posible ejecutar el código que contienen
haciendo una llamada desde la línea de comandos del sistema operativo. A ambos
tipos de módulos se les suele llamar ejecutables
portables (PE), ya que su código puede ejecutarse en cualquiera de los
diferentes sistemas operativos de la familia Windows para los que existe alguna
versión del CLR.
El contenido de un
módulo no sólo MSIL, sino que también consta de otras dos áreas muy importantes:
la cabecera de CLR y los metadatos:
·
La cabecera de CLR es un pequeño bloque de información que indica que
se trata de un módulo gestionado e indica es la versión del CLR que necesita,
cuál es su firma digital, cuál es su punto de entrada (si es un ejecutable),
etc.
·
Los metadatos son un conjunto de datos organizados en forma de tablas
que almacenan información sobre los tipos definidos en el módulo, los miembros
de éstos y sobre cuáles son los tipos externos al módulo a los que se les
referencia en el módulo. Los metadatos de cada modulo los genera
automáticamente el compilador al crearlo, y entre sus tablas se incluyen[1]:
[1] No se preocupe si no entiende aún algunos de los conceptos nuevos
introducido en las descripciones de las tablas de metadatos, pues más adelante
se irán explicando detalladamente.
Tabla
|
Descripción
|
ModuleDef
|
Define las
características del módulo. Consta de un único elemento que almacena un
identificador de versión de módulo (GUID creado por el compilador) y el
nombre de fichero que se dio al módulo al compilarlo (así este nombre siempre
estará disponible, aunque se renombre el fichero)
|
TypeDef
|
Define las
características de los tipos definidos en el módulo. De cada tipo se almacena
su nombre, su tipo padre, sus modificadores de acceso y referencias a los
elementos de las tablas de miembros correspondientes a sus miembros.
|
MethodDef
|
Define las
características de los métodos definidos en el módulo. De cada método se
guarda su nombre, signatura (por cada parámetro se incluye una referencia al
elemento apropiado en la tabla ParamDef), modificadores y posición del módulo
donde comienza el código MSIL de su cuerpo.
|
ParamDef
|
Define las
características de los parámetros definidos en el módulo. De cada parámetro
se guarda su nombre y modificadores.
|
FieldDef
|
Define las
características de los campos definidos en el módulo. De cada uno se almacena
información sobre cuál es su nombre, tipo y modificadores.
|
PropertyDef
|
Define las
características de las propiedades definidas en el módulo. De cada una se
indica su nombre, tipo, modificadores y referencias a los elementos de la
tabla MethodDef correspondientes a sus métodos set/get.
|
EventDef
|
Define las
características de los eventos definidos en el módulo. De cada uno se indica
su nombre, tipo, modificadores. y referencias a los elementos de la tabla
MethodDef correspondientes a sus métodos add/remove.
|
AssemblyRef
|
Indica cuáles son
los ensamblados externos a los que se referencia en el módulo. De cada uno se
indica cuál es su nombre de fichero (sin extensión), versión, idioma y marca
de clave pública.
|
ModuleRef
|
Indica cuáles son
los otros módulos del mismo ensamblado a los que referencia el módulo. De
cada uno se indica cuál es su nombre de fichero.
|
TypeRef
|
Indica cuáles son
los tipos externos a los que se referencia en el módulo. De cada uno se indica cuál es su nombre y, según donde
estén definidos, una referencia a la posición adecuada en la tabla
AssemblyRef o en la tabla ModuleRef.
|
MemberRef
|
Indican cuáles
son los miembros definidos externamente a los que se referencia en el módulo.
Estos miembros pueden ser campos, métodos, propiedades o eventos; y de cada
uno de ellos se almacena información sobre su nombre y signatura, así como
una referencia a la posición de la tabla TypeRef donde se almacena
información relativa al tipo del que es miembro.
|
Tabla 1: Principales tablas de metadatos
Nótese que el significado de los metadatos
es similar al de otras tecnologías previas a la plataforma .NET como lo son los
ficheros IDL. Sin embargo, los metadatos tienen dos ventajas importantes sobre
éstas: contiene más información y siempre se almacenan incrustados en el módulo
al que describen, haciendo imposible la separación entre ambos. Además, como se
verá más adelante, es posible tanto consultar los metadatos de cualquier módulo
a través de las clases del espacio de nombres System.Reflection de la BCL como añadirles información adicional
mediante atributos (se verá más adelante)
Ensamblados
Un ensamblado es una agrupación lógica de
uno o más módulos o ficheros de recursos (ficheros .GIF, .HTML, etc.) que se
engloban bajo un nombre común. Un programa puede acceder a información o código
almacenados en un ensamblado sin tener porqué sabe cuál es el fichero en
concreto donde se encuentran, por lo que los ensamblados nos permiten
abstraernos de la ubicación física del código que ejecutemos o de los recursos
que usemos. Por ejemplo, podemos incluir todos los tipos de una aplicación en
un mismo ensamblado pero colocando los más frecuentemente usados en un cierto
módulo y los menos usados en otro, de modo que sólo se descarguen de Internet
los últimos si es que se van a usar.
Todo ensamblado
contiene un manifiesto, que son
metadatos con información sobre las características del ensamblado. Este
manifiesto puede almacenarse cualquiera de los módulos que formen el ensamblado
o en uno específicamente creado para ello, caso éste último necesario cuando es
un ensamblado satélite (sólo
contiene recursos)
Las principales
tablas incluidas en los manifiestos son las siguientes:
Tabla
|
Descripción
|
AssemblyDef
|
Define las
características del ensamblado. Consta de un único elemento que almacena el
nombre del ensamblado sin extensión, versión, idioma, clave pública y tipo de
algoritmo de dispersión usado para hallar los valores de dispersión de la
tabla FileDef.
|
FileDef
|
Define cuáles son
los archivos que forman el ensamblado. De cada uno se da su nombre y valor de
dispersión. Nótese que sólo el módulo que contiene el manifiesto sabrá qué
ficheros que forman el ensamblado, pero el resto de ficheros del mismo no
sabrán si pertenecen o no a un ensamblado (no contienen metadatos que les
indique si pertenecen a un ensamblado)
|
ManifestResourceDef
|
Define las
características de los recursos incluidos en el módulo. De cada uno se indica
su nombre y modificadores de acceso. Si es un recurso incrustado se indica
dónde empieza dentro del PE que lo contiene, y si es un fichero independiente
se indica cuál es el elemento de la tabla FileDef correspondiente a dicho
fichero.
|
ExportedTypesDef
|
Indica cuáles son
los tipos definidos en el ensamblado y accesibles desde fuera del mismo. Para
ahorrar espacio sólo recogen los que no pertenezcan al módulo donde se
incluye el manifiesto, y de cada uno se indica su nombre, la posición en la
tabla FileDef del fichero donde se ha implementado y la posición en la tabla
TypeDef correspondiente a su definición.
|
AssemblyProccesorDef
|
Indica en qué procesadores
se puede ejecutar el ensamblado, lo que puede ser útil saberlo si el
ensamblado contiene módulos con código nativo (podría hacerse usando C++ con extensiones gestionadas)
Suele estar vacía, lo que indica que se puede ejecutar en cualquier procesador;
pero si estuviese llena, cada elemento indicaría un tipo de procesador
admitido según el formato de identificadores de procesador del fichero
WinNT.h incluido con Visual Studio.NET (por ejemplo, 586 = Pentium, 2200 = Arquitectura IA64,
etc.)
|
AssemblyOSDef
|
Indica bajo qué
sistemas operativos se puede ejecutar el ensamblado, lo que puede ser útil si
contiene módulos con tipos o métodos disponibles sólo en ciertos sistemas.
Suele estar vacía, lo que indica que se puede ejecutar en cualquier
procesador; pero si estuviese llena, indicaría el identificador de cada uno
de los sistemas admitidos siguiendo el formato del WinNT.h de Visual
Studio.NET (por ejemplo, 0 = familia Windows 9X, 1 = familia Windows NT,
etc.) y el número de la versión del mismo a partir de la que se admite.
|
Tabla
2: Principales tablas
de un manifiesto
Para asegurar que no se haya alterado la
información de ningún ensamblado se usa el criptosistema de clave pública RSA.
Lo que se hace es calcular el código de dispersión SHA-1 del módulo que
contenga el manifiesto e incluir tanto este valor cifrado con RSA (firma digital) como la clave pública
necesaria para descifrarlo en algún lugar del módulo que se indicará en la
cabecera de CLR. Cada vez que se vaya a cargar en memoria el ensamblado se
calculará su valor de dispersión de nuevo y se comprobará que es igual al
resultado de descifrar el original usando su clave pública. Si no fuese así se
detectaría que se ha adulterado su contenido.
Para asegurar
también que los contenidos del resto de ficheros que formen un ensamblado no
hayan sido alterados lo que se hace es calcular el código de dispersión de
éstos antes de cifrar el ensamblado y guardarlo en el elemento correspondiente
a cada fichero en la tabla FileDef del manifiesto. El algoritmo de cifrado
usado por defecto es SHA-1, aunque en este caso también se da la posibilidad de
usar MD5. En ambos casos, cada vez que se accede al fichero para acceder a un tipo o recurso se calculará de nuevo su
valor de dispersión y se comprobará que coincida con el almacenado en FileDef.
Dado que las claves
públicas son valores que ocupan muchos bytes (2048 bits), lo que se hace para
evitar que los metadatos sean excesivamente grandes es no incluir en las
referencias a ensamblados externos de la tabla AssemblyRef las claves públicas
de dichos ensamblados, sino sólo los 64 últimos bits resultantes de aplicar un
algoritmo de dispersión a dichas claves. A este valor recortado se le llama marca de clave pública.
Hay dos tipos de
ensamblados: ensamblados privados y ensamblados compartidos. Los privados
se almacenan en el mismo directorio que la aplicación que los usa y sólo puede
usarlos ésta, mientras que los compartidos se almacenan en un caché de ensamblado global (GAC) y
pueden usarlos cualquiera que haya sido compilada referenciándolos.
Los compartidos han de cifrase con RSA ya que lo que los
identifica es en el GAC es su nombre (sin extensión) más su clave pública, lo
que permite que en el GAC puedan instalarse varios ensamblados con el mismo
nombre y diferentes claves públicas. Es decir, es como si la clave pública formase parte del nombre del ensamblado,
razón por la que a los ensamblados así cifrados se les llama ensamblados de nombre fuerte. Esta
política permite resolver los conflictos derivados de que se intente instalar
en un mismo equipo varios ensamblados compartidos con el mismo nombre pero
procedentes de distintas empresas, pues éstas tendrán distintas claves
públicas.
También para evitar
problemas, en el GAC se pueden mantener múltiples versiones de un mismo
ensamblado. Así, si una aplicación fue compilada usando una cierta versión de
un determinado ensamblado compartido, cuando se ejecute sólo podrá hacer uso de
esa versión del ensamblado y no de alguna otra más moderna que se hubiese
instalado en el GAC. De esta forma se soluciona el problema del infierno de las DLL comentado al
principio del tema.
En realidad es
posible modificar tanto las políticas de búsqueda de ensamblados (por ejemplo,
para buscar ensamblados privados fuera
del directorio de la aplicación) como la
política de aceptación de ensamblados compartidos (por ejemplo, para que se
haga automáticamente uso de las nuevas versiones que se instalen de DLLs
compartidas) incluyendo en el directorio de instalación de la aplicación un
fichero de configuración en formato XML con las nuevas reglas para las mismas.
Este fichero ha de llamarse igual que el ejecutable de la aplicación pero ha de
tener extensión .cfg.
Librería
de clase base (BCL)
La Librería de Clase
Base (BCL) es una librería incluida en el .NET
Framework formada por cientos de tipos de datos que permiten acceder a los
servicios ofrecidos por el CLR y a las funcionalidades más frecuentemente
usadas a la hora de escribir programas. Además, a partir de estas clases
prefabricadas el programador puede crear nuevas clases que mediante herencia
extiendan su funcionalidad y se integren a la perfección con el resto de clases
de la BCL. Por ejemplo, implementando ciertos interfaces podemos crear nuevos
tipos de colecciones que serán tratadas exactamente igual que cualquiera de las
colecciones incluidas en la BCL.
Esta librería está
escrita en MSIL, por lo que puede usarse desde cualquier lenguaje cuyo compilador genere MSIL. A
través de las clases suministradas en ella es posible desarrollar cualquier
tipo de aplicación, desde las tradicionales aplicaciones de ventanas, consola o
servicio de Windows NT hasta los novedosos servicios Web y páginas ASP.NET. Es
tal la riqueza de servicios que ofrece que puede crearse lenguajes que carezcan
de librería de clases propia y sólo usen la BCL -como C#.
Dado la amplitud de
la BCL, ha sido necesario organizar las clases en ella incluida en espacios de nombres que agrupen clases
con funcionalidades similares. Por ejemplo, los espacios de nombres más usados
son:
Espacio de nombres
|
Utilidad de los tipos de datos que contiene
|
System
|
Tipos muy
frecuentemente usados, como los los tipos básicos, tablas, excepciones,
fechas, números aleatorios, recolector de basura, entrada/salida en consola, etc.
|
System.Collections
|
Colecciones de
datos de uso común como pilas, colas, listas, diccionarios, etc.
|
System.Data
|
Manipulación de
bases de datos. Forman la denominada arquitectura ADO.NET.
|
System.IO
|
Manipulación de
ficheros y otros flujos de datos.
|
System.Net
|
Realización de
comunicaciones en red.
|
System.Reflection
|
Acceso a los
metadatos que acompañan a los módulos de código.
|
System.Runtime.Remoting
|
Acceso a objetos
remotos.
|
System.Security
|
Acceso a la
política de seguridad en que se basa el CLR.
|
System.Threading
|
Manipulación de
hilos.
|
System.Web.UI.WebControls
|
Creación de
interfaces de usuario basadas en ventanas para aplicaciones Web.
|
System.Winforms
|
Creación de
interfaces de usuario basadas en ventanas para aplicaciones estándar.
|
System.XML
|
Acceso a datos en
formato XML.
|
Tabla 3: Espacios de nombres de la BCL más usados
Common Type System (CTS)
El Common Type System (CTS) o Sistema de
Tipo Común es el conjunto de reglas que han de seguir las definiciones de tipos
de datos para que el CLR las acepte. Es decir, aunque cada lenguaje gestionado
disponga de sus propia sintaxis para definir tipos de datos, en el MSIL
resultante de la compilación de sus códigos fuente se ha de cumplir las reglas
del CTS. Algunos ejemplos de estas reglas son:
- Cada tipo de dato puede constar de cero
o más miembros. Cada uno de estos miembros puede ser un campo, un método
una propiedad o un evento.
- No puede haber herencia múltiple, y
todo tipo de dato ha de heredar directa o indirectamente de System.Object.
- Los modificadores de acceso admitidos
son:
Modificador
|
Código desde el que es accesible el miembro
|
public
|
Cualquier código
|
private
|
Código del mismo
tipo de dato
|
family
|
Código del mismo
tipo de dato o de hijos de éste.
|
assembly
|
Código del mismo
ensamblado
|
family and assembly
|
Código del mismo
tipo o de hijos de éste ubicado en el mismo ensamblado
|
family or assembly
|
Código del mismo
tipo o de hijos de éste, o código ubicado en el mismo ensamblado
|
Tabla 4: Modificadores de acceso a miembros admitidos
por el CTS
Common Language Specification (CLS)
El Common Language Specification (CLS) o
Especificación del Lenguaje Común es un conjunto de reglas que han de seguir
las definiciones de tipos que se hagan usando un determinado lenguaje
gestionado si se desea que sean accesibles desde cualquier otro lenguaje
gestionado. Obviamente, sólo es necesario seguir estas reglas en las
definiciones de tipos y miembros que sean accesibles externamente, y no la en las de los privados. Además, si no
importa la interoperabilidad entre lenguajes tampoco es necesario seguirlas. A
continuación se listan algunas de reglas significativas del CLS:
- Los tipos de datos básicos admitidos
son bool,
char, byte, short, int, long, float, double, string y
object Nótese pues que no todos los lenguajes tienen porqué admitir los
tipos básicos enteros sin signo o el tipo decimal
como lo hace C#.
- Las tablas han de tener una o más
dimensiones, y el número de dimensiones de cada tabla ha de ser fijo.
Además, han de indexarse empezando a contar desde 0.
- Se pueden definir tipos abstractos y
tipos sellados. Los tipos sellados no pueden tener miembros abstractos.
- Las excepciones han de derivar de System.Exception, los
delegados de System.Delegate, las enumeraciones de System.Enum, y los tipos por valor que no sean enumeraciones de System.ValueType.
- Los métodos de acceso a propiedades en
que se traduzcan las definiciones get/set de éstas han de llamarse de la
forma get_X y set_X respectivamente, donde X
es el nombre de la propiedad; los de acceso a indizadores han de
traducirse en métodos get_Item y setItem;
y en el caso de los eventos, sus definiciones add/remove han de traducirse
en métodos de add_X y remove_X.
- En las definiciones de atributos sólo
pueden usarse enumeraciones o datos de los siguientes tipos: System.Type, string, char, bool, byte, short, int, long, float, double y object.
- En un mismo ámbito no se pueden definir
varios identificadores cuyos nombres sólo difieran en la capitalización
usada. De este modo se evitan problemas al acceder a ellos usando
lenguajes no sensibles a mayúsculas.
- Las enumeraciones no pueden implementar
interfaces, y todos sus campos han
de ser estáticos y del mismo tipo. El tipo de los campos de una enumeración
sólo puede ser uno de estos cuatro tipos básicos: byte, short, int o long.
No hay comentarios.