Как проверить DLL, если функция существует? - PullRequest
5 голосов
/ 06 марта 2012

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

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

ОБНОВЛЕНИЕ

Я принял ответ Дэвида ниже и решил опубликовать свой окончательный код, чтобы показать весь процесс.Я превратил его в функцию, возвращающую 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;

Ответы [ 2 ]

10 голосов
/ 06 марта 2012

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

9 голосов
/ 06 марта 2012

Если вы контролируете библиотеки 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;
...