Red de conocimiento informático - Computadora portátil - Cómo liberar el objeto tinterfacedobject en Delphi

Cómo liberar el objeto tinterfacedobject en Delphi

Comprensión de la interfaz del Capítulo 4 de Delphi

No hace mucho, un amigo que se dedica al software me preguntó un acertijo. El misterio es "cita a ciegas", déjame adivinar un término de software. Lo pensé durante aproximadamente un minuto y supuse que la respuesta estaba "orientada a objetos". Pensé que era bastante interesante, así que tuve una idea y se me ocurrió un acertijo para responderle. El rompecabezas era "beso" y también se le pidió que adivinara un término de software. Un minuto después, dijo con humor: "¡Cuando estás frente a tu hermoso objeto, por supuesto que no puedes evitar interactuar con ella!". Nos reímos al mismo tiempo. Mientras hablábamos y reíamos, parecía que la relación entre nosotros y nuestros programas se profundizaba. Para nosotros, el software es vida.

Sección 1 El concepto de interfaz

El término "interfaz" tiene un significado demasiado amplio y puede causar fácilmente malentendidos. La interfaz de la que estamos hablando aquí no es la interfaz del programa en el diseño modular del programa, ni es la interfaz entre dispositivos de hardware de computadora. La interfaz de la que vamos a hablar ahora es un concepto de lenguaje de programación similar a una clase, y también es la tecnología básica para implementar software de objetos distribuidos.

En DELPHI, las interfaces se definen de la misma manera que las clases, pero en lugar de usar la palabra reservada clase, usan interfaz. Aunque las interfaces y las clases se definen de manera similar, las connotaciones de sus conceptos son muy diferentes.

Sabemos que una clase es una descripción abstracta de objetos con las mismas propiedades y comportamiento. Las descripciones de clases son para objetos del mundo real. La interfaz no describe el objeto, sólo el comportamiento. Una interfaz es una descripción de un método de comportamiento, independientemente de si es un objeto u otra cosa que implementa este método de comportamiento. Por lo tanto, los puntos de partida de las interfaces y las clases son diferentes y analizan los problemas desde diferentes perspectivas.

Se puede decir que la interfaz es un concepto puramente técnico que surge del desarrollo de tecnología de programación distribuida o entre procesos. El concepto de clase es una forma universal de pensar y es el núcleo del pensamiento orientado a objetos. Sin embargo, el concepto de interfaz se desarrolló junto con la idea del software orientado a objetos. Usar el concepto de interfaces para comprender y construir estructuras de software distribuido o de procesos cruzados es más intuitivo y simple que los conceptos de bajo nivel como la llamada a procedimiento remoto (RPC) que se usaba directamente en los primeros días. Porque puedes entender una interfaz como un objeto y ya no te importa si el objeto es local o remoto.

En DELPHI, la interfaz se declara como interfaz. El principio de denominación es: las interfaces se nombran comenzando con la letra I, al igual que las clases se nombran comenzando con la letra T. Solo se pueden definir métodos en la declaración de una interfaz, no miembros de datos. Debido a que la interfaz es solo una descripción de métodos y comportamientos, no almacena el estado del atributo del objeto. Aunque se pueden definir propiedades para interfaces en DELPHI, se debe acceder a estas propiedades en función de los métodos.

Todas las interfaces heredan directa o indirectamente de IUnknown. IUnknown es el ancestro original de todos los tipos de interfaz y tiene el mismo estatus que TObject en el concepto de clase. En realidad, es incorrecto decir "una interfaz hereda otra interfaz". En cambio, debería decir "una interfaz extiende otra interfaz". La expansión de la interfaz refleja una especie de "compatibilidad". Esta "compatibilidad" es única. Nunca habrá una situación en la que una interfaz sea compatible con dos interfaces principales al mismo tiempo.

