2012-03-06 27 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这可能是实际去的方式,我太早接受了... – 2012-03-06 22:48:29

+0

嗯,这是经过验证和测试的代码。但是什么阻止你使用LoadLibrary和GetProcAddress?知道这可能会有所帮助。 – 2012-03-06 23:16:56

+0

因为实际上我需要弄清楚如何最终为这个项目列出这些函数:) – 2012-03-07 00:05:48

10

你必须使用LoadLibrary,然后用GetProcAddress您要检查是否存在针对每个功能。真的没有其他合理的选择(除非有特定的理由你需要避免`LoadLibrary)。由于您的意图似乎只是为了检查功能是否存在,没有其他,LoadLibraryGetProcAddress是最简单的方法;你可以用很少的几行代码完成所有的工作,错误检查非常简单直接。

+0

+1和接受(在6分钟内它会让我) – 2012-03-06 00:22:50

+4

你不会做这样的“二分搜索”。 PE格式定义明确,读取导出的函数表很容易。虽然这样做和调用LoadLibrary有很大的区别。后者执行修复并在DllMain中执行代码。可以想象,你想避免这种情况,尽管不太可能。我觉得你的陈述“有 真的没有其他选择”太强大了。 – 2012-03-06 06:49:23

+2

这个问题具体问到关于检查DLL是否存在某个函数集,这意味着避免修正不是一个考虑因素(我也包括在决定回答什么的范围内)。 – 2012-03-06 12:02:26