2012-03-06 26 views
5

Estoy trabajando en algo que carga dinámicamente DLL especialmente formulados. Necesito poder verificar la DLL y asegurarme de que todas las funciones esperadas existen antes de considerar usar esta DLL. Si le faltan algunas funciones determinadas, no debería intentar cargarlo. Sé que podría intentar llamar a una de las funciones y ver si hay una excepción, pero vería errores en el modo de depuración.¿Cómo verificar una DLL si existe una función?

¿Cómo debo consultar una DLL si existe una función? Me gustaría comprobarlo antes de Lo cargo (usando LoadLibrary) pero supongo que está bien si tengo que cargarlo para realizar esta comprobación también.

ACTUALIZACIÓN

He aceptado la respuesta de David a continuación, y pensé que había puesto mi código final para mostrar todo el proceso. Lo convertí en una función que devolvía un Bool, si tenía éxito o no, limpiaba un poco el código, y añadía otra función en la parte inferior que usa esta para verificar cada nombre uno por uno.

Decidí usar este método en lugar de leer GetProcAddress porque me ayudará en el futuro con otras cosas.

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; 

Respuesta

9

Si tiene el control de los archivos DLL y no desea cargarlos para verificar la capacidad, puede usar el recurso de versión para indicar la capacidad. Esto requeriría que la aplicación de host tenga conocimiento de cuál era la versión mínima admitida para cada característica de DLL opcional. Puede leer la versión de los recursos a bajo costo sin cargar la DLL.

Es perfectamente posible, y bastante simple, obtener la lista de funciones exportadas por un archivo DLL con la carga en su proceso con LoadLibrary. La biblioteca del sistema dbghelp.dll proporciona servicios para hacer eso. Sin embargo, sospecho que es excesivo para su situación.

Si no es un problema cargar y descargar la DLL, entonces GetProcAddress es probablemente la solución preferida. Si hay alguna razón por la cual necesita evitar cargar el archivo DLL para verificar la capacidad, use el recurso de versión para inferir la capacidad. Si necesita hacer esto con DLL heredados que no tienen un recurso de versión significativa, entonces use dbghelp.dll para encontrar las funciones exportadas.


En aras de la exhaustividad, he aquí algo de código para leer todos los símbolos exportados desde un archivo DLL, sin tener que cargar con 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

Parece prometedor, y cuando regrese a mi escritorio, lo intentaré: D Esta puede ser la manera de hacerlo, acepté demasiado pronto ... –

+0

Bueno, este es un código probado. Pero, ¿qué te impide usar LoadLibrary y GetProcAddress? Sabiendo que podría ayudar. –

+0

Porque de hecho tendré que averiguar cómo enumerar estas funciones para este proyecto de todos modos :) –

10

usted tiene que utilizar LoadLibrary, y luego usar GetProcAddress para cada función que desea comprobar la existencia de. Realmente no hay otra opción razonable (a menos que existan razones específicas para evitar el `LoadLibrary). Como su intención parece ser solo verificar si las funciones están presentes y nada más, LoadLibrary y GetProcAddress son los medios más simples para hacerlo; puede hacer todo el trabajo en muy pocas líneas de código, y la comprobación de errores es extremadamente simple y directa.

+0

+1 y Aceptado (en 6 minutos cuando me deja) –

+4

Usted no haría una "búsqueda binaria" como tal. El formato PE está bien definido y es bastante fácil leer la tabla de funciones exportadas. Sin embargo, hay una gran diferencia entre hacerlo y llamar a LoadLibrary. Este último realiza correcciones y ejecuta código en DllMain. Es concebible que quieras evitar eso, aunque bastante improbable. Siento tu afirmación de que "Hay realmente no hay otra opción" para ser demasiado fuerte. –

+2

La pregunta específicamente sobre la comprobación de un archivo DLL para la existencia de un determinado conjunto de funciones, lo que significa que la prevención de la reparación no era una consideración (que también incluí en mi determinación de qué responder). –

Cuestiones relacionadas