2012-03-06 4 views
5

Я работаю над тем, что динамически загружает специально разработанные DLL. Мне нужно иметь возможность проверить DLL и убедиться, что все ожидаемые функции существуют до того, как я подумаю об использовании этой DLL. Если у него отсутствуют некоторые определенные функции, я не должен пытаться его загрузить. Я знаю, что могу попытаться вызвать одну из функций и посмотреть, есть ли какое-то исключение, но я увижу ошибки в режиме отладки.Как проверить DLL, если существует функция?

Как мне проверить проверку DLL, если существует функция? Я хотел бы проверить его до Я загружаю его (используя LoadLibrary), но я думаю, что это нормально, если мне нужно загрузить его, чтобы выполнить эту проверку тоже.

UPDATE

Я принял ответ Дэвида ниже, и думал, что я отправлю свой окончательный код, чтобы показать весь процесс. Я превратил его в функцию, возвращающую Bool, независимо от того, удалось это или нет, немного очистил код и добавил еще одну функцию внизу, которая использует эту, чтобы проверять каждое имя по одному.

Я решил использовать этот метод вместо чтения GetProcAddress, потому что это поможет мне в будущем с другими вещами.

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; 

ответ

9

Если вы контролируете библиотеки DLL, и вы не хотите их загружать, чтобы проверить возможность, вы можете использовать ресурс версии, чтобы указать возможность. Для этого потребуется, чтобы хост-приложение знало, какая минимальная поддерживаемая версия для каждой дополнительной функции DLL. Вы можете быстро прочитать ресурс версии без загрузки DLL.

Совершенно возможно и довольно просто получить список функций, экспортируемых DLL, с загрузкой его в ваш процесс с помощью LoadLibrary. Системная библиотека dbghelp.dll предоставляет услуги для этого. Тем не менее, я подозреваю, что это слишком много для вашей ситуации.

Если не загружать и выгружать DLL, то GetProcAddress, вероятно, является предпочтительным решением. Если есть какая-то причина, по которой вам нужно не загружать DLL, чтобы проверить возможность, используйте ресурс версии для вывода возможностей. Если вам нужно сделать это с помощью устаревших DLL, у которых нет значимого ресурса версии, используйте dbghelp.dll, чтобы найти экспортированные функции.


Для полноты картины, вот некоторый код, чтобы прочитать все экспортируемые символы от DLL, не загружая его с 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

Выглядит многообещающе, и когда я вернусь к своему столу, я дам ему пойти: D Это может быть способ пойти на самом деле, я принял слишком рано ... –

+0

Ну, это проверенный и проверенный код. Но что мешает вам использовать LoadLibrary и GetProcAddress? Зная, что это может помочь. –

+0

Потому что на самом деле мне нужно будет выяснить, как в любом случае перечислить эти функции для этого проекта :) –

10

Вы должны использовать LoadLibrary, а затем использовать GetProcAddress для каждой функции, которую вы хотите, чтобы проверить наличие для. Там действительно нет другого разумного выбора (если нет особых причин, которых вам нужно избегать `LoadLibrary). Поскольку ваше намерение заключается в том, чтобы просто проверить, присутствуют ли функции и не более того, LoadLibrary и GetProcAddress - это самое простое средство для этого; вы можете выполнять всю работу в очень немногих строках кода, а проверка ошибок чрезвычайно проста и понятна.

+0

+1 и принят (через 6 минут, когда это позволит мне) –

+4

Вы бы не выполнили «двоичный поиск» как таковой. Формат PE хорошо определен, и достаточно легко прочитать таблицу экспортируемых функций. Существует большая разница между этим и вызовом LoadLibrary. Последний выполняет fixups и выполняет код в DllMain. Вполне возможно, что вы захотите избежать этого, хотя и маловероятного. Я чувствую ваше утверждение, что «есть действительно нет другого выбора», чтобы быть слишком сильным. –

+2

Вопрос, заданный конкретно о проверке DLL для существования определенного набора функций, что означает, что устранение исключений не было рассмотрением (которое я также включил в свое определение, что ответить). –

Смежные вопросы