PDA

Ver la Versión Completa : error en dll con una form


cd.rafael
22-01-2013, 22:43:14
Cordial saludo,

He revisado muchos temas respecto al manejo de forms dentro de una dll y hasta ahora no he podido resolver mi problema, que a continuación expongo:

Tengo un ejecutable normal el cual invoca a una dll que a su vez lanza una form. Si dicha form la muestro con "ShowModal" todo funciona bien, pero si la dejo solo como "Show", el ejecutable deja de responder.

Este es el código que uso en el ejecutable:


procedure TForm1.Button1Click(Sender: TObject);
type
TDLLFunc = procedure(pStUsuario: String; pStPassword: String; pStDB: String; pStEsquema: String; pStTabla: String);
const
DLLFunc: TDLLFunc = nil;
var
DLLHandle: THandle;
begin
DLLHandle := LoadLibrary('forma.dll');
if (DLLHandle < HINSTANCE_ERROR) then
raise Exception.Create('Librería no encontrada.' + SysErrorMessage(GetLastError));

try
@DLLFunc := GetProcAddress(DLLHandle, 'pFormaMaestro');
if Assigned(DLLFunc) then
DLLFunc(txtUsuario.Text, txtContra.Text, txtBD.Text, txtEsquema.Text, txtTabla.Text);

finally
FreeLibrary(DLLHandle);
end;
end;Y el código de la dll es:


library forma;

uses
ShareMem,
SysUtils,
Dialogs,
Windows,
Classes,
DConexion in 'DConexion.pas' {dtmConexion: TDataModule} ,
FPrincipal in 'FPrincipal.pas' {frmPrincipal} ,
FAcerca in 'FAcerca.pas' {frmAcerca} ,
FBusqueda in 'FBusqueda.pas' {frmBusqueda} ,
FSplash in 'FSplash.pas' {frmSplash} ,
FRegistro in 'FRegistro.pas' {frmRegistro};

procedure pFormaMaestro(pStUsuario: String; pStPassword: String; pStDB: String; pStEsquema: String; pStTabla: String);
begin
//Pantalla Inicial
frmSplash := TfrmSplash.Create(nil);
frmSplash.Show;
frmSplash.Update;

//Crear el DataModulo
dtmConexion := TdtmConexion.Create(nil);
dtmConexion.gStTabla := pStTabla;
dtmConexion.gStUsuario := pStUsuario;
dtmConexion.gStPassword := pStPassword;
dtmConexion.gStDB := pStDB;
dtmConexion.gStEsquema := pStEsquema;
dtmConexion.pInicioDatos;
frmSplash.Hide;
frmSplash.Free;

//Crear la Forma
if (dtmConexion.gBlSelect) then
begin
try
frmPrincipal := TfrmPrincipal.Create(nil);
frmPrincipal.ShowModal;
dtmConexion.cdsPrincipal.Close;
dtmConexion.qryPrincipal.Close;
frmPrincipal.Hide;
frmPrincipal.Free;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
end;

exports pFormaMaestro;

begin

end.
Como comenté anteriormente, el código que acabé de exponer funciona bien, pero si cambio el "ShowModal" por el "Show" la aplicación falla.

Cabe anotar que estas líneas de código:


dtmConexion.cdsPrincipal.Close;
dtmConexion.qryPrincipal.Close;
frmPrincipal.Hide;
frmPrincipal.Free;
Las manejo dentro de los eventos de cierre del form cuando lo muestro con "Show".

Agradezco de antemano cualquier colaboración.

nlsgarcia
23-01-2013, 02:15:30
cd.rafael,


He revisado muchos temas respecto al manejo de forms dentro de una dll y hasta ahora no he podido resolver mi problema, que a continuación expongo: Tengo un ejecutable normal el cual invoca a una dll que a su vez lanza una form. Si dicha form la muestro con "ShowModal" todo funciona bien, pero si la dejo solo como "Show", el ejecutable deja de responder.

Revisa este código:

Library DLLForm;