Dado que la interfaz solo describe un conjunto de métodos y comportamientos, se deben usar clases para implementar estos métodos y comportamientos. Las interfaces no pueden crear instancias. No existen instancias de interfaz. Solo las clases pueden crear instancias de objetos. Pero debe haber una instancia de objeto detrás de una interfaz. Este objeto es el implementador de los métodos de la interfaz y la interfaz es una referencia a un conjunto de métodos del objeto.

Conceptualmente, la clase de un objeto puede implementar una o más interfaces. La responsabilidad de una clase para una interfaz es solo implementar la interfaz, y no se debe decir que la clase hereda una o más interfaces. La palabra "implementación" y la palabra "herencia" tienen significados diferentes y deben distinguirse conceptualmente.

Generalmente, al declarar una interfaz, necesita un identificador GUID que identifique de forma única el tipo de interfaz. Los tipos de interfaz deben ser utilizados por programas distribuidos en diferentes espacios de proceso o computadoras, a diferencia de los tipos de clases que solo se identifican y usan en un espacio de programa. Para garantizar que un tipo de interfaz pueda identificarse de forma única en todas partes, es necesario un método para identificar eficazmente diferentes interfaces. No es posible utilizar nombres artificiales. Nadie puede garantizar que la interfaz que desarrolle no tendrá el mismo nombre que otras.

Como resultado, surgió el GUID (Identificador único global) llamado "Identificador único global". Es un identificador generado aleatoriamente mediante un algoritmo complejo y tiene una longitud de 16 bytes, lo que puede garantizar que el identificador generado en cualquier parte del mundo sea diferente. En el entorno de edición de DELPHI, puede utilizar Ctrl+Shift+G para generar fácilmente un identificador GUID como identificador único de la interfaz.

Es necesario especificar un GUID para la interfaz. Aunque los GUID que no especifican una interfaz a menudo se pueden compilar y pasar, definitivamente habrá problemas al usar algunas funciones relacionadas con la identificación y conversión de la interfaz. Especialmente en el desarrollo de programas basados ​​en COM, GUID debe ser indispensable.

El concepto de interfaz es realmente muy simple, pero juega un papel clave en el desarrollo de software distribuido. La razón por la que algunos amigos piensan que las interfaces son complicadas es principalmente porque no comprenden los conceptos y principios de las interfaces. Porque la gente siempre tiene una sensación de misterio sobre las cosas que no saben. Esta sensación de misterio a menudo hace que la gente tema el mundo desconocido. Para descubrir el misterio de las interfaces, debes continuar aprendiendo y comprendiendo los misterios de las interfaces. De hecho, habrá mucha diversión en el proceso de exploración, ¿verdad?

Sección 2 IUnknown

Debido a que IUnknown es el ancestro común de todas las interfaces, primero debes comprenderlo. Conocer las causas de las cosas puede ayudarnos eficazmente a comprender el proceso y los resultados de las cosas. La definición original de IUnknown está en la unidad System.pas. Debido a que está definido en la unidad System.pas, debe ser algo original relacionado con el sistema o compilador. En cuanto a la definición de IUnknown, es muy simple, solo 6 líneas por oración.

IUnknown = interfaz

['{00000000-0000-0000-C000-000000000046}']

función QueryInterface(const IID: TGUID; out Obj) : HResult; stdcall;

función _AddRef: Integer; stdcall;

función _Release: stdcall;

end;

Sin embargo , estas 6 líneas de código de definición son la base del mundo de la interfaz. Tres de los métodos de interfaz contienen filosofías simples pero profundas. Comprender estas filosofías nos beneficiará mucho al escribir programas basados ​​en interfaces.

Estos tres métodos de interfaz de IUnknown son métodos que cada clase de objeto de interfaz debe implementar y son los métodos básicos del mecanismo de interfaz. ¿Por qué estos tres métodos son la base del mecanismo de interfaz? Sólo escucha mi larga historia.

