2012-03-06 13 views
5

Sto lavorando a qualcosa che carica dinamicamente DLL appositamente formulate. Devo essere in grado di controllare la DLL e assicurarsi che tutte le funzioni previste esistano prima di considerare l'utilizzo di questa DLL. Se mancano alcune funzioni, non dovrei provare a caricarlo. So che potrei provare a chiamare una delle funzioni e vedere se c'è un'eccezione, ma vedrei gli errori in modalità di debug.Come controllare una DLL se esiste una funzione?

Come devo verificare la presenza di una DLL se esiste una funzione? Vorrei controllarlo prima del Lo carico (usando LoadLibrary) ma suppongo che sia OK se devo caricarlo per eseguire anche questo controllo.

UPDATE

ho accettato la risposta di David qui sotto, e ho pensato di postare il mio codice finale per mostrare l'intero processo. L'ho trasformato in una funzione che restituisce un Bool, che sia riuscita o meno, ha pulito un po 'il codice e ha aggiunto un'altra funzione nella parte inferiore che utilizza questo per controllare ciascun nome uno per uno.

Ho deciso di utilizzare questo metodo invece di leggere GetProcAddress perché mi aiuterà in futuro con altre cose.

type 
  PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS; 
  PIMAGE_EXPORT_DIRECTORY = ^IMAGE_EXPORT_DIRECTORY; 

function ImageNtHeader(Base: Pointer): PIMAGE_NT_HEADERS; stdcall; 
    external 'dbghelp.dll'; 
function ImageRvaToVa(NtHeaders: Pointer; Base: Pointer; Rva: ULONG; 
    LastRvaSection: Pointer): Pointer; stdcall; external 'dbghelp.dll'; 

function ExportedFunctionNames(const ImageName: string; NamesList: TStrings): Bool; 
var 
    i: Integer; 
    FileHandle: THandle; 
    ImageHandle: THandle; 
    ImagePointer: Pointer; 
    Header: PIMAGE_NT_HEADERS; 
    ExportTable: PIMAGE_EXPORT_DIRECTORY; 
    NamesPointer: Pointer; 
    Names: PAnsiChar; 
    NamesDataLeft: Integer; 
begin 
    Result:= False; 
    NamesList.Clear; 
    FileHandle:= CreateFile(PChar(ImageName), GENERIC_READ, FILE_SHARE_READ, 
    nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 
    if FileHandle = INVALID_HANDLE_VALUE then Exit; 
    try 
    ImageHandle:= CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil); 
    if ImageHandle = 0 then Exit; 
    try 
     ImagePointer:= MapViewOfFile(ImageHandle, FILE_MAP_READ, 0, 0, 0); 
     if not Assigned(ImagePointer) then Exit; 
     try 
     Header:= ImageNtHeader(ImagePointer); 
     if not Assigned(Header) then Exit; 
     if Header.Signature <> $00004550 then Exit; // "PE\0\0" as a DWORD. 
     ExportTable:= ImageRvaToVa(Header, ImagePointer, 
      Header.OptionalHeader.DataDirectory[0].VirtualAddress, nil); 
     if not Assigned(ExportTable) then Exit; 
     NamesPointer:= ImageRvaToVa(Header, ImagePointer, 
      Cardinal(ExportTable.AddressOfNames), nil); 
     if not Assigned(NamesPointer) then Exit; 
     Names:= ImageRvaToVa(Header, ImagePointer, Cardinal(NamesPointer^), nil); 
     if not Assigned(Names) then Exit; 
     NamesDataLeft:= Header.OptionalHeader.DataDirectory[0].Size; 
     for i:= 0 to ExportTable.NumberOfNames - 1 do begin 
      NamesList.Add(Names); 
      while (Names^ <> chr(0)) and (NamesDataLeft > 0) do begin 
      Inc(Names); 
      Dec(NamesDataLeft); 
      end; 
      Inc(Names); 
     end; 
     Result:= True; 
     finally 
     UnmapViewOfFile(ImagePointer); 
     end; 
    finally 
     CloseHandle(ImageHandle); 
    end; 
    finally 
    CloseHandle(FileHandle); 
    end; 
end; 

function IsMyDLL(const Filename: String): Bool; 
var 
    H: THandle; 
    L: TStringList; 
    function InList(const Func: String): Bool; 
    begin 
    Result:= L.IndexOf(Func) >= 0; 
    end; 
begin 
    Result:= False; 
    L:= TStringList.Create; 
    try 
    if ExportedFunctionNames(Filename, L) then begin 
     Result:=//Names of functions which need to exist 
     InList('GetName') and 
     InList('GetDescription') and 
     InList('GetVersion') and 
     InList('Start') and 
     InList('Stop'); 
    end; 
    finally 
    L.Free; 
    end; 
end; 

risposta

9

Se si ha il controllo delle DLL e non si desidera caricarle per verificare la capacità, è possibile utilizzare la risorsa versione per indicare la capacità. Ciò richiederebbe all'app host di conoscere la versione minima supportata per ciascuna funzione DLL facoltativa. È possibile leggere la risorsa versione a basso costo senza caricare la DLL.

È perfettamente possibile, e piuttosto semplice, ottenere l'elenco di funzioni esportate da una DLL con il caricamento nel processo con LoadLibrary. La libreria di sistema dbghelp.dll fornisce servizi per farlo. Tuttavia, sospetto che sia eccessivo per la tua situazione.

