【问题标题】:How to check a DLL if a function exists?如果函数存在,如何检查 DLL?
【发布时间】:2012-03-23 11:36:50
【问题描述】:

我正在研究动态加载专门制定的 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;

【问题讨论】:

    标签: delphi dll delphi-xe2 loadlibrary


    【解决方案1】:

    您必须使用LoadLibrary,然后对要检查其存在的每个函数使用GetProcAddress。真的没有其他合理的选择(除非有特定原因需要避免`LoadLibrary)。由于您的意图似乎只是检查功能是否存在,仅此而已,LoadLibrary 和 GetProcAddress 是最简单的方法;您只需几行代码即可完成所有工作,而且错误检查非常简单明了。

    【讨论】:

    • 你不会做这样的“二分搜索”。 PE 格式定义明确,很容易阅读导出函数表。这样做和调用 LoadLibrary 之间有很大的不同。后者执行修复并在 DllMain 中执行代码。可以想象你想避免这种情况,尽管不太可能。我觉得你所说的“真的别无选择”太强了。
    • 该问题专门询问了有关检查 DLL 是否存在某组函数的问题,这意味着不考虑避免修复(我在确定要回答的内容时也包括在内)。
    【解决方案2】:

    如果您可以控制 DLL,并且不想加载它们以检查功能,那么您可以使用版本资源来指示功能。这将要求主机应用程序了解每个可选 DLL 功能的最低支持版本。您可以在不加载 DLL 的情况下廉价地读取版本资源。

    使用 LoadLibrary 将 DLL 导出的函数列表加载到您的进程中是完全可能的,而且相当简单。 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;
    

    【讨论】:

    • 看起来很有希望,当我回到我的办公桌时,我会试一试:D 这实际上可能是要走的路,我接受得太早了......
    • 嗯,这是经过验证的代码。但是是什么阻止了您使用 LoadLibrary 和 GetProcAddress?知道这可能会有所帮助。
    • 因为实际上我需要弄清楚如何最终为这个项目列出这些功能:)
    • 它很漂亮,它完全符合我的需要,而且还有一些。我将代码修改为大约一半的行数,并使其成为成功的布尔函数。将使用最终代码更新我的问题...
    猜你喜欢
    • 2014-02-18
    • 2015-07-24
    • 2011-05-25
    • 1970-01-01
    • 2014-01-14
    • 1970-01-01
    • 1970-01-01
    • 2010-11-05
    相关资源
    最近更新 更多