Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   DLLs con nombre desconocido (https://www.clubdelphi.com/foros/showthread.php?t=33802)

ixMike 17-07-2006 19:07:49

DLLs con nombre desconocido
 
Hace cosa de una semana (o un poco más) pregunté cómo llamar a funciones de DLLs cuyo nombre desconozco. La respuesta de seoane me sirvió mucho, pero no me lo solucionó del todo, ya que una de las funciones a las que llamo mantiene una ventana abierta, por lo que no puedo llamar a FreeLibrary en la misma función, sino de otra forma.
Tras hacer varias pruebas y ojear el archivo MAPI.PAS que se encuentra en DELPHI\Source\RTL\WIN\ (al menos en la versión que tengo) logré llegar a una solución.
Primero en type declaro los tipos de función que voy a necesitar.
Código Delphi [-]
  {...}
type
  TShowCursorProc = function(bShow: BOOL) : HResult; stdcall;
  TForm1 = class (TForm)
  {...}
Después coloco una variable global para el manejador de la DLL, y todas las variables necesarias para almacenar todas las funciones. Todo tiene que estar inicializado.
Código Delphi [-]
  {...}
var
  Form1: TForm1;
  HandleDLL: THandle = 0;
  FuncionDLL: TShowCursorProc = nil;
  {...}
Creo un procedimiento que inicialice la DLL, a la que puedo llamar por ejemplo cuando cree la ventana, y otra que la libere cuando la cierre.
Código Delphi [-]
{...}
implementation
Procedure InitDLL(dll: string);
begin
If HandleDLL=0 then HandleDLL:=LoadLibrary(PChar(Dll));
end;
Procedure FreeDLL(var Hdll: THandle);
begin
If Hdll<>0 then FreeLibrary(Hdll);
end;
Procedure TForm1.FormCreate(Sender: TObject);
begin
InitDLL('user32.dll');
end;
Procedure TForm1.FormClose(Sender: TObject);
begin
FreeDLL(HandleDLL);
end;
{...}
Ya por último declaro todas las funciones que necesite.
Código Delphi [-]
Procedure MostrarCursor(mostrar: Boolean);
begin
If HandleDLL <> 0 then @FuncionDLL:=GetProcAddress(HandleDll,'ShowCursor');
If @FuncionDLL <> nil then FuncionDLL(mostrar);
end;
También quiero aclarar que en la respuesta de seoane se produce un error de acceso a memoria después de llamar a FreeLibrary. Es una cosa muy rara, porque después puse la línea ShowMessage('Se liberó') y el mensaje se mostraba, y acto seguido salía el error.
Bueno, aunque parezca que lo he solucionado sigo teniendo una gran duda. Y es que HandleDLL almacena el manejador de una libraría, pero yo voy a tener varias librerías, y todas van a tener que estar cargadas a la vez, por lo que no puedo liberar una y cargar otra. Como los nombres de las librerías los almaceno en un TStringList se me ha ocurrido crear un objeto que almacene un array de integers para almacenar en cada uno de ellos el Handle de cada librería.
Si me sale bien lo pondré aquí, pero si alguien me puede ayudar agradeceré muchísimo la respuesta.

ixMike 17-07-2006 19:16:01

Lo Tengooooooooooooooooooooooooooo
 
¡¡¡Lo logré!!!
Primero creé un tipo de objeto parecido al TStrinList, pero que almacena integers en vez de strings. Aquí está el código de la unidad:
Código Delphi [-]
unit Integers;
interface
uses
  Classes;
type
  TIntArray = array [0..0] of Integer;
  PIntArray = ^TIntArray;
  TIntegers = class(TObject)
    protected
    FIntegers: PIntArray;
    FCount: Integer;
    Function GetIntegers(Index: Integer): Integer;
    Procedure SetIntegers(Index: Integer; valor: Integer);
    
    public
    Procedure Add(I: Integer);
    Procedure Clear;
    Procedure Delete(Index: Integer);
    Function IndexOf(I: Integer): Integer;
    Property Count: Integer read FCount;
    Property Values[Index: Integer]: Integer read GetIntegers write SetIntegers;
    Constructor Create;
    Destructor Destroy; override;
    end;
implementation
Constructor TIntegers.Create;
begin
inherited;
GetMem(FIntegers, 0);
FCount:=0;
end;
Destructor TIntegers.Destroy;
begin
FreeMem(FIntegers, FCount * SizeOf(Integer));
inherited;
end;
Procedure TIntegers.Add(I: Integer);
begin
Inc(FCount);
ReallocMem(FIntegers, FCount * SizeOf(Integer));
FIntegers^[FCount-1]:=I;
end;
Procedure TIntegers.Delete(Index: Integer);
var
n: integer;
begin
If FCount=0 then exit;
for n:=Index+1 to FCount-1 do FIntegers^[n-1]:=FIntegers^[n];
Dec(FCount);
ReallocMem(FIntegers, FCount * SizeOf(Integer));
end;
Procedure TIntegers.Clear;
begin
FCount:=0;
ReallocMem(FIntegers, 0);
end;
Function TIntegers.GetIntegers(Index: Integer): Integer;
begin
If Index<=FCount then Result:=FIntegers^[Index];
end;
Function TIntegers.IndexOf(I: Integer): Integer;
var
n: integer;
begin
Result:=-1;
If FCount=0 then exit;
For n:=0 to FCount-1 do If FIntegers^[n]=I then
  begin
  Result:=n;
  exit;
  end;
end;
Procedure TIntegers.SetIntegers(Index: Integer; Valor: Integer);
begin
If index<=FCount then FIntegers^[Index]:=Valor;
end;
end.
Después creé un par de DLLs de ejemplo, con tres funciones que devuelven el nombre de la misma, la versión y el nombre del programador.
Para comprobar que funcionaban, en un nuevo proyecto coloqué un TListView con cuatro columnas (Archivo, nombre, versión, autor). En ella se tendría que listar el nombre de las DLLs encontradas en el directorio de la aplicación y el resultado de las funciones GetName, GetVersion y GetAuthor de las librerías de ejemplo que hice.
Además, las cargo al inicio del programa y las libero al final. Aquí está el códido del programa:
Código Delphi [-]
unit PruebaMain;
interface
uses
  Windows, Forms, ComCtrls, Controls, Classes, SysUtils, Integers;
type
  TGetVersionFunc = function : String; stdcall;
  TGetNameFunc = function : String; stdcall;
  TGetAuthorFunc = function : String; stdcall;
  TForm1 = class(TForm)
    ldlls: TListView;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  ints: TIntegers;
  vGetName: TGetNameFunc = nil;
  vGetVersion: TGetVersionFunc = nil;
  vGetAuthor: TGetAuthorFunc = nil;
implementation
{$R *.DFM}
Function LiberarLibreria(manejador: Integer): integer;
begin
if manejador<>0 then FreeLibrary(manejador);
Result:=Manejador;
end;
Function CargarLibreria(dll: string): Integer;
begin
Result:=LoadLibrary(PChar(dll));
end;
Function GetName(h: integer): String;
begin
@vGetName:=GetProcAddress(h,'GetName');
if @vGetName <> nil then Result:=vGetName else result:='Error loading library';
end;
Function GetVersion(h: integer): String;
begin
@vGetVersion:=GetProcAddress(h,'GetVersion');
if @vGetVersion <> nil then Result:=vGetVersion else result:='Error loading library';
end;
Function GetAuthor(h: integer): String;
begin
@vGetAuthor:=GetProcAddress(h,'GetAuthor');
if @vGetAuthor <> nil then Result:=vGetAuthor else result:='Error loading library';
end;
procedure TForm1.FormCreate(Sender: TObject);
var
encontrado, n: integer;
archivo: TSearchRec;
nodo: TListItem;
begin
LDLLs.Items.Clear;
// Busca archivos DLL en el mismo directorio
encontrado:=FindFirst(ExtractFilePath(ParamStr(0))+'*.dll',faAnyFile,archivo);
while encontrado=0 do
  begin
  nodo:=ldlls.Items.Add;
  nodo.Caption:=archivo.name;
  encontrado:=FindNext(archivo);
  end;
FindClose(archivo);
////////////
If LDLLs.Items.Count=0 then exit; //Si no ha encontrado DLLs sale
// Asignar handles
ints:=TIntegers.Create;
for n:=0 to LDlls.ITems.Count-1 do
 ints.Add(CargarLibreria(Ldlls.Items[n].caption));
///////////
// Ejecutar funciones
for n:=0 to LDlls.ITems.Count-1 do
  begin
  Ldlls.Items[n].Subitems.Clear;
  Ldlls.Items[n].Subitems.Add(GetName(ints.values[n]));
  Ldlls.Items[n].Subitems.Add(GetVersion(ints.values[n]));
  Ldlls.Items[n].Subitems.Add(GetAuthor(ints.values[n]));
  end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
n: integer;
begin
for n:=0 to ints.Count-1 do ints.values[n]:=LiberarLibreria(ints.values[n]);
ints.Free;
end;
end.
Espero que esto le sirva de ayuda a alguien.

dec 17-07-2006 19:20:25

Hola,

Je, je, je... bueno. Seguro que lo que dices le sirve de ayuda a más de una persona, estoy seguro. A mí lo que me llamó la atención es que planteases un problema y dieras con la solución (y qué solución) en menos de un minuto.

¡Todo un record de capacidad de respuesta! ¡Cuéntanos cómo lo haces! :D :D :D

Y, en todo caso, gracias por compartir la información con nosotros.

Quiero terminar diciendo que existe un apartado de Trucos en el ClubDelphi en donde tal vez podrías guardar este Hilo, si bien con algunos cambios, para que quedara allí también a disposición de a quien le pudiera hacer menester. ;)

