Usar escuchas de eventos en un entorno no gui(DLL)(Delphi)
event-handling freepascal (4)
¿Qué hay de malo en crear una nueva clase propia que será el delegado, en lugar de un formulario?
type
TDataDelegate = class
public
procedure DataChange(Sender: TObject; Field: TField);
etc...
end;
procedure TDataDelegate.DataChange(Sender: TObject; Field: TField);
begin
// Do what you normally would do in your form''s event handler
end;
Y solo asegúrese de crear una instancia de la clase
DataDelegate := TDataDelegate.Create;
DataSource2.OnDataChange := DataDelegate.DataChange;
etc ...
En otras palabras, en lugar de un formulario, usa una clase que hayas escrito para manejar los eventos de las distintas clases. Al igual que en un formulario, cada uno de los procedimientos debe tener la firma del controlador de eventos. La única diferencia es que el IDE no creará estos métodos por usted.
También podría usar un TDataModule, supongo, pero no estoy seguro de las implicaciones. La ventaja sería soporte IDE.
Estoy intentando convertir una aplicación GUI que hice en Delphi (en realidad, su Lazarus) en una biblioteca (DLL).
En la aplicación GUI, utilicé un OnDataChange
eventos OnDataChange
, pero parece que no puedo imaginar cómo hacer lo mismo para la biblioteca.
Esto es lo que parece en la aplicación GUI:
procedure TForm1.Datasource2DataChange(Sender: TObject; Field: TField);
begin
ZMakeRankedTable.Close;
GetNN;
end;
Y en el archivo LFM de la unidad:
object Datasource2: TDatasource
DataSet = ZMakeRankedTable
OnDataChange = Datasource2DataChange
left = 184
top = 95
end
¿Cómo hago lo mismo para la biblioteca? ¿Dónde puedo inicializar el oyente del evento?
Convierte tu Formulario a un Módulo de Datos y crea una instancia de eso:
DTM := TMyDataModule.Create(nil);
Debería funcionar incluso en aplicaciones que no sean de GUI. No he usado a Lazarus para más que unas pocas pruebas, pero no veo ninguna razón por la cual esto no funcione.
De hecho, esto está bien explicado aquí :
El problema es que un puntero a método (OnDataChange) debe ser un procedimiento de un objeto (como TForm), no un procedimiento regular.
Hay muchas explicaciones acerca de los eventos, pero la mayoría son incompletas, o no son fáciles de entender, demasiado comprimidas, o no paso a paso, o "no OO" ... así que decidí proporcionar una descripción de mi enfoque del tema.
Escribir una DLL significa encapsulación. Me gustaría proponer la siguiente estructura, que utiliza una clase interconectada. Claro que funcionaría sin interfaz también, pero hablar de DLL implica encapsulación ... y las interfaces son una herramienta / estructura central para lograrlo.
De lo contrario, (instancias de) las interfaces son contadas por referencia, lo que significa que, si lo hace exhaustivamente = siempre, el código "se comportará mejor" (consulte otras entradas sobre las interfaces).
También me refiero a las interfaces por una razón más (aunque relacionada), es posible que esté menos fuera de tema, como se puede adivinar: Te obliga a mantener las cosas separadas = explícito, como verás. Sin embargo, tendrá acceso fácil y simple a todas las "propiedades" del objeto de implementación, por supuesto también a través de los límites de la DLL.
Para empezar, una buena manera de encapsular las cosas en una DLL es exportar solo un procedimiento, que sería
export interfaceProvider;
que correspondería a una función estándar (no ser parte de una clase)
function interfaceProvider() : IYourInterface;
En esta función se llamaría al constructor de la clase. Una variable global (dentro de la DLL) del tipo IYourInterface no es necesaria, pero simplifica la vida.
La función interfaceProvider () se ubicaría en una especie de envoltura o unidad de puerta de enlace.
Entre otros métodos operacionales, la interfaz IYourInterface también exhibiría un método
procedure assignDataChangeEvent( _event : TDataChangeEvent);
que a su vez es implementado por la clase respectiva que se deriva de la interfaz (también forma parte de la DLL, por supuesto), como tal
TEncapsulatedStuffinDLL = class(Tinterfacedobject, IYourInterface)
Ahora solo tenga en cuenta que los eventos son como "devoluciones de llamada elegantes" o "devoluciones de llamada organizadas de forma elegante". En Delphi, la clave es una definición de tipo particular. Como un tipo en la misma unidad donde define la interfaz, y antes de la definición de la interfaz en sí, agregue algo como esto
TDataChangeEvent = procedure(const Sender:TObject; const n : integer) of object;
Tenga en cuenta que el oyente / receptor del evento (aquel que está fuera / usando el DLL) tiene que usar precisamente la misma firma de parámetros (ver a continuación: proc. DbChangeListener).
En la clase que implementa la interfaz, la llamamos TEncapsulatedStuffinDLL, primero definiría como campo privado
private
OnDataChange : TDataChangeEvent ;
...
Luego necesitamos los siguientes dos métodos:
procedure TEncapsulatedStuffinDLL.assignDataChangeEvent( _eventListener : TDataChangeEvent ) ;
begin
// here we assign the receiver of the callback = listener to the event
OnDataChange := _eventListener ;
end;
procedure TEncapsulatedStuffinDLL.indicateChange;
begin
// release the event = perform the callback
if Assigned(OnDataChange) then begin
OnDataChange(self);
end;
// note that OnDataChange is pointing to the assigned receiver, since
// the method assignDataChangeEvent has been called
end;
En la sección donde sucede lo relevante, llamamos al lanzamiento del evento
procedure TEncapsulatedStuffinDLL.someMethod();
begin
// sth happening, then "releasing the event" = executing the callback
// upon some condition we now do...
indicateChange ;
end;
El último bit entonces es iniciar todo desde el exterior. Supongamos que la clase donde sucede esto se llama TDllHost, por lo que primero definimos el método de escucha real
public
procedure dbChangeListener(const Sender:TObject; const n : integer);
... implementarlo así
procedure TDllHost.dbChangeListener(const Sender:TObject; const n : integer);
begin
.... doing sth based on the provided parameters
end;
y durante el tiempo de ejecución nos iniciamos así (las interfaces se definen mejor en su propia unidad, por supuesto, a pesar de que Delhi permite hacerlo "enredado" ... pero esto corroboraría la idea de encapsulación)
procedure TDllHost.init();
var
dbstuffInterface : IYourInterface ; // could also be global private to TDllHost
begin
// please complete this (off topic) section about late binding a DLL
....
// we would have a retrieval of the interface from the DLL
dbstuffInterface := interfaceProvider();
// and finally we provide the procedure pointer to the class
dbstuffInterface.assignDataChangeEvent( dbChangeListener );
// the assignment of the method to the method variable
// is done by the class itself
end;
Un beneficio significativo proporcionado por el uso de interfaces para organizar las cosas en un archivo DLL es que el IDE lo respalda mucho mejor. Sin embargo, rara vez se encuentran ejemplos de programación haciendo un uso estricto de las interfaces, por desgracia.
En caso de que no use una DLL de primera mano, el procedimiento init () se vería diferente. En lugar de cargar la DLL, se necesitaría dbstuffInterface para crear una instancia a través de una llamada normal al constructor de la clase implementadora.
En mi humilde opinión, el manejo de los eventos en DLL de esta manera es bastante sencillo, y generalmente aplicable desde una perspectiva OO. Debería funcionar en cualquier idioma que soporte interfaces (y tipos de procedimientos). Incluso es mi forma preferida de organizar devoluciones de llamadas / eventos, si no uso DLL en absoluto ... sin embargo, en un momento posterior uno puede cambiar fácilmente a una encapsulación completa utilizando archivos DLL. El único inconveniente (menor) podría ser que dicha DLL probablemente no se pueda utilizar a través de C-Standards. Si desea utilizarlo, por ejemplo, en Java, sería necesario un contenedor adicional para volver a POP-NO (procedimientos antiguos, sin objetos).