Primero hablemos del método de interfaz QueryInterface. Sabemos que una clase de objeto puede implementar múltiples interfaces. Cualquier objeto de interfaz debe implementar la interfaz IUnknown. Por lo tanto, siempre que obtenga un puntero de interfaz, definitivamente puede llamar al método QueryInterface a través de este puntero de interfaz. Al llamar a QueryInterface, puede saber qué otras interfaces implementa este puntero de interfaz. Esto es muy importante para los mecanismos de programación de interfaces. Determinar si un puntero de interfaz implementa una función de interfaz y la coincidencia de interfaz y la conversión entre diferentes tipos de interfaz están relacionados con el método QueryInterface.

QueryInterface tiene dos parámetros y un valor de retorno. El primer parámetro es el identificador del tipo de interfaz, que es un identificador GUID de 16 bytes. Dado que el compilador DELPHI sabe qué GUID corresponde a cada interfaz, puede utilizar directamente un identificador como ImyInterface como primer parámetro. Si la interfaz admite el tipo de interfaz especificado por el primer parámetro, el puntero de interfaz obtenido se devuelve al programa que llama a través del segundo parámetro Obj y el valor de retorno es S_OK.

Desde aquí también se puede ver por qué es necesario especificar un identificador GUID para la interfaz.

Porque el método QueryInterface requiere dicho identificador, que es la base de la interfaz y los mecanismos de coincidencia y conversión.

A continuación, hablemos de los métodos _AddRef y _Release. Los métodos de interfaz _AddRef y _Release son métodos que cada clase de objeto de interfaz debe implementar. _AddRef aumenta el recuento de referencias al objeto de interfaz, mientras que _Release disminuye la referencia al objeto de interfaz. Si el recuento de referencias del objeto de interfaz es cero, el objeto de interfaz se destruye y se libera el espacio. Este es un principio básico requerido por el mecanismo de la interfaz. Es tan simple como 1+1=2 y no requiere una explicación profunda. Sólo los matemáticos están interesados ​​en estudiar por qué uno más uno es dos. Pero los matemáticos tienen un conocimiento profundo de 1+1=2. De manera similar, una comprensión profunda del mecanismo de referencia de objetos de la interfaz nos permitirá comprender muchas verdades, lo que beneficiará nuestro trabajo de desarrollo.

Un maestro dijo una vez: ¡Las interfaces se cuentan como referencias!

Para entender esta frase, primero debemos entender el concepto de “referencia”. "Cotización" significa "préstamo" e indica una relación de referencia. La parte citante solo tiene la conexión para encontrar a la parte citada, y la parte citada es el centro real. Dado que el objeto se puede encontrar a través de esta relación de referencia, la referencia es en realidad la identidad representativa del objeto. En programación, una referencia es en realidad un puntero, que utiliza la dirección del objeto como representante de identidad del objeto.

En programas que no se basan en mecanismos de interfaz, no es necesario gestionar las relaciones de referencia de objetos. Debido a que todas las instancias de objetos que no son de interfaz están en el mismo espacio de proceso, el proceso de creación, uso y liberación de objetos puede ser controlado estrictamente por programas. Sin embargo, en un programa basado en el mecanismo de interfaz, la creación, el uso y la liberación de objetos pueden ocurrir en el mismo espacio de proceso, o en diferentes espacios de proceso, o incluso en dos computadoras a miles de kilómetros de distancia en Internet. Cuando se establece una interfaz en un lugar, el objeto que implementa la interfaz puede existir en otro lugar; después de que se establece una interfaz en un lugar, se puede utilizar en otro lugar. En este caso, resulta muy difícil utilizar programas tradicionales para controlar la creación y liberación de objetos. Debe haber otro mecanismo acordado para manejar la creación y liberación de objetos. Por lo tanto, esta importante tarea recae en _AddRef y _Release de IUnknown.