Se non è un problema caricare e scaricare la DLL, GetProcAddress è probabilmente la soluzione preferita. Se c'è qualche motivo per cui è necessario evitare di caricare la DLL per verificare la capacità, utilizzare la risorsa della versione per dedurre la capacità. Se è necessario eseguire questa operazione con DLL legacy che non dispongono di una risorsa versione significativa, utilizzare dbghelp.dll per trovare le funzioni esportate.


Per ragioni di completezza, ecco qualche codice per leggere tutti i simboli esportati da una DLL, senza complesse LoadLibrary.

type 
    PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS; 
    PIMAGE_EXPORT_DIRECTORY = ^IMAGE_EXPORT_DIRECTORY; 

function ImageNtHeader(Base: Pointer): PIMAGE_NT_HEADERS; stdcall; external 'dbghelp.dll'; 
function ImageRvaToVa(NtHeaders: Pointer; Base: Pointer; Rva: ULONG; LastRvaSection: Pointer): Pointer; stdcall; external 'dbghelp.dll'; 

procedure ImageExportedFunctionNames(const ImageName: string; NamesList: TStrings); 
var 
    i: Integer; 
    FileHandle: THandle; 
    ImageHandle: THandle; 
    ImagePointer: Pointer; 
    Header: PIMAGE_NT_HEADERS; 
    ExportTable: PIMAGE_EXPORT_DIRECTORY; 
    NamesPointer: Pointer; 
    Names: PAnsiChar; 
    NamesDataLeft: Integer; 
begin 
    //NOTE: our policy in this procedure is to exit upon any failure and return an empty list 

    NamesList.Clear; 

    FileHandle := CreateFile(
    PChar(ImageName), 
    GENERIC_READ, 
    FILE_SHARE_READ, 
    nil, 
    OPEN_EXISTING, 
    FILE_ATTRIBUTE_NORMAL, 
    0 
); 
    if FileHandle=INVALID_HANDLE_VALUE then begin 
    exit; 
    end; 
    Try 
    ImageHandle := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil); 
    if ImageHandle=0 then begin 
     exit; 
    end; 
    Try 
     ImagePointer := MapViewOfFile(ImageHandle, FILE_MAP_READ, 0, 0, 0); 
     if not Assigned(ImagePointer) then begin 
     exit; 
     end; 

     Try 
     Header := ImageNtHeader(ImagePointer); 
     if not Assigned(Header) then begin 
      exit; 
     end; 
     if Header.Signature<>$00004550 then begin // "PE\0\0" as a DWORD. 
      exit; 
     end; 

     ExportTable := ImageRvaToVa(Header, ImagePointer, Header.OptionalHeader.DataDirectory[0].VirtualAddress, nil); 
     if not Assigned(ExportTable) then begin 
      exit; 
     end; 

     NamesPointer := ImageRvaToVa(Header, ImagePointer, Cardinal(ExportTable.AddressOfNames), nil); 
     if not Assigned(NamesPointer) then begin 
      exit; 
     end; 
     Names := ImageRvaToVa(Header, ImagePointer, Cardinal(NamesPointer^), nil); 
     if not Assigned(Names) then begin 
      exit; 
     end; 

     NamesDataLeft := Header.OptionalHeader.DataDirectory[0].Size; 
     for i := 0 to ExportTable.NumberOfNames-1 do begin 
      NamesList.Add(Names); 
      // Locate the next name 
      while (Names^<>chr(0)) and (NamesDataLeft>0) do begin 
      inc(Names); 
      dec(NamesDataLeft); 
      end; 
      inc(Names); 
     end; 
     Finally 
     UnmapViewOfFile(ImagePointer); // Ignore error as there is not much we could do. 
     End; 
    Finally 
     CloseHandle(ImageHandle); 
    End; 
    Finally 
    CloseHandle(FileHandle); 
    End; 
end; 
+0

Sembra promettente, e quando torno alla mia scrivania faccio un tentativo: D Questa potrebbe essere la strada giusta, ho accettato troppo presto ... –

+0

Bene, questo è un codice provato e testato. Ma cosa ti impedisce di utilizzare LoadLibrary e GetProcAddress? Sapendo che potrebbe aiutare. –

+0

Perché in realtà dovrò capire come elencare queste funzioni alla fine per questo progetto comunque :) –

10

Devi usare LoadLibrary, e quindi utilizzare GetProcAddress per ogni funzione che si desidera verificare l'esistenza per. Non c'è davvero altra scelta ragionevole (a meno che non vi siano ragioni specifiche per evitare `LoadLibrary). Poiché il tuo intento sembra essere solo quello di verificare se le funzioni sono presenti e nient'altro, LoadLibrary e GetProcAddress sono i mezzi più semplici per farlo; puoi fare tutto il lavoro in pochissime righe di codice e il controllo degli errori è estremamente semplice e diretto.

+0

+1 e Accettato (in 6 minuti quando lo lascerà) –

+4

Non farei una "ricerca binaria" in quanto tale. Il formato PE è ben definito ed è abbastanza facile leggere la tabella delle funzioni esportate. C'è una grande differenza tra farlo e chiamare LoadLibrary però. Quest'ultimo esegue correzioni ed esegue codice in DllMain. È concepibile che tu voglia evitarlo, anche se piuttosto improbabile. Sento la tua affermazione che "C'è davvero la scelta di " troppo forte. –

+2

La domanda specificava sul controllo di una DLL per l'esistenza di un certo insieme di funzioni, il che significa che l'eliminazione delle correzioni non era una considerazione (che includevo anche nella mia determinazione su cosa rispondere). –