uses
SysUtils,
Forms,
UnitFrmDLL in 'UnitFrmDLL.pas' {FrmDLL};

{$R *.res}

procedure ShowForm; StdCall;
var
f : TFrmDLL;
begin
f := TFrmDLL.Create(Application);
f.Show;
end;

exports
ShowForm;

begin
end.

El código anterior declara una DLL (DLLForm) que Exporta el Procedure ShowForm el cual muestra un formulario con el método Show declarado en la unidad UnitFrmDLL.pas

Revisa el código de la Unidad UnitFrmDLL del DLL anterior.

unit UnitFrmDLL;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TFrmDLL = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;

function mSum(x1, x2 : Double) : Double;
function mSub(x1, x2 : Double) : Double;
function mMul(x1, x2 : Double) : Double;
function mDiv(x1, x2 : Double) : Double;

implementation

{$R *.dfm}

function mSum(x1, x2 : Double) : Double;
begin
Result := x1 + x2;
end;

function mSub(x1, x2 : Double) : Double;
begin
Result := x1 - x2;
end;

function mMul(x1, x2 : Double) : Double;
begin
Result := x1 * x2;
end;

function mDiv(x1, x2 : Double) : Double;
begin
Result := x1 / x2;
end;

procedure TFrmDLL.Button1Click(Sender: TObject);
var
x1, x2 : Double;
begin
x1 := StrToFloat(Edit1.Text);
x2 := StrToFloat(Edit2.Text);
ShowMessage(FloatToStr(mSum(x1,x2)));
end;

procedure TFrmDLL.Button2Click(Sender: TObject);
var
x1, x2 : Double;
begin
x1 := StrToFloat(Edit1.Text);
x2 := StrToFloat(Edit2.Text);
ShowMessage(FloatToStr(mSub(x1,x2)));
end;

procedure TFrmDLL.Button3Click(Sender: TObject);
var
x1, x2 : Double;
begin
x1 := StrToFloat(Edit1.Text);
x2 := StrToFloat(Edit2.Text);
ShowMessage(FloatToStr(mMul(x1,x2)));
end;

procedure TFrmDLL.Button4Click(Sender: TObject);
var
x1, x2 : Double;
begin
x1 := StrToFloat(Edit1.Text);
x2 := StrToFloat(Edit2.Text);
ShowMessage(FloatToStr(mDiv(x1,x2)));
end;

procedure TFrmDLL.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;

end.

Este formulario es instanciado en el Procedure ShowForm Exportado en el DLL anterior.

Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

procedure ShowForm; StdCall; External 'DLLForm';

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowForm;
end;

end.

El código anterior declara una referencia al procedure ShowForm del DLL anterior e invoca una llamada al mismo.

Revisa estos links:

Creating and Using DLLs from Delphi
http://delphi.about.com/od/windowsshellapi/a/dll_basics.htm

Static vs. Dynamic Dynamic Link Library Loading - A Comparison
http://delphi.about.com/od/windowsshellapi/a/delphi-dll-loading-static-dynamic.htm

Creating and Using DLLs from Delphi
http://delphi.about.com/od/windowsshellapi/a/dll_basics.htm

Tutorials - Adding forms to a DLL
http://www.delphi-central.com/formdll.aspx

Todo el código anterior este disponible en el link: http://terawiki.clubdelphi.com/Delphi/Ejemplos/Varios/?download=Form_in_DLL.rar

Espero sea útil :)

Nelson.

ozsWizzard
23-01-2013, 09:38:32
Hola, cd.rafael.

A ver, lo primero son consejillo. Cuando crees y liberes objetos, yo me aseguraría de que se liberen siempre, haya habido errores o no en el código intermedio. Lo haría de la siguiente manera:

Objeto := TObjeto.Create(param);
try
//Codigo...
finally
Objeto.Free; //Si la variable es global, incluso usaría FreeAndNil(Objeto);
end;



Por otra parte, sobre este código:

procedure pFormaMaestro(pStUsuario: String; pStPassword: String; pStDB: String; pStEsquema: String; pStTabla: String);
begin
//Pantalla Inicial
frmSplash := TfrmSplash.Create(nil);
frmSplash.Show;
frmSplash.Update;

//Crear el DataModulo
dtmConexion := TdtmConexion.Create(nil);
dtmConexion.gStTabla := pStTabla;
dtmConexion.gStUsuario := pStUsuario;
dtmConexion.gStPassword := pStPassword;
dtmConexion.gStDB := pStDB;
dtmConexion.gStEsquema := pStEsquema;
dtmConexion.pInicioDatos;
frmSplash.Hide;
frmSplash.Free;

//Crear la Forma
if (dtmConexion.gBlSelect) then
begin
try
frmPrincipal := TfrmPrincipal.Create(nil);
frmPrincipal.ShowModal;
dtmConexion.cdsPrincipal.Close;
dtmConexion.qryPrincipal.Close;
frmPrincipal.Hide;
frmPrincipal.Free;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
end;


Lo primero de lo que me doy cuenta es que el DataModule no lo liberas nunca, a veces las llamadas a dll dan problemas por cosas así.

Por otra parte, yo he tenido problemas usando formularios y componentes de base de datos en dll, pero sólo con dbexpres. Y no he sido capaz de arreglarlo, es dbexpres es así :(

cd.rafael
23-01-2013, 13:55:08
Cordial Saludo,

De antemano agradezco sus prontas respuestas.

Respecto a la respuesta de nlsGarcia, tu apunte funciona perfecto, pero el tema es que quisiera que fuera de manera dinámica para evitar el crecimiento del tamaño del ejecutable. La idea es usar esa dll con el fin de evitar casi 20 formas que son similares.


Para la otra respuesta de ozsWizzard, te comento que ya había probado el liberar el DataModulo, pero me generaba excepciones que no pude controlarlas. Lo más extraño es que si lo hago de manera estática, como propone nlsGarcia, no me genera error. El problema es cuando es dinámica.

Voy a tratar de hacer una forma sencilla sin datamodulo y sin componentes, para ver qué puede ser.

Voy comentando lo que me vaya pasando...

cd.rafael
23-01-2013, 16:01:07
Cordial Saludo,

Pude resolver el problema colocando "SimpleShareMem" en el "uses" de la librería (tiene que estar de primero). El asunto es que cuando llamo al form con Show, la librería sigue su ejecución y no espera a que el formulario interactúe con el usuario. La verdad no sé porqué puede ser.

Lo que necesito es que la librería no termine su ejecución hasta que el formulario se cierre, pero no quiero usar ShowModal, porque me bloquea la aplicación principal.

Agradezco toda la colaboración que me puedan prestar.

cd.rafael
23-01-2013, 17:30:06
Cordial Saludo,

Ya logré realizar el cargue de la librería más de una vez. Se debe eliminar la instrucción "FreeLibrary" del mismo procedure donde se llama, hay que buscar otra forma para liberarla, tratando de detectar si la forma ya se cerró.

En fin... la gran decepción que tuve fue lo siguiente:

Cuando se lanza por primera vez la dll, la cual maneja los datos de una tabla X, funciona perfectamente. Cuando se llama la dll por segunda vez para manejar los datos de la tabla Y, sin haber terminado la primera llamada, la primera instancia que estaba manejando la tabla X, adopta los datos de la tabla Y que se maneja en la segunda instancia. Puede que sea por el manejo compartido de direcciones de memoria.

Lo único que me queda es pasar de librería a un ejecutable para no perder el trabajo.

Si de pronto alguien tiene alguna sugerencia, con gusto será escuchada.

Gracias.

ozsWizzard
23-01-2013, 17:36:15
Yo haría funciones distintas para cada tabla. Si no es eso creo que no he entendido bien cual es el problema.

cd.rafael
23-01-2013, 18:31:57
Cordial Saludo,

La idea de la librería es proporcionar una forma que me permita manejar las operaciones básicas en una tabla cualquiera (insertar, modificar, eliminar, imprimir y exportar). De esta manera evito tener una forma para cada tabla (aclarando que son tablas básicas, de referencia), ya que puedo enviarle a la dll cualquier tabla sin necesidad de generar código específico para cada tabla. Lo más importante es que se cargara de forma dinámica y no incrementar el tamaño del ejecutable principal.

Si el requerimiento hubiera permitido mantener la forma en estado modal, no habría problema, pero se necesita poder ver abrir más de una vez dicha librería y es ahí donde todo se complica, porque no independiza los datos. No coloco una función para cada tabla porque no sería algo estándar.

Gracias.

ozsWizzard
24-01-2013, 09:11:11
No estoy seguro, pero cada llamada debería ser independiente.

Lo que yo haría y que, a priori, debería funcionar es lo siguiente:

1.- En la dll, conexión deshabilitada y componentes de base de datos deshabilitados.
2.- La función de la dll tiene que tener como parámetros los datos de conexión y la tabla a la que acceder.
3.- una vez se llama con estos parámetros, la función de la dll debe montar la conexión y el acceso a la tabla.

Hasta donde yo sé, no debería haber problema.

nlsgarcia
24-01-2013, 17:12:58
cd.rafael,


Respecto a la respuesta de nlsgarcia, tu apunte funciona perfecto, pero el tema es que quisiera que fuera de manera dinámica para evitar el crecimiento del tamaño del ejecutable.

Revisa este código:

library Project1dll;

uses
SysUtils,
Classes,
DllForm in 'DllForm.pas' {frmDllForm};

procedure ShowDllForm;stdcall;
begin
frmDllForm :=TfrmDllForm.Create(nil);
frmDllForm.Show;
end;

function ShowDllFormModal:integer;stdcall;
begin
frmDllForm :=TfrmDllForm.Create(nil);
Result := frmDllForm.ShowModal;
end;

Exports
ShowDllForm,
ShowDllFormModal ;

begin
end.

El código anterior declara una DLL que exporta dos rutinas que visualizan formularios, una en forma Modal (Function) y otra en forma No Modal (Procedure).

Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Db, DBTables;

type
TShowForm = procedure;
TShowFormModal = function :integer;

TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
DLLHandle: THandle;
ShowForm : TShowForm;
ShowFormModal : TShowFormModal;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowFormModal;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ShowForm;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
DLLHandle := LoadLibrary('Project1dll.dll');
if DLLHandle <> 0 then
begin
@ShowForm := GetProcAddress(DLLHandle, 'ShowDllForm');
@ShowFormModal := GetProcAddress(DLLHandle, 'ShowDllFormModal');
end;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
FreeLibrary(DLLHandle);
end;

end.

El código anterior permite de forma dinámica cargar el Procedure ShowForm y la Function ShowFormModal del DLL anterior para el manejo de formularios.


Pude resolver el problema colocando "SimpleShareMem" en el "uses" de la librería (tiene que estar de primero)

Revisa este texto sobre ShareMem:

Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select View-Project Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the DELPHIMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using DELPHIMM.DLL, pass string information using PChar or ShortString parameters.

Te sugiero que uses parámetros de llamada y respuesta de tipo PChar en vez de Strings con el uso de la DLL lo cual eliminara la necesidad de la Unidad ShareMem y hara el DLL más portable al usar tipos de datos en Windows comunes entre diferentes lenguajes.


Ya logré realizar el cargue de la librería más de una vez. Se debe eliminar la instrucción "FreeLibrary" del mismo procedure donde se llama, hay que buscar otra forma para liberarla, tratando de detectar si la forma ya se cerró

En el código anterior se resuelve el problema de la Persistencia del Formulario al manejar la carga y la liberación de memoria de la DLL por separado, esto permite cargar solo las funciones y métodos exportados que sean requeridos y liberarlos al finalizar la aplicación, lo cual es lógico en este caso particular.


Cuando se lanza por primera vez la dll, la cual maneja los datos de una tabla X, funciona perfectamente. Cuando se llama la dll por segunda vez para manejar los datos de la tabla Y, sin haber terminado la primera llamada, la primera instancia que estaba manejando la tabla X, adopta los datos de la tabla Y que se maneja en la segunda instancia

Te sugiero revisar la lógica de tu aplicación, quizás estés utilizando variables globales comunes entre llamadas.


Lo único que me queda es pasar de librería a un ejecutable para no perder el trabajo.

Revisa todo lo comentado anteriormente, quizás puedas adaptarlo a tu proyecto fácilmente e implementar una DLL portable gestionada de forma dinámica por tu aplicación.

Revisa estos links:

Tutorials - Adding forms to a DLL
http://www.delphi-central.com/formdll.aspx

Tutorial - Dynamically Loading DLL's
http://www.delphi-central.com/dynamicdll.aspx

En estos links se explica el manejo de forms en Delphi por medio de DLLs de forma Dinámica y Estática con los ejemplos mostrado anteriormente los cuales se pueden descargar para su análisis.

Espero sea útil :)