Este mecanismo de referencia de objetos de interfaz requiere que la creación y liberación del objeto de interfaz sea responsabilidad del programa donde se encuentra la instancia del objeto, es decir, la clase de objeto que implementa la interfaz es responsable. Siempre que se haga referencia a la interfaz del objeto en cualquier lugar, se debe llamar al método _AddRef de la interfaz. Cuando ya no se hace referencia al objeto, también se debe llamar al método _Release de la interfaz. Una instancia de objeto se libera una vez que ya no se hace referencia a ella en ningún lugar.

Es precisamente para resolver el problema de la gestión del espacio de instancias de objetos de interfaz que los métodos _AddRef y _Release se han convertido en métodos que todas las clases de objetos de interfaz deben implementar.

Sección 3: La vida y la muerte de los objetos de interfaz

A primera vista, el título de esta sección parece un poco aterrador. ¿Cómo se relacionan los objetos de interfaz con la vida y la muerte? ¿Es realmente tan importante la vida o muerte de los objetos de la interfaz? Un buen gobernante debería preocuparse por la vida y la muerte de su pueblo. De manera similar, un buen programador también debería preocuparse por la vida y la muerte de sus objetos. Y los objetos de la interfaz son vagabundos que deambulan por la red distribuida, ¡y deberíamos preocuparnos más por su vida o muerte!

Porque el objeto de interfaz se crea con la creación de la referencia de interfaz y desaparece cuando se completa la referencia de interfaz. Cuando se utilizan interfaces en DELPHI, a nadie parece importarle cómo nacen y cómo mueren los objetos que implementan la interfaz. Ésta es la simplicidad del uso de interfaces en DELPHI, y también es el objetivo que persigue al resolver el problema del uso de mecanismos de interfaz. Cuando se necesita una interfaz, siempre habrá un objeto creado para ella. Una vez que ya no haga referencia a ninguna interfaz, el objeto morirá sin quejarse y nunca reducirá un solo byte de recursos del sistema. Es realmente un poco triste que "los gusanos de seda de primavera no se acaben hasta que mueran, y las antorchas de cera se conviertan en cenizas antes de que se sequen sus lágrimas".

Debido a que la vida y muerte de un objeto de interfaz está directamente relacionada con la cantidad de interfaces que hacen referencia al objeto, es necesario estudiar bajo qué circunstancias se aumentará una referencia de interfaz y bajo qué circunstancias se disminuirá una referencia de interfaz. para comprender la vida y la muerte del objeto de la interfaz.

Ahora implementaremos la clase de objeto de interfaz más simple TIntfObj, que solo implementa los tres métodos básicos definidos en la interfaz IUnknown. Algunos amigos sabrán de un vistazo que esta clase en realidad plagia parte del código de la clase TInterfacedObject en DELPHI. Es solo que agregamos algunas declaraciones de salida de información en los métodos _AddRef y _Release respectivamente, para que podamos explorar la vida y la muerte del objeto de interfaz.

Mire el siguiente programa:

programa ProgramaA;

usa

SysUtils, Dialogs;

tipo

TIntfObj = clase(TObject, IUnknown)

protegido

FRefCount: entero;

función QueryInterface(const IID: TGUID; out Obj): HResult; ;

función _AddRef: Entero; stdcall;

función _Release: Entero; stdcall;

end;

función TIntfObj.QueryInterface( const IID: TGUID; fuera Obj): HResult; stdcall;

const

E_NOINTERFACE = HResult($80004002);

comenzar

si GetInterface(IID, Obj) entonces Resultado := 0 else Resultado := E_NOINTERFACE;

end;

función TIntfObj._AddRef: Integer; stdcall;

comenzar

INC(FRefCount);

ShowMessage(Format('Aumentar el recuento de referencias a %d.', [FRefCount]));

resultado:= FRefCount ;

fin;

función TIntfObj._Release: Entero; stdcall;

comenzar

DEC(FRefCount);

si FRefCount <> 0 entonces

ShowMessage(Format('Reducir el recuento de referencias a %d.', [FRefCount]))

si no comenzar

Destruir;