ixMike 17-07-2006 19:28:33

un poquito de trampa
 
:rolleyes: La verdad es esta::rolleyes:

No tengo Internet en casa:( . Voy alguna tarde a un ciber:D .
La pregunta me la planteé en un cierto momento, y estuve como una hora seguida sin parar de teclear, y corregir :mad: ...

Ahora he puesto la pregunta y la respuesta por separado para si alguien ve el título, que no lo vea sin respuestas:cool: .

Buena observación ;)

maeyanes 17-07-2006 19:52:24

Veo que usas un TListView para obtener la lista de DLL's. Como la clase TListItem tiene una propiedad llamada Data, ahí podrías guardar el Handle de la DLL, así te evitas crear la clase TIntegers.

Aquí te pongo parte de tu código usando la propiedad Data de TListItem:

Código Delphi [-]
procedure TForm1.FormCreate(Sender: TObject);
var
  encontrado, n: integer;
  archivo: TSearchRec;
  nodo: TListItem;

begin
  LDLLs.Items.Clear;
  // Busca archivos DLL en el mismo directorio 
  encontrado:=FindFirst(ExtractFilePath(ParamStr(0))+'*.dll',faAnyFile,archivo);
  while encontrado=0 do
  begin
    nodo := ldlls.Items.Add;
    nodo.Caption := archivo.name;
    nodo.Data := Pointer(CargarLibreria(archivo.Name)); // Guardo el Handle del DLL.
    encontrado:=FindNext(archivo);
  end;
  FindClose(archivo);

  // Ejecutar funciones
  for n:=0 to LDlls.ITems.Count-1 do
  begin
    Ldlls.Items[n].Subitems.Clear;
    Ldlls.Items[n].Subitems.Add(GetName(Integer(LDlls.Items[i].Data)));
    Ldlls.Items[n].Subitems.Add(GetVersion(Integer(LDlls.Items[i].Data)));
    Ldlls.Items[n].Subitems.Add(Integer(LDlls.Items[i].Data)));
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  n: integer;

begin
  for n := 0 to LDlls.Count - 1 do
    LiberarLibreria(Integer(LDlls.Items[i].Data))
end;

Saludos...

Neftali [Germán.Estévez] 18-07-2006 09:36:29

Cita:

Empezado por ixMike
Ahora he puesto la pregunta y la respuesta por separado para si alguien ve el título, que no lo vea sin respuestas.

Esa es una buena costumbre de la cual muchos deberían tomar nota.

Gracias.


La franja horaria es GMT +2. Ahora son las 03:16:09.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi