Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en...

38
Marco Besteiro y Miguel Rodríguez Código Inseguro 1/38 Código inseguro (Unsafe Code) e interoperabilidad. Código inseguro Referencias y punteros. En C#, para acceder a memoria se utilizan referencias. Las referencias son similares a los punteros, con la salvedad de que no permiten manipular el valor de la dirección a la que apuntan (valor de la dirección, no contenido). Es decir, si una referencia apunta a una variable que está en la dirección 0x004FFFA8, a través de la referencia es posible utilizar la variable pero no se puede obtener el valor 0x004FFFA8 (que es la dirección de la variable) y tampoco se le pueden sumar valores a la dirección para apuntar a otra dirección (por ejemplo: sumarle 4 a 0x004FFFA8 para acceder a la variable contenida a partir de la posición 0x004FFFAB). La gran ventaja que ofrecen las referencias es que evitan muchos errores derivados del mal uso de los punteros, pero también tiene desventajas: - Al no permitir acceder directamente a la memoria, limitan el rendimiento que podría dar a una aplicación hacerlo. - Ciertas aplicaciones como, por ejemplo, un depurador o debugger, necesitan manejar direcciones de memoria. - Las dll antiguas (el API de Windows, por ejemplo) tienen funciones que utilizan punteros como parámetros, de modo que no podrían ser invocadas si no se permite la utilización de punteros. De lo comentado se deduce que, en ocasiones, puede ser interesante utilizar punteros. Un primer acercamiento a los punteros son los delegates, que pertenecen al llamado “safe code” o “código seguro” pero únicamente se comportan como punteros a funciones. En C# se permite utilizar punteros y al código en el que se utilizan punteros se le denomina “código inseguro” o “unsafe code”. El código inseguro debe ser marcado utilizando la palabra reservada “unsafe”. Es posible marcar como “unsafe”: - Una clase - Un miembro de una clase - Un bloque de código En C# no se permite declarar punteros a tipos referencia. Tampoco es posible marcar como unsafe una variable local (ha de marcarse el método o bloque de código en el que está). Además de lo comentado, al compilar código inseguro es necesario indicárselo al compilador utilizando el flag unsafe (independientemente de si se compila desde el Visual Studio .NET o desde la línea de comandos).

Transcript of Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en...

Page 1: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

1/38

Código inseguro (Unsafe Code) e interoperabilidad.

Código inseguro

Referencias y punteros. En C#, para acceder a memoria se utilizan referencias. Las referencias son similares a los punteros, con la salvedad de que no permiten manipular el valor de la dirección a la que apuntan (valor de la dirección, no contenido). Es decir, si una referencia apunta a una variable que está en la dirección 0x004FFFA8, a través de la referencia es posible utilizar la variable pero no se puede obtener el valor 0x004FFFA8 (que es la dirección de la variable) y tampoco se le pueden sumar valores a la dirección para apuntar a otra dirección (por ejemplo: sumarle 4 a 0x004FFFA8 para acceder a la variable contenida a partir de la posición 0x004FFFAB). La gran ventaja que ofrecen las referencias es que evitan muchos errores derivados del mal uso de los punteros, pero también tiene desventajas:

- Al no permitir acceder directamente a la memoria, limitan el rendimiento que podría dar a una aplicación hacerlo.

- Ciertas aplicaciones como, por ejemplo, un depurador o debugger, necesitan manejar direcciones de memoria.

- Las dll antiguas (el API de Windows, por ejemplo) tienen funciones que utilizan punteros como parámetros, de modo que no podrían ser invocadas si no se permite la utilización de punteros.

De lo comentado se deduce que, en ocasiones, puede ser interesante utilizar punteros. Un primer acercamiento a los punteros son los delegates, que pertenecen al llamado “safe code” o “código seguro” pero únicamente se comportan como punteros a funciones. En C# se permite utilizar punteros y al código en el que se utilizan punteros se le denomina “código inseguro” o “unsafe code”. El código inseguro debe ser marcado utilizando la palabra reservada “unsafe”. Es posible marcar como “unsafe”:

- Una clase - Un miembro de una clase - Un bloque de código

En C# no se permite declarar punteros a tipos referencia. Tampoco es posible marcar como unsafe una variable local (ha de marcarse el método o bloque de código en el que está). Además de lo comentado, al compilar código inseguro es necesario indicárselo al compilador utilizando el flag unsafe (independientemente de si se compila desde el Visual Studio .NET o desde la línea de comandos).

Page 2: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

2/38

• Desde la línea de comandos es tan sencillo como indicar /unsafe al compilar: csc /unsafe ... • Desde Visual Studio .NET se ha de mostrar la ventana de propiedades:

Figura 13.1

Page 3: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

3/38

Figura 13.2 En caso de que no esté seleccionado, se ha de seleccionar el proyecto en desarrollo desde la explorador de soluciones:

Page 4: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

4/38

Figura 13.3 Al hacerlo, en la ventana propiedades se mostrará la información sobre el proyecto y se activarán los botones e iconos del cuadro de la ventana. Pulse sobre el cuarto icono de la ventana de propiedades y aparecerá el cuadro de diálogo con las página de propiedades del proyecto. En propiedades de configuración se ha de elegir Generar y Permitir bloques de código no seguros a True.

Page 5: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

5/38

Figura 13.4 De este modo será posible compilar código inseguro. En caso contrario el compilador no lo permitirá (genera error en tiempo de compilación). Por lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias variables de tipo puntero en una misma línea se separan por comas y van precedidas todas ellas por *. Por ejemplo: int *p1, *p2; En C# sólo la primera variable de tipo puntero ha de ir precedida por *. Por ejemplo: int *p1, p2; Ejemplo: int num = 5; //la variable num ocupa 4 bytes de memoria

//(stack o pila), por ejemplo, a partir //de la posición 0x004FFFA8 hasta la //0x004FFFAB. El contenido de esas posiciones //es el valor 5.

int num2 = 10; //la variable num2 ocupa 4 bytes //de memoria (stack o pila), por //ejemplo, a partir de la posición 0x004FFFA4 hasta //la 0x004FFFA7. El contenido de esas //posiciones es el valor 10.

int *pNum; // pNum es una variable de tipo puntero a entero y //ocupa 4 bytes de memoria (stack o pila), por //ejemplo, a partir de la 0x004FFFA0 hasta la //0x004FFFA3. El contenido de esas posiciones no está //definido (basura).

Page 6: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

6/38

*pNum = &num2; //al asignar la dirección de la variable num2 al //contenido de la variable pNum, las posiciones //correspondientes a la variable de tipo puntero pNum //(0x004FFFA0 a 0x004FFFA3) pasan a //contener la dirección de la variable num2, es //decir, 0x004FFFA4.

Aritmética de punteros. Es posible añadir o restar enteros a un puntero utilizando los operadores +, -, ++, --, += y -=. Por ejemplo, supóngase que se añade la instrucción pNum++ al ejemplo anterior: int num = 5; int num2 = 10; int *pNum; *pNum = &num2; pNum++; Al incrementar en una unidad pNum lo que sucede es que su contenido pasa a ser el actual aumentado en cuatro unidades, ya que el tamaño de una variable de tipo entero es 4 bytes y el compilador entiende que se quiere que pNum apunte a la siguiente variable de tipo entero y no al siguiente byte. De este modo, el contenido de pNum será 0x004FFFA4 + 4, es decir 0x004FFFA8, que es la dirección de la variable num. Es importante notar que si el código hubiese sido: int num = 5; int num2 = 10; int *pNum; *pNum = # pNum++; Al incrementar pNum pasaría a valer 0x004FFFA8 + 4, que es 0x004FFFAC. Aquí se presenta el problema inherente al manejo directo de punteros. ¿Qué hay en 0x004FFFAC? Lo más posible es que no haya un entero sino algo desconocido, lo cual implicará un malfuncionamiento del programa cuyos efectos no son fácilmente predecibles. El siguiente ejemplo muestra cómo desarrollar código inseguro y utilizar punteros y variables, indicando información sobre éstos. using System; class CodigoInseguroPunteros { static unsafe void Main (string[] args) { int int_num = 15; int *p_int_num = &int_num;

Console.WriteLine("El valor de int_num es:" + *p_int_num); Console.WriteLine("La dirección de int_num es: " +

(uint) p_int_num); Console.WriteLine("El tamaño de int_num es:"+sizeof (int)); Console.WriteLine("El valor de p_int_num es: " +

Page 7: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

7/38

(uint) p_int_num); Console.WriteLine("La dirección de p_int_num es: " +

(uint) &p_int_num); Console.WriteLine("El tamaño de p_int_num es: " +

sizeof (int*)); } } El resultado es:

Figura 13.5 Tanto int_num como p_int_num son almacenados en la pila o stack ya que son datos de tipo primitivo (valor). Un int (entero) tiene un tamaño de 4 bytes. Un puntero tiene un tamaño de 4 bytes (independientemente del tipo de datos al que apunte). El contenido de p_int_num es la dirección de int_num. Se puede observar que la dirección de p_int_num es 1243464, es decir, anterior en 4 bytes (que es lo que ocupa el puntero) a int_num. Puesto que la pila crece hacia abajo y las variables se le añaden en el orden en que han sido declaradas, lo lógico es que p_int_num ocupe las posiciones más bajas.

Page 8: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

8/38

Figura 13.6 El siguiente ejemplo es similar al anterior pero abarca más tipos de datos de tipo valor (todos primitivos). using System; class ManejoPunteros { static unsafe void Main (string[] args) { bool bool_val = true; char char_val = 'a'; sbyte sbyte_num = 5; short short_num = 10; int int_num = 15; long long_num = 20; double double_num = 25; float float_num = 30; bool *p_bool_val = &bool_val; char *p_char_val = &char_val; sbyte *p_sbyte_num = &sbyte_num; short *p_short_num = &short_num; int *p_int_num = &int_num; long *p_long_num = &long_num; double *p_double_num = &double_num; float *p_float_num = &float_num;

Console.WriteLine("El valor de bool_val es: " + *p_bool_val); //podría ponerse bool_val

Console.WriteLine("La dirección de bool_val es: " + (uint) p_bool_val); //podría ponerse &bool_val

Console.WriteLine("El tamaño de bool_val es: " + sizeof (bool));

Console.WriteLine("El valor de char_val es: " + *p_char_val);

Page 9: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

9/38

Console.WriteLine("La dirección de char_val es: " + (uint) p_char_val);

Console.WriteLine("El tamaño de char_val es: " + sizeof (char));

Console.WriteLine("El valor de sbyte_num es: " + *p_sbyte_num);

Console.WriteLine("La dirección de sbyte_num es: " + (uint) p_sbyte_num);

Console.WriteLine("El tamaño de sbyte_num es: " + sizeof (sbyte));

Console.WriteLine("El valor de short_num es: " + *p_short_num);

Console.WriteLine("La dirección de short_num es: " + (uint) p_short_num); Console.WriteLine("El tamaño de short_num es: " +

sizeof (short)); Console.WriteLine("El valor de int_num es: " +

*p_int_num); Console.WriteLine("La dirección de int_num es: " +

(uint) p_int_num); Console.WriteLine("El tamaño de int_num es: " +

sizeof (int)); Console.WriteLine("El valor de long_num es: " +

*p_long_num); Console.WriteLine("La dirección de long_num es: " +

(uint) p_long_num); Console.WriteLine("El tamaño de long_num es: " +

sizeof (long)); Console.WriteLine("El valor de double_num es: " +

*p_double_num); Console.WriteLine("La dirección de double_num es: " +

(uint) p_double_num); Console.WriteLine("El tamaño de double_num es: " +

sizeof (double)); Console.WriteLine("El valor de float_num es: " +

*p_float_num); Console.WriteLine("La dirección de float_num es: " +

(uint) p_float_num); Console.WriteLine("El tamaño de float_num es: " +

sizeof (float)); } } El resultado será:

Page 10: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

10/38

Figura 13.7 Aquí puede observarse también la colocación de las variables en la pila (en este caso no se muestra la información sobre los punteros).

Casting de punteros. Es posible convertir punteros a un tipo de datos a punteros a otro tipo diferente de datos. Por ejemplo: int num = 5; int *pNum; byte *pOcteto; pOcteto = # pNum = # System.Console.WriteLine(“Contenido apuntado por pNum {0}”, *pNum); System.Console.WriteLine(“Contenido apuntado por pOcteto {0}”,

*pOcteto); Tanto pOcteto como pNum apuntan a num, que es una variable que ocupa 4 bytes en memoria (stack), ya que es una variable de tipo int. Cuando se acceda a num mediante pNum, se permitirá manejar los 4 bytes de num como lo que es (un entero), en cambio, cuando se acceda a num a través de pOcteto, sólo podrá manipularse el primer byte u octeto de los 4 que ocupa num, porque a través de un puntero a byte sólo puede manejarse un byte, independientemente del tipo de datos real al que se refiera la dirección contenida en el puntero a byte. Es importante deducir de esto que dependiendo del casting que se haga es posible que no tenga utilidad alguna. Un casting lógico puede ser convertir cualquier tipo de puntero en un puntero a sbyte, para poder recorrer byte a byte el contenido de una variable. También es posible convertir un puntero a otro tipo de datos no puntero, como puede ser int, uint, long... De todos estos casting los más lógicos son a int o uint, ya que un dato de tipo int o uint ocupa 4 bytes y uno de tipo long 8, etc...

Page 11: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

11/38

Además, si se desea mostrar por consola el valor de un puntero, por ejemplo pNum, no puede hacerse: //error en compilación System.Console.WriteLine(“Contenido de pNum {0}”, pNum); Ha de hacerse lo siguiente: System.Console.WriteLine(“Contenido de pNum {0}”, (uint) pNum); Nota: en C#, al igual que en C y C++ puede utilizarse el operador sizeof para obtener el tamaño de un tipo de datos.