Nelson.

cd.rafael
28-01-2013, 21:14:47
Cordial Saludo,

Ya correjí el tema del ShareMem usando PChar, pero el problema de los datos cuando se inician dos instancias de la dll continúa.

Las variables que uso para el llamado no son globales, son idnependientes para cada llamada a la dll. :confused:

Muchas gracias.

nlsgarcia
29-01-2013, 01:52:20
cd.rafael,


La idea de la librería es proporcionar una forma que me permita manejar las operaciones básicas en una tabla cualquiera (insertar, modificar, eliminar, imprimir y exportar). De esta manera evito tener una forma para cada tabla (aclarando que son tablas básicas, de referencia), ya que puedo enviarle a la dll cualquier tabla sin necesidad de generar código específico para cada tabla.



Cuando se lanza por primera vez la dll, la cual maneja los datos de una tabla X, funciona perfectamente. Cuando se llama la dll por segunda vez para manejar los datos de la tabla Y, sin haber terminado la primera llamada, la primera instancia que estaba manejando la tabla X, adopta los datos de la tabla Y que se maneja en la segunda instancia.



...el problema de los datos cuando se inician dos instancias de la dll continúa. Las variables que uso para el llamado no son globales, son idnependientes para cada llamada a la dll.

Elimina esta declaración de la unidad DConexion:

var
dtmConexion : TDataModule;

Modifica la DLL forma en lo referente al Data Modulo dtmConexion:

procedure pFormaMaestro(pStUsuario: String; pStPassword: String; pStDB: String; pStEsquema: String; pStTabla: String);
var
dtmConexion : TDataModule;
begin
//Pantalla Inicial
frmSplash := TfrmSplash.Create(nil);
frmSplash.Show;
frmSplash.Update;

//Crear el DataModulo
dtmConexion := TdtmConexion.Create(Application);
dtmConexion.gStTabla := pStTabla;
dtmConexion.gStUsuario := pStUsuario;
dtmConexion.gStPassword := pStPassword;
dtmConexion.gStDB := pStDB;
dtmConexion.gStEsquema := pStEsquema;
dtmConexion.pInicioDatos;
frmSplash.Hide;
frmSplash.Free;

//Crear la Forma
if (dtmConexion.gBlSelect) then
begin
try
frmPrincipal := TfrmPrincipal.Create(nil);
frmPrincipal.ShowModal;
dtmConexion.cdsPrincipal.Close;
dtmConexion.qryPrincipal.Close;
dtmConexion.Free;
frmPrincipal.Hide;
frmPrincipal.Free;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
end;

La idea es que se cree una nueva variable local del Data Modulo dtmConexion y su instancia correspondiente en cada llamada del Procedimiento pFormaMaestro y que esta libere todos su recursos al finalizar el procedimiento mencionado para de esta forma asegurar que las instancias sean independientes entre llamadas.

Espero sea útil :)

Nelson.