ShowMessage('Reducir el recuento de referencias a 0 y destruir el objeto.');

end;

resultado:=FRefCount;< / p>

end;

var

aObject:TIntfObj;

aInterface:IUnknown;

procedimiento IntfObjLife;

begin

aObject:=TIntfObj.Create;

aInterface:=aObject; //Agregar una referencia

aInterface:=nil; / Reducir una referencia

end;

begin

IntfObjLife;

end.

Necesitamos usar Función de depuración de un solo paso para estudiar la relación entre el aumento y la disminución del recuento de referencias de la interfaz y la vida y muerte de la interfaz. Por lo tanto, se recomienda borrar el elemento Optimización en la página Compilador en la opción Opciones para evitar que el compilador optimice las instrucciones que necesitamos.

Cuando el programa ejecute las tres líneas de código de la subrutina IntfObjLife, depure el código paso a paso. Descubrirá que cuando se produce una asignación a una variable de tipo de interfaz, provocará un aumento o disminución en el recuento de referencias de la interfaz.

Ejecute la instrucción

aInterface:=aObject;

Aparecerá el mensaje "El recuento de referencias aumenta a 1", indicando que se ha agregado una referencia de interfaz. .

Cuando se ejecuta la declaración

aInterface:=nil;

, aparecerá "El recuento de referencias disminuye a 0 y destruirá el objeto, lo que indica que". la referencia de la interfaz disminuye a cero y el objeto de la interfaz se elimina.

Entonces, podemos sacar la conclusión: al asignar un valor de referencia a una variable del tipo de interfaz, el recuento de referencias al objeto de la interfaz aumentará y cuando el valor de referencia de la variable del tipo de interfaz sea; borrado (asignado nulo), reducirá el recuento de referencias del objeto de interfaz.

Mire el código siguiente para profundizar su comprensión de esta conclusión.

var

aObjeto: TIntfObj;

InterfazA, InterfazB: IDesconocido;

……

aObjeto: = TIntfObj.Create;

InterfaceA := aObject; //La referencia aumenta a 1

InterfaceA := InterfaceA; //La referencia aumenta a 2, pero inmediatamente disminuye a 1

p>

InterfaceB := InterfaceA; //La referencia aumenta a 2

InterfaceA := nil; //La referencia disminuye a 1

InterfaceB := InterfaceA ; // La referencia disminuye a 0, libera el objeto

......

Ya sea asignando el objeto de interfaz a la variable o asignando la variable de interfaz a la interfaz. variable, o asignando nil a la variable de interfaz, esto se confirma. Curiosamente, cuando se ejecuta la sentencia InterfaceA := InterfaceA, la referencia al objeto de interfaz primero aumenta y luego disminuye inmediatamente. ¿Por qué sucede esto? ¡Déjate pensar a ti mismo!

A continuación, echemos un vistazo al siguiente código:

procedure IntfObjLife;

var

aObject:TIntfObj;

aInterfaz:IUnknown;

comienzo

aObjeto:=TIntfObj.Create;

aInterfaz:=aObjeto;

fin ;

La diferencia entre este proceso y el anterior es que las variables se definen como variables locales, y al final no se asignan valores nulos a las variables de interfaz. Al revisar este código, descubrimos que antes de que el programa ejecutara la declaración final de la subrutina, la referencia al objeto de interfaz todavía se reducía a 0 y se liberaba. ¿Por qué es esto?

Sabemos que las variables tienen alcance. El alcance de las variables globales está en cualquier parte del programa, mientras que el alcance de las variables locales está solo dentro de la subrutina correspondiente. Una vez que una variable sale de su alcance, la variable en sí ya no existe y el valor que almacena pierde su significado. Por lo tanto, cuando el programa esté a punto de salir de la subrutina, la variable local aInterface ya no existirá y el valor de referencia del objeto de interfaz que almacena también perderá su significado. Smart DELPHI reduce automáticamente el recuento de referencias del objeto de interfaz para garantizar que el programa pueda administrar correctamente el espacio de memoria del objeto de interfaz durante las llamadas y retornos de capa.