Punteros void. No es una buena idea utilizar punteros a void en C# pero en ocasiones puede ser necesario. El caso más común es cuando se desea invocar una función del API que necesita recibir parámetros de tipo void*. Se puede declarar punteros a void y hacer casting entre punteros de otros tipos y void: void * pVoid; int * pInt; int num = 5; *pInt = &num pVoid = (void*) pInt; pero no es posible desreferenciar punteros a void utilizando el operador *, es decir *pVoid causará un error de compilación en cualquier expresión en que aparezca (en las declaraciones no, por supuesto).

Punteros a estructuras y a miembros de estructuras. C# permite declarar punteros a estructuras con la condición de que la estructura para la que se declare el puntero no posea miembros de tipo referencia. Si se salta esta norma, el compilador da un error. Salvada esta restricción, los punteros a estructuras funcionan de modo similar a los punteros a los tipos valor predefinidos (byte, int, uint, long…). Por ejemplo: struct Punto {

public int X; public int Y;

} Punto * pPunto; Punto p1 = new Punto(); pPunto = &p1; (*pPunto).X = 5; //también es válido pPunto->X = 5; (*pPunto).Y = 10; //también es válido pPunto->Y = 10; System.Console.WriteLine (“X: “ + (*pPunto).X );

Page 12: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

12/38

System.Console.WriteLine (“Y: “ + (*pPunto).Y ); Por supuesto, también se pueden crear punteros a los miembros de una estructura. Por ejemplo: Punto * pPunto; int * pX, pY; Punto p1 = new Punto(); pX = &(p1.X); //también es válido pX = &(p1->X); pY = &(p1.Y); //también es válido pY = &(p1->Y); *pX = 5; *pY = 10; System.Console.WriteLine (“X: “ + pX ); System.Console.WriteLine (“Y: “ + pY );

Punteros a clases y a miembros de clases. No es posible crear punteros a clases (una clase es un tipo referencia) ya que se almacenan, como toda variable de tipo referencia, en el heap y el garbage collector elimina las variables del heap cuando dejan de estar referenciadas por referencias, aunque queden punteros apuntándolas. En cambio, es posible que un puntero apunte a los miembros de tipo valor de una clase (sólo a estos). No obstante, se plantea un problema: los miembros de una variable de una clase serán eliminados cuando el garbage collector elimine la variable del heap (ya que el garbage collector no tiene en cuenta los punteros). La solución es utilizar la palabra reservada fixed cuando se declara un puntero a un miembro de una clase. fixed le indica al compilador que el código que genere ha de asegurar que el garbage collector no eliminará el objeto al que pertenece el miembro fijado mientras se esté utilizando. Por ejemplo: class Punto {

public int X; public int Y;

} int * pX, pY; Punto p1 = new Punto(); fixed (pX = &(p1.X)) {

//manejar pX *pX = 5; System.Console.WriteLine (“X: “ + pX );

} ... fixed (pY = &(p1.Y)) {

//manejar pY *pY = 10; System.Console.WriteLine (“Y: “ + pY );

}

Page 13: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

13/38

Lo anterior podría haberse programado de modos alternativos. • Modo 1 (en grupo): ... ... int * pX, pY; Punto p1 = new Punto(); fixed (pX = &(p1.X)) fixed (pY = &(p1.Y)) {

//manejar pX *pX = 5; System.Console.WriteLine (“X: “ + pX );

//manejar pY *pY = 10; System.Console.WriteLine (“Y: “ + pY );

} ... ... • Modo 2 (anidado): ... ... int * pX, pY; Punto p1 = new Punto(); fixed (pX = &(p1.X)) {

//manejar pX *pX = 5; System.Console.WriteLine (“X: “ + pX );

fixed (pY = &(p1.Y)) {

//manejar pY *pY = 10; System.Console.WriteLine (“Y: “ + pY );

} } ... ...

Page 14: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

14/38

Interoperabilidad. Se denomina “managed code” al código que se puede ejecutar bajo el control del CLR y “unmanaged code” a aquél cuya ejecución no puede ser controlada por el CLR y por tanto no pueden aplicársele las reglas de control de seguridad del CLR ni la recolección de basura (garbage collector). El “managed code” necesita en muchas ocasiones operar con el unmanaged code (código nativo) existente. Incluso habrá casos en los que sea necesario desarrollar una parte de un proyecto en código nativo e invocar tal código nativo desde el código IL que es ejecutado, de modo controlado (managed code), por el CLR. Para permitir esto, el CLR soporta dos formas de operación con el código nativo:

- Platform Invocation Services (P/Invoke). - COM Interoperability.

