PDA

Ver la Versión Completa : Form en DLL


Jose Roman
09-08-2012, 16:51:43
Hola a todos,
Estoy realizando un programa en el cual hay un campo CONTRASEÑA si el usuario graba los cambios aparecera una ventana (la cual esta incluida en la DLL) donde se solicita confirmar la contraseña, el programa verificara que ambas contraseñas sean iguales, esto lo confirma por medio de una funcion (incluida tambien en la DLL), el intento lo hice por medio de un PROCEDURE (para que apareciera la ventana y no hubo inconveniente) pero al realizarlo con una FUNCTION me genera un error:

Access violation at address 00000000. Read of address 00000000.

Alguien podriame decirme donde tengo el error??, adjunto el codigo fuente:

Codigo fuente DLL



library CLDRes;
uses
SysUtils,
Classes,
bsSkinData,
Forms,
Controls,
uPassOk in 'FORM\uPassOk.pas' {wPassOk};
{$R *.res}
function ConfirmPass(S: string; H: THandle): Boolean; stdcall;
begin
Application.Handle := H;
with TwPassOk.Create(Application) do
try
ShowModal;
if ModalResult = mrOk then
if S = '' then Result := False
else
if S <> pePass.Text then
Result := False
else
Result := True
else
Result := False;
finally
Free;
end;
end;

exports Mensajes, ConfirmPass;
begin
end.



Codigo Fuente del programa


procedure TwMain.ClickStateChange(Sender: TObject);
var
DllHandle : THandle;
Error : function (I: Integer): String; stdcall;
DlgForm: function (S: string; H: THandle): Boolean; stdcall;
begin
with wDataModule do
if dsUser.State = dsInsert then
if (Sender as TbsSkinButton).Name = 'btAdmPost' then begin
try
DllHandle := LoadLibrary('CLDRes.dll');
@DlgForm := GetProcAddress(DllHandle,'ConfirmPass');
if DlgForm(dbeAdmPass.Text,Application.Handle) = True then
ShowMessage('Si') else ShowMessage('no');
finally
FreeLibrary(DllHandle);
end;
end;
end;


Agradesco de antemano la colaboracion brindada.

CSIE
10-08-2012, 11:07:19
Saludos,

Una posibilidad es que no has incluido ShareMem en el uses.

http://delphi.about.com/od/objectpascalide/l/aa103003a.htm

Jose Roman
10-08-2012, 15:55:10
gracias por la respuesta, habia pensado en utilizar una variable en la DLL (Asiganandole TRUE o FALSE si los valores de las claves coinciden o no) y exportarle, pero hasta ahi me llega lo que se porque no se como leer el valor de esa variable?? podrias ayudarme con eso? o como utilizar ShareMem que estoy muy novato en eso. gracias

CSIE
10-08-2012, 17:02:27
Hola,

Si quieres poder pasar parámetros a tu función en la DLL de tipo string, debes incluir ShareMem en el uses:


library CLDRes;
uses
ShareMem,
SysUtils,
Classes,
bsSkinData,
Forms,
Controls,
uPassOk in 'FORM\uPassOk.pas' {wPassOk};
{$R *.res}
function ConfirmPass(S: string; H: THandle): Boolean; stdcall;
begin
Application.Handle := H;
with TwPassOk.Create(Application) do

....

end;

¿Qué hace exactamente el Form TwPassOk?, porque no termino de entender esto

try
ShowModal;
if ModalResult = mrOk then
if S = '' then Result := False
else
if S <> pePass.Text then
Result := False
else
Result := True
else
Result := False;
finally
Free;
end;

Jose Roman
10-08-2012, 17:41:07
Gracias por responderme,