Por lo tanto, podemos sacar una nueva conclusión: cuando cualquier variable de interfaz excede su alcance, el recuento de referencias del objeto de interfaz relacionado se reducirá.

Cabe señalar que la variable de parámetro de un subprograma también es una variable y su alcance también está dentro del alcance del subprograma.

Cuando se llama a una subrutina que contiene parámetros de tipo de interfaz, el recuento de referencias del objeto de interfaz relevante aumentará debido al paso de los parámetros y disminuirá cuando la subrutina regrese.

De manera similar, si el valor de retorno del subprograma es un tipo de interfaz, el alcance del valor de retorno es el rango que comienza desde el punto de retorno del programa que llama hasta la última declaración final del programa que llama. Esta situación también hará que el recuento de referencias del objeto de interfaz aumente o disminuya.

Es hora de resumir la vida y la muerte del sujeto. Luego de cerrar el ataúd, podemos extraer los siguientes principios:

1. Al asignar el valor de referencia del objeto de interfaz a elementos como variables globales, variables locales, variables de parámetros y valores de retorno, aumentará el recuento de referencias del objeto de interfaz.

2. Antes de cambiar el valor de referencia de la interfaz originalmente almacenado por una variable, se disminuirá el recuento de referencia de su objeto asociado. Asignar nil a una variable es un caso especial de asignación y modificación de referencias de interfaz. Solo reduce el recuento de referencias del objeto de interfaz original y no implica nuevas referencias de interfaz.

3. Cuando elementos como variables globales, variables locales, variables de parámetros y valores de retorno que almacenan valores de referencia de la interfaz exceden su alcance, el recuento de referencias del objeto de la interfaz se reducirá automáticamente.

4. Cuando el recuento de referencias del objeto de interfaz llega a cero, el espacio de memoria del objeto de interfaz se libera automáticamente. (En algunos sistemas middleware que utilizan tecnología de almacenamiento en caché de objetos, como MTS, es posible que no se siga este principio)

Lo que hay que recordar es que una vez que entregue el objeto de interfaz creado a la interfaz, el objeto Tu vida y tu muerte están confiadas a la interfaz. Al igual que casar a tu preciosa hija con un hombre leal, debes confiar completamente en él y creer que él puede cuidarla bien. De ahora en adelante, todas las conexiones con objetos deben realizarse a través de interfaces, en lugar de tratar directamente con objetos. Ya sabes, pasar por alto al yerno e intervenir directamente en los asuntos de la hija puede causar grandes problemas.

Si no lo crees, echemos un vistazo al siguiente código:

program HusbandOfWife;

tipo

IHusband = interfaz

función GetSomething:string;

fin;

TWife = class(TInterfacedObject, IHusband)

privado

FSomething:string;

público

constructor Crear(Algo:cadena);

función ObtenerAlgo:cadena;

fin;

constructor TWife.Create(Algo: cadena);

comenzar

Crear heredado;

FAlgo:=Algo;

fin;

función TWife.GetSomething:string;

comenzar

resultado:= FSomething;

fin;

procedimiento MaridoHaciendo(aMarido:YoMarido

comienzo

fin;

var

LaEsposa: TEsposa;

ElHusband : IHusband;

comenzar

TheWife := TWife.Create('万冠家财');

TheHusband := TheWife //El objeto; TheWife se delega a la variable de interfaz general TheHusband

TheHusband := nil; //Borra la referencia de la interfaz y el objeto desaparece

TheWife.GetSomething //El acceso directo al objeto debe; ser un error!

TheWife := TWife.Create('万冠家财');

HusbandDoing(TheWife); //El objeto se delega a la variable de interfaz de parámetro aHusband y el objeto desaparece. cuando se devuelve

TheWife.GetSomething; //¡Acceder al objeto directamente provocará un error!

fin.