Es importante no confundir unmanaged code con unsafe code. Este último es managed code precedido por la palabra clave unsafe al que se le permite utilizar características no seguras (unsafe) del lenguaje C++, como son los punteros.

Platform Invocation Services. Invoke o Platform Invocation Services es el nombre que se da a la tecnología que permite invocar “unmanaged code” desde el código .NET o “managed code”.

Utilización de dll nativas desde .NET.

Para poder invocar una función de una dll desde código .NET basta con utilizar el atributo DllImport o SysImport. El atributo DllImport se comentó en el tema referente a los atributos y el atributo SysImport es similar, sólo que se utiliza para invocar funciones de las dll del sistema (user32.dll, gdi32.dll y kernel32.dll). Al explicar el atributo DllImport se utilizó un ejemplo en el que se invocaba a la función MessageBox de la librería user32.dll: using System; using System.Reflection; using Clases_NameSpace; //Para utilizar DllImport es necesario el namespace //System.Runtime.InteropServices using System.Runtime.InteropServices; class PruebaAtributosReservados { [DllImport("User32.dll")] public static extern int MessageBox(int hParent, string Message,

string Caption, int Type); public static void Main() {

Page 15: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

15/38

Clase2.FunClase2 (); Clase3.FunClase3 (); System.Console.WriteLine(" ");

System.Console.WriteLine("Para llamar a MessageBox pulse Enter: ");

System.Console.ReadLine(); MessageBox(0, "Hola desde el API de Windows",

"Mensaje invocado desde .NET", 0); } } El mismo ejemplo, utilizando SysImport es: using System; using System.Reflection; using Clases_NameSpace; //Para utilizar DllImport es necesario el namespace //System.Runtime.InteropServices using System.Runtime.InteropServices; class PruebaAtributosReservados { [SysImport(dll="User32.dll")]

public static extern int MessageBox(int hParent, string Message, string Caption, int Type);

public static void Main() { ...

... El resultado de ejecutar este ejemplo es:

Figura 13.8 Tras pulsar Enter se mostrará el cuadro de mensaje:

Page 16: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

16/38

Figura 13.9

COM interoperability. El CLR ofrece soporte tanto para utilizar componentes COM desde C# como para utilizar objetos C# desde componentes COM y desde código nativo en general como si los objetos C# fuesen componentes COM. La interoperabilidad entre componentes COM y C# puede realizarse mediante enlace temprano (en compilación) o tardío (en ejecución).

Utilización de componentes COM desde .NET.

El uso de componentes COM desde C# es diferente dependiendo de si se utiliza enlace temprano o tardío.

Utilización de componentes COM desde .NET mediante enlace temprano.

Para poder utilizar un componente COM desde .NET de modo que queden todas las referencias resueltas en tiempo de compilación (enlace temprano o early binding) es necesario que exista un objeto que siga la tecnología .NET (para poder incluirlo en el código .NET) y que exponga las interfaces del componente COM. A este objeto se le llama Runtime Callable Wrapper (RCW) y actúa como proxy que utiliza .NET para acceder al componente COM. El RCW se encarga de traducir las llamadas que desde .NET se hagan a sus métodos en las llamadas correctas al componente COM. También se encarga de traducir la respuesta del componente COM de modo que pueda ser utilizado desde .NET y de controlar el ciclo de vida del componente COM (Addref, Release...). Para crear el RCW correspondiente a un componente COM se ha de utilizar TlbImp.exe. Por ejemplo: supónga que se dispone de un componente COM contenido en la dll quartz.dll (C:\Winnt\Sustem32\quartz.dll) y que se desea utilizar desde .NET, en concreto desde una aplicación C#. Los pasos a seguir son:

1) Crear el RCW correspondiente al componente contenido en quartz.dll y copiarlo en el directorio de la aplicación .NET que lo va a utilizar (no tiene porque ser así):

tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll

Page 17: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

17/38

QuartzTypeLib.dll es el RCW, que realmente es una clase .NET, la cual podrá utilizarse en la aplicación C# que llamará al componente contenido en quartz.dll.

2) Crear la aplicación que utiliza el componente contenido en quartz.dll

//Fichero InteropCOM_Enlace_Temprano_Consola.cs