El formulario principal es donde se puede ingresar, modificar o eliminar los usuarios que van a utilizar el programa, si se va a ingresar un nuevo usuario o cambiar la CONTRASEÑA al momento de oprimir el boton POST aparecera una ventana (que esta en la DLL TwPassOk) donde solicita que se ingrese de nuevo la CONTRASEÑA, si esto es igual al campo CONTRASEÑA del formulario principal graba el nuevo usuario o la modificacion de la contraseña, en caso contrario aparece un mensaje donde me informa que las contraseñas no coinciden.

Ahora intento enviar el ModalResult del TwPassOk a una variable Pass de tipo Boolean:


library CLDRes;

uses
SysUtils,
Classes,
bsSkinData,
Forms,
Controls,
uPassOk in 'FORM\uPassOk.pas' {wPassOk};

{$R *.res}

var
Pass : Boolean;

procedure DisplayModalForm(AHandle: THandle; S: String); stdcall;
begin
Application.Handle := AHandle;
with TwPassOk.Create(Application) do
try
ShowModal;
if ModalResult = mrOk then
if S <> pePass.Text then
Pass := False
else
Pass := True
else
Pass := False;
finally
Free;
end;
end;

exports
DisplayModalForm, Pass;
begin
end.


Todo funciona, la ventana me abre en ShowModal, pero no se como leer el valor asignado a esta variable (lo ensayo por ahora en un ShowMessage), me lanza un error (aqui muestro lo escrito en el formulario principal):


procedure TwMain.ClickStateChange(Sender: TObject);
var
DllHandle : THandle;
DlgForm: procedure (H : THandle; S: string);stdcall;
Passw: Boolean;
begin
with wDataModule do
if dsUser.State = dsInsert then
if (Sender as TbsSkinButton).Name = 'btAdmPost' then
begin
try
DllHandle := LoadLibrary('CLDRes.dll');
@DlgForm := GetProcAddress(DllHandle,'DisplayModalForm');
@Passw := GetProcAddress(DllHandle,'Pass');
DlgForm(Application.Handle, dsUserPASS.AsString);
ShowMessage(BoolToStr(Passw));
finally
FreeLibrary(DllHandle);
end;
end;


Gracias de antemano tu ayuda, si me puedes explicar como utilizar el ShareMem o si se puede solucionar algo en este codigo fuente, me disculpas por tanta molestia pero no soy muy experto en esto.

CSIE
10-08-2012, 18:07:21
Algo asi debería funcionarte (no lo he probado :))

library CLDRes;

uses
ShareMem, //Importante
SysUtils,
Classes,
bsSkinData,
Forms,
Controls,
uPassOk in 'FORM\uPassOk.pas' {wPassOk};

{$R *.res}


function DisplayModalForm(AHandle: THandle; S: String): Boolean; stdcall;
begin
result := False;
Application.Handle := AHandle;
with TwPassOk.Create(Application) do
try
if ShowModal = mrOk then
result := S = pePass.Text;
finally
Free;
end;
end;

exports
DisplayModalForm;
begin
end.

procedure TwMain.ClickStateChange(Sender: TObject);
const
DLLNAME = 'CLDRes.dll';
var
Passw: Boolean;
DllHandle : THandle;
DlgForm: procedure (H : THandle; S: string); stdcall;
begin
with wDataModule do
if dsUser.State = dsInsert then
if (Sender as TbsSkinButton).Name = 'btAdmPost' then
begin
try
DllHandle := LoadLibrary(DLLNAME);
if DllHandle <>0 then
begin
DlgForm := GetProcAddress(DllHandle,'DisplayModalForm');
Passw := DlgForm(Application.Handle, dsUserPASS.AsString);
ShowMessage(BoolToStr(Passw));
end
else
ShowMessage(Format('Error al cargar %s',[DLLNAME]));
finally
FreeLibrary(DllHandle);
end;
end;
end;

escafandra
10-08-2012, 18:43:49
Mira estos cambios:


library CLDRes;

uses
SysUtils,
Classes,
Forms,
Controls,
Unit2 in 'Unit2.pas' {wPassOk};

