Tema 1:
Introducción a Microsoft.NET
(C) 2001 José Antonio González Seco
Microsoft.NET
Microsoft.NET es el conjunto de nuevas tecnologías en las
que Microsoft ha estado trabajando durante los últimos
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)
El concepto de Microsoft.NET también incluye al conjunto de
nuevas aplicaciones que Microsoft y terceros han (o están)
desarrollando para ser utilizadas en la plataforma .NET. Entre
ellas podemos destacar aplicaciones desarrolladas por Microsoft
tales como Windows.NET, Hailstorm, Visual Studio.NET, MSN.NET,
Office.NET, y los nuevos servidores para empresas de
Microsoft (SQL Server.NET, Exchange.NET, etc.)
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:
|
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.