using System; using QuartzTypeLib; namespace COMInterop_Enlace_Temprano_Consola { /// <summary> /// Summary description for Aplicacion_COMInterop_Consola. /// </summary> public class Aplicacion_COMInterop_Consola { public Aplicacion_COMInterop_Consola() { // // TODO: Add constructor logic here // } public static void Main() { QuartzTypeLib.FilgraphManager fgm = new FilgraphManager(); // La siguiente instrucción equivale a una llamada // a QueryInterface pidiendo el interface IMediaControl QuartzTypeLib.IMediaControl mc = (QuartzTypeLib.IMediaControl)fgm;

// El método RenderFile recibe como parámetro el fichero a //reproducir mc.RenderFile(@"C:\Archivos de programa\Microsoft.NET\FrameworkSDK\Samples\

technologies\remoting\advanced\ remotingcom\mediaplayer\client\clock.avi");

//Run reproduce el fichero. mc.Run(); Console.WriteLine("Pulse Enter para continuar."); Console.ReadLine(); } } }

3) Compilar la aplicación incluyendo la dll del RCW como referencia: csc /r:QuartzTypeLib.dll InteropCOM_Enlace_Temprano_Consola.cs

4) Invocar al ejecutable de la aplicación:

InteropCOM_Enlace_Temprano_Consola

Page 18: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

18/38

El resultado es:

Figura 13.10 Este mismo ejemplo, hecho desde el Visual Studio .NET es más sencillo gracias al cuadro de diálogo del menú principal Proyecto/Agregar Referencia:

Page 19: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

19/38

Figura 13.11 Tras aceptar la librería seleccionada quartz.dll Visual Studio muestra un mensaje indicando que no existe el RCW para el componente contenido en quartz.dll y preguntando si se desea que lo cree.

Figura 13.12 Suponiendo que se acepta, Visual Studio .NET crea una dll llamada QuartzTypeLib.dll (RCW) y la almacena en el directorio de la aplicación. A partir de este momento, la aplicación puede utilizar la clase o, mejor dicho, las clases del paquete QuartzTypeLib (puede haber más de una clase o componente en una dll). En el explorador de soluciones se ve que aparece una referencia a QuartzTypeLib.

Page 20: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

20/38

Figura 13.13 Una de las ventajas que da Visual Studio .NET es que muestra ayuda contextual al escribir el código que utiliza la referencia añadida (en este caso QuartzTypeLib).

Figura 13.14 Nota: En caso de que el componente esté en una dll no incluida por defecto en la pestaña “COM” del cuadro de diálogo Agregar referencia puede utilizarse el botón Examinar para buscar e incluir la dll que se desee.

Page 21: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

21/38

Utilización de componentes COM desde .NET mediante enlace tardío.

Cuando se utiliza enlace tardío para invocar los métodos de un componente COM desde una aplicación, ésta ha de obtener la dirección de tales métodos durante la ejecución. Es decir, al compilar la aplicación nada se sabe sobre la dirección de los componentes que va a utilizar la aplicación. En enlace tardío no es necesario crear el RCW para el componente COM. Se ha de utilizar el método GetTypefromProgID de la clase Type. Los pasos a seguir para utilizar un componente COM mediante enlace tardío son:

1) Incluir el namespace System.Interop.InteropSevices. 2) Crear un objeto Type para el componente COM utilizando

Type.GetTypeFromProgID() o Type.GetTypeFromCLSID(). 3) Crear un componente COM utilizando Activator.CreateInstance(). 4) Llamar a los métodos del componente COM utilizando el método

InvokeMember del objeto Type. El enlace tardío ya es conocido de un tema anterior y se puede observar que se sigue una idea similar al llamar a componentes COM mediante enlace tardío. Es importante saber que cuando se invoca a los métodos de un componente COM mediante enlace tardío, la invocación se hace utilizando la interface IDispatch. En el siguiente ejemplo va a intentarse invocar utilizando enlace tardío a los métodos RenderFile y Run del componente FilgraphManager, contenido en Quartz.dll. Por supuesto, no hay que añadir una referencia a Quartz.dll en el proyecto. using System; using System.Reflection; using System.Runtime.InteropServices; namespace COMInterop_Enlace_Tardio_Consola { /// <summary> /// Summary description for Aplicacion_COMInterop_Consola. /// </summary> public class Aplicacion_COMInterop_Consola_Enlace_Tardio { public Aplicacion_COMInterop_Consola_Enlace_Tardio() { // // TODO: Add constructor logic here // } public static void Main() { Type objTypeQuartz; object objQuartz;

object[] arObjs = {@"C:\Archivos de programa\ Microsoft.NET\FrameworkSDK\Samples\ technologies\remoting\advanced\ remotingcom\mediaplayer\client\clock.avi"};

Page 22: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

22/38

objTypeQuartz = Type.GetTypeFromProgID("Quartz.FilgraphManager");

objQuartz = Activator.CreateInstance(objTypeQuartz); objTypeQuartz.InvokeMember("RenderFile", BindingFlags.InvokeMethod, null, objQuartz, arObjs); objTypeQuartz.InvokeMember("Run", BindingFlags.InvokeMethod, null, objQuartz, null);

Console.WriteLine("Pulse Enter para continuar.");

Console.ReadLine(); } } } Si se compila esta aplicación, al ejecutarse lanza una excepción.