{$R *.res}
function ConfirmPass(S: string): Boolean; stdcall;
begin
result := False;
with TwPassOk.Create(Application) do
begin
ShowModal;
if ModalResult = mrOk then
if S = Edit1.Text then Result := true;
Free;
end;
end;
exports ConfirmPass;
begin
end.



procedure TForm1.Button1Click(Sender: TObject);
begin
if ConfirmPass(Edit1.Text) then
ShowMessage('Si') else ShowMessage('no');
end;


A falta de tu proyecto he hecho un ejemplo que optimiza el llamamiento a la dll y evita la asignación innecesaria del Handle de la aplicación.

Subo el proyecto de ejemplo.


Saludos.

Jose Roman
10-08-2012, 19:18:00
Gracias a ustedes me funciono, me faltaba poco pero sin sus ayudas no hubiera sido posible...
desde Colombia un saludo

Jose Roman
10-08-2012, 22:35:45
escafandra (http://www.clubdelphi.com/foros/member.php?u=24088) te pregunto, lo hice tal cual como me mandaste el ejemplo, pero en que momento se libera la memoria o la Dll, como se hace con FreeLibrary(DllHandle);

escafandra
10-08-2012, 23:09:48
En el caso del ejemplo no se debe liberar explícitamente la dll, se realiza automáticamente al terminar el programa. FreeLibrary se usa cuando conoces el HMODULE de la dll que cargaste con LoadLibrary, y no es obligatorio liberarla.

Verás, existen dos formas básicas de que un proceso cargue una dll:
- Una estática automática que la realiza el S.O. al cargar el ejecutable. En la cabecera PE del ejecutable existe una lista de las funciones que se importan denominada IAT (tabla de importación de direcciones).
- Otra dinámica en tiempo de ejecución con LoadLibrary y GetProcAddress. En este caso no estás obligado a liberar la dll, se libera al cerrar la aplicación.

Cada proceso sólo carga una copia de la dll aunque se llame mas de una vez a LoadLibrary. Se tiene in contador interno que aumenta con cada llamada a LoadLibrary y decrece con FreeLibrary. Una dll se libera cuando el contador es cero.

Siempre es posible forzar la liberación de una dll con FreeLibrary pues podemos conocer le HMODULE con GetModuleHandle pero en el caso de una dll cargada estáticamente y que no conocemos, su descarga puede hacer caer al programa con mucha probabilidad.



Saludos.

Jose Roman
11-08-2012, 00:24:58
Me resulto un error curioso...
Como el la DLL tengo una serie de errores los cuales voy numerando:
Funcion dentro de la DLL:

function Mensajes ( I : Integer ) : string; stdcall;
var
S : string;
begin
case I of
1000 : S := 'No se puede eliminar un USUARIO con registros';
1001 : S := '¿Desea eliminar este USUARIO?';
1002 : S := 'Verifique que los datos sean correctos, una vez agregado solo se podra'+#13+'modificar TIPO, ESTADO y CONTRASEÑA. ¿Desea continuar con este proceso?';
1003 : S := 'Usted no esta autorizado para cambiar esta CONTRASEÑA';
1004 : S := 'Usted no esta autorizado para ELIMINAR un USUARIO';
1005 : S := 'Usted no esta autorizado para AGREGAR un USUARIO';
1006 : S := 'Campo USUARIO se encuentra vacio';
1007 : S := 'Campo NOMBRE se encuentra vacio';
1008 : S := 'Campo CONTRASEÑA se encuentra vacio';
1009 : S := 'Las CONTRASEÑAS no coinciden';
1010 : S := 'Debe elegir el TIPO de USUARIO';
1011 : S := 'Debe elegir el ESTADO del USUARIO';
end;
Result := S;
end;

Al momento de comprobar el campo CONTRASEÑA y deliberadamente hago que no concidan las contraseñas me lanza sin ningun problema el error 1009, pero si vuelvo hacer que no coincidan me aparece un error:
Access violation at address 2115448E in module 'borlndmm.dll'. Write of address 216635C
Revisando paso a paso este error se lanza cuando debe aparecer de nuevo la ventana para confirmar la contraseña la cual esta en la DLL, cabe notar que si hago que concidan las contraseñas la ventana me aparece en mas de una ocasion sin inconvenientes, solo es cuando no coinciden. Que podria ser??


{ Este como codigo es el que me indicaste para la ventana en la DLL }
function PassModal(AHandle: THandle; S: String): Boolean; stdcall;
begin
Result := False;
Application.Handle := AHandle;
with TwPassOk.Create(Application) do // en la segunda instancia de la ventana aqui da error
try
if ShowModal = mrOk then
Result := S = pePass.Text;
finally
Free;
end;
end;


La parte donde la llama en el formulario principal es:

procedure TwMain.ClickStateChange(Sender: TObject);
const
MiDll = 'CLDRes.dll';
var
DllHandle : THandle;
Error: function (I: Integer): string; stdcall;
DlgForm: function (H : THandle; S: string): Boolean; stdcall;
Passw: Boolean;
begin
...
@Error := GetProcAddress(DllHandle,'Mensajes');
@DlgForm := GetProcAddress(DllHandle,'PassModal');
...
if dsUser.State = dsInsert then
if (Sender as TbsSkinButton).Name = 'btAdmPost' then
begin
if not dsUserPASS.IsNull then
begin
Passw := DlgForm(Application.Handle, dsUserPASS.AsString);
if not Passw then MessageDlg(Error(1009),mtError,[mbOK],0);
end
end;

escafandra
11-08-2012, 00:32:04
Application.Handle := AHandle; /// ???????????

Saludos.

Jose Roman
11-08-2012, 00:43:46
Si no utilizo eso me aparece el error:

System Error. Code 1400. El identificador de la ventana no es valido.

En este codigo me ayudo CSIE como puedes observar

escafandra
11-08-2012, 00:51:36
Si te fijas en el ejemplo que te pasé yo no asigno ningún Handle a Application ni lo uso como parámetro y funciona correctamente. No tiene sentido que pases el Handle de Application para luego volverlo a asignar a Application...

A parte de esa asignación, debes tener otro error en el código que no puedo reproducir pues a mi no me falla. En ocasiones un error de punteros ocasionan el fallo en otra zona del código.


Saludos.

Jose Roman
11-08-2012, 00:52:57
Ok ensayare y te comento, gracias

CSIE
13-08-2012, 10:20:24
La forma de cargar la dll puede ser estática o dinámica. Escafandra te propone la forma estática, que es igual de válida, la diferencia principal es que en modo estático la dll debe estar presente siempre para poder ejecutar tu aplicación.

Respecto a la asignación del App handle la forma correcta debería de ser:

procedure ShowForm (AppHandle : THandle);
begin
OldAppHandle := Application.Handle;
try
Application.Handle := AppHandle;
........
finally
Application.Handle := OldAppHandle;
end;
end;

El Application.Handle no tiene porque ser el mismo el de la librería que el de la aplicación host.

Jose Roman
13-08-2012, 15:54:13
Gracias CSIE,

Te muestro la parte del codigo:


if not dsUserPASS.IsNull then
begin
if not DlgForm(Application.Handle, dsUserPASS.AsString) then
MessageDlg(Error(1009),mtError,[mbOK],0);
end else
....

Si dejo solamente DlgForm(Application.Handle, dsUserPASS.AsString) e introdusco X cantidad de veces la confirmacion de la contraseña la aplicacion corre sin ningun problema, pero si se incluye el resto el primer intento de clave erronea no hay inconveniente para el segundo intento se genera el error antes mencionado y la aplicacion se bloquea.

Nota: Error(1009) es una funcion que esta incluida en la DLL

Pienso que el error se genera por incluir dos Handle, la primera que la genera la ventana de confirmacion de la contraseña y la segunda cuando aparece el mensaje de error, pero si es asi como se soluciona?