Figura 13.15 Si se pulsa No:

Page 23: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

23/38

Figura 13.16 La excepción se lanza al intentar crear un objeto del tipo FilgraphManager, es decir, al ejecutar la línea: objQuartz = Activator.CreateInstance(objTypeQuartz); ya que objTypeQuartz es null y no una referencia a un objeto. El problema real esta en la línea anterior:

objTypeQuartz= Type.GetTypeFromProgID("Quartz.FilgraphManager"); El problema es que no está registrado el ProgID de FilgraphManager. La verdad es que no está registrado ni siquiera FilgraphManager. Para comprobarlo no hay más que mostrar el registro del sistema y buscarlo.

Figura 13.17

Page 24: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

24/38

Figura 13.18 El resultado será que no se encuentra:

Page 25: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

25/38

Figura 13.19 Si se busca FilgraphManager, tampoco se encontrará. Esto puede hacer sospechar que no está registrado, pero la verdad es que sí lo está (puede ejecutarse regsvr32 C:\Winnt\System32\Quartz.dll para asegurarse). Si se busca Quartz:

Figura 13.20 Se encontraran varias coincidencias. La que interesa es:

Page 26: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

26/38

Figura 13.21 Es decir, Quartz.dll sí está registrada y se conoce su CLSID. Podría pensarse que tal vez valga este CLSID, aunque el que se quiere es el de FilgraphManager, que no se encuentra y por tanto cambiar el código de modo que se utilice el CLSID, en lugar del ProgID: using System; using System.Reflection; using System.Runtime.InteropServices; namespace COMInterop_Enlace_Tardio_Consola { /// <summary> /// Summary description for Aplicacion_COMInterop_Consola. /// </summary> public class Aplicacion_COMInterop_Consola_Enlace_Tardio { public Aplicacion_COMInterop_Consola_Enlace_Tardio() { // // TODO: Add constructor logic here // } public static void Main() { Type objTypeQuartz; object objQuartz;

Page 27: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

27/38

object[] arObjs = {@"C:\Archivos de programa\Microsoft.NET\FrameworkSDK\Samples\technologies\remoting\advanced\remotingcom\mediaplayer\client\clock.avi"}; System.Guid clasid = new System.Guid("70E102B0-5556-11CE-97C0-00AA0055595A");

objTypeQuartz = Type.GetTypeFromCLSID(clasid); objQuartz = Activator.CreateInstance(objTypeQuartz);

objTypeQuartz.InvokeMember("RenderFile", BindingFlags.InvokeMethod, null, objQuartz, arObjs); objTypeQuartz.InvokeMember("Run", BindingFlags.InvokeMethod, null, objQuartz, null);

… … Pero en tiempo de ejecución sigue lanzando una excepción (en modo no debug):

Figura 13.22 Podría utilizarse el CLSID pero:

- Tendría que ser el CLSID del componente deseado. - El componente deseado tendría que soportar la interface IDispatch.

Llegar aquí y no realizar un ejemplo que funcione deja la duda en el aire, de modo que habrá que realizar un ejemplo válido.Hay que buscar un componente registrado que tenga un progID (o un versionIndependentProgID) o un CLSID y que soporte la interface COM IDispatch. Powerpoint.Application puede ser el componente que cumpla tal ejemplo.

Page 28: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

28/38

Figura 13.23 El código de este ejemplo será: using System; using System.Reflection; using System.Runtime.InteropServices; namespace COMInterop_Enlace_Tardio_Consola_Funciona { /// <summary> /// Summary description for Aplicacion_COMInterop_Consola. /// </summary>

public class Aplicacion_COMInterop_Consola_Enlace_Tardio_Funciona

{ public Aplicacion_COMInterop_Consola_Enlace_Tardio_Funciona() { // // TODO: Add constructor logic here // } public static void Main() { Type objTypeApplicationPowerPoint; object objApplicationPowerPoint;

Console.WriteLine("Pulse Enter para lanzar PowerPoint.");

Console.ReadLine();

Page 29: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

29/38

objTypeApplicationPowerPoint = Type.GetTypeFromProgID("PowerPoint.Application"); objApplicationPowerPoint = Activator.CreateInstance(objTypeApplicationPowerPoint); objTypeApplicationPowerPoint.InvokeMember("Activate", BindingFlags.InvokeMethod, null, objApplicationPowerPoint, null);

Console.WriteLine("Pulse Enter para continuar."); Console.ReadLine(); } } } Y el resultado de ejecutarla:

Figura 13.24 Al pulsar Enter aparecerá Powerpoint.

Page 30: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

30/38

Figura 13.25

Utilización de componentes .NET desde componentes COM y dll nativas.

Lo más común es que al desarrollar código .NET se necesite, en ocasiones, utilizar componentes COM existentes o nuevos. No obstante, también puede darse el caso contrario, es decir, la necesidad de invocar componentes .NET desde código nativo Windows (componentes COM o funciones de dll). Para poder utilizar componentes .NET como si fuesen componentes COM es necesario que los componentes .NET estén registrados (en el registro del sistema o registry) y exista un proxy al que se invoque a través de los datos registrados, siendo el proxy el que realmente invocará los métodos del objeto o componente .NET. La aplicación RegAsm (Register Assembly) registra un assembly y todos sus clases (componentes), creando además el proxy. Se puede decir que es la inversa de TlbImp. Una vez registrado un assembly hay que realizar varios pasos para que sea realmente accesible como un componente COM:

1. Crear un nombre fuerte (strong name) para asignarlo al assembly. El nombre fuerte identificará al assembly (consta de un nombre de tipo texto, un número de versión, información sobre la cultura si se ha indicado, una clave pública y una firma digital). sn –k assemblyx.snk

Page 31: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

31/38

2. Crear un fichero AssemblyInfo.cs que contenga una referencia al fichero con

el nombre fuerte:

using System.Reflection; [assembly: AssemblyKeyFile(“assemblyx.snk”)] donde assemblyx.snk es el nombre fuerte del assembly.

3. Compilarlo, generando un módulo:

csc /t:module /out:AssemblyInfo.dll AssemblyInfo.cs

4. Compilar assemblyx.cs utilizando el módulo assemblyinfo.dll y generando una librería:

csc /t:library /addmodule:assemblyinfo.dll assemblyx.cs

5. Copiar la librería assemblyx.dll a la caché global (si no se hace, no será accesible desde el código nativo (COM y funciones de dll no COM):

gacutil /i assemblyx.dll

En el siguiente ejemplo se crea una clase muy sencilla llamada Doble (en el fichero N_Doble.cs), que contiene un método Doblar que multiplica por dos el valor que recibe como parámetro. namespace N_Doble { using system;

public class Doble { public int Doblar(int num) {

return (num * 2); }

} } Lo primero que se ha de hacer es compilar N_Doble.cs y generar N_Doble.dll.

Page 32: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

32/38

Figura 13.26 Una vez generada la librería, se utiliza regasm para registrarla en el registro del sistema.

Figura 13.27 En el registro del sistema ha de encontrarse una tentrada para el componente Doble.

Page 33: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

33/38

Figura 13.28 Ahora es necesario copiar N_Doble.dll a la caché global para que pueda ser accedida desde el código Windows nativo. El primer paso es generar un “strong name” o nombre fuerte (es una buena idea generarlo y depositarlo en el archivo N_Doble.snk).

Figura 13.29

Page 34: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

34/38

Una vez se tiene el fichero con el nombre fuerte, ha de ser referenciado desde un fichero que luego se añadirá al assembly N_Doble.dll. En este caso se llama AssemblyInfo.cs y contiene: using System.Reflection; [assembly: AssemblyKeyFile("N_Doble.snk")] Antes de añadir esta información a N_Doble.dll habrá que generar un módulo a partir de AssemblyInfo.cs.

Figura 13.30 Para añadir la información de tal módulo a N_Doble.dll lo que realmente se hará es regenerar N_Doble.dll a partir de N_Doble.cs indicando que se añada el módulo AssemblyInfo.dll.

Figura 13.31

Page 35: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

35/38

Una vez se ha obtenido N_Doble.dll con la información de AssemblyInfo.dll sólo resta copiarlo a la caché global.

Figura 13.32 A partir de este momento es posible utilizar desde código Windows nativo el componente Doble. Un posible ejemplo puede ser un script (VBScript) contenido, por ejemplo, en una página Web: //Fichero PrueballamarNETdesdeCOM.html <html> <head> <title> Prueba de llamada a .NET desde Windows nativo </title> </head> <body> <script language=VBScript> Option Explicit Dim objeto_N_Doble Dim Numero Dim Resultado Set objeto_N_Doble = CreateObject("N_Doble.Doble") Numero = InputBox("Número a doblar") Resultado = objeto_N_Doble.Doblar(CLng(Numero)) Call MsgBox(Resultado) </script> </body> </html> Al cargar la página:

Page 36: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

36/38

Figura 13.33

Page 37: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

37/38

Figura 13.34

Page 38: Código inseguro (Unsafe Code) e interoperabilidad. lo demás, el uso y declaración de punteros en C# es igual que en C y C++, con una excepción: en C y C++, cuando se declaran varias

Marco Besteiro y Miguel Rodríguez Código Inseguro

38/38