如何确定一个单元是否已经编译进了 Delphi 程序?

15

我希望能够确定某个单元是否已编译进 Delphi 程序中,例如,单元 SomeUnitName 是我的某些程序的一部分,但不是其他程序的一部分。我想要一个函数。

function IsSomeUnitNameInProgram: boolean;

如果在SomeUnitName中声明,它将总是被包含,因此未在SomeUnitName中声明 (which is of course not declared in SomeUnitName because in that case it would always be included) 。在运行时返回true,如果该单元已编译进程序,则返回false。

目前我的想法是使用jcl调试信息(从详细的映射文件编译而来),我基本上将其添加到所有的程序中以确定这些信息,但是我希望不需要使用jcl。

向SomeUnitName添加代码不是一个选项。

目前是针对Delphi 2007,但最好也适用于Delphi XE2。

@DavidHeffernan问及一些背景信息:

这不仅仅是为了一个程序,而是为了100多个不同的程序。其中大部分是内部使用的,但有些也会交付给客户。由于我们使用了相当多的库,一些是购买的,其他则是在不同的开源许可证下发布的,因此我想能够向"关于"对话框中添加一个"致谢"选项卡,仅显示实际编译到程序中的那些库,而不是所有库。感谢TOndrej的答案,现在它可以完美地按照我所希望的方式工作了: 该代码检查是否存在一个单元,如果使用程序,则始终链接该单元;如果存在,则向关于框中添加库名称、版权和链接。


1
你在编译时就知道这个。为什么要进行运行时检查呢? - David Heffernan
2
他可能拥有程序的多个构建版本,具有不同的功能。并且不依赖于条件编译。如果那是他自己的代码,他可以依赖于一些选择性注册方案,例如 VCL RegisterClass 或 UnitVersioning lib。但如果这不是他的单元... - Arioch 'The
2个回答

20

单元名称被编译到'PACKAGEINFO'资源中,您可以在其中查找:

uses
  SysUtils;

type
  PUnitInfo = ^TUnitInfo;
  TUnitInfo = record
    UnitName: string;
    Found: PBoolean;
  end;

procedure HasUnitProc(const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);
begin
  case NameType of
    ntContainsUnit:
      with PUnitInfo(Param)^ do
        if SameText(Name, UnitName) then
          Found^ := True;
  end;
end;

function IsUnitCompiledIn(Module: HMODULE; const UnitName: string): Boolean;
var
  Info: TUnitInfo;
  Flags: Integer;
begin
  Result := False;
  Info.UnitName := UnitName;
  Info.Found := @Result;
  GetPackageInfo(Module, @Info, Flags, HasUnitProc);
end;

要为当前可执行文件实现此操作,请传递 HInstance

HasActiveX := IsUnitCompiledIn(HInstance, 'ActiveX');

(GetPackageInfo函数列举了所有可能的单元,对于包含许多单元的可执行文件来说,这可能会导致效率低下。在这种情况下,你可以分解SysUtils中的实现并编写自己的版本,在找到单元后停止枚举。)


5

该函数将返回应用程序中包含的单位名称列表。适用于 Delphi 2010。未经其他编译器验证。

function UnitNames: TStrings;
var
  Lib: PLibModule;
  DeDupedLibs: TList<cardinal>;
  TypeInfo: PPackageTypeInfo;
  PInfo: GetPackageInfoTable;
  LibInst: Cardinal;
  u: Integer;
  s: string;
  s8: UTF8String;
  len: Integer;
  P: PByte;
begin
result := TStringList.Create;
DeDupedLibs := TList<cardinal>.Create;
Lib := LibModuleList;
try
  while assigned( Lib) do
    begin
    LibInst := Lib^.Instance;
    Typeinfo := Lib^.TypeInfo;
    if not assigned( TypeInfo) then
      begin
      PInfo := GetProcAddress( LibInst, '@GetPackageInfoTable');
      if assigned( PInfo) then
        TypeInfo := @PInfo^.TypeInfo;
      end;
    if (not assigned( TypeInfo)) or (DeDupedLibs.IndexOf( LibInst) <> -1) then continue;
    DeDupedLibs.Add( LibInst);
    P := Pointer( TypeInfo^.UnitNames);
    for u := 0 to TypeInfo^.UnitCount - 1 do
      begin
      len := P^;
      SetLength( s8, len);
      if len = 0 then Break;
      Inc( P, 1);
      Move( P^, s8[1], len);
      Inc( P, len);
      s := UTF8ToString( s8);
      if Result.IndexOf( s) = -1 then
        Result.Add( s)
      end
    end
finally
  DeDupedLibs.Free
  end
end;

在问题中建议使用的示例

function IsSomeUnitNameInProgram: boolean;
var
  UnitNamesStrs: TStrings;
begin
UnitNamesStrs := UnitNames;
result := UnitNamesStrs.IndexOf('MyUnitName') <> -1;
UnitNamesStrs.Free
end;

TOndrej的解决方案是正确的方法。这种方式的唯一优点是您不必枚举所有软件包以获取聚合列表,并且可能会更有效率。缺点是它感觉像是hacky并且尚未在其他编译器上进行验证。 - Sean B. Durkin
回想起来,我应该在“有趣的方法”后面加上一些笑脸(;)。这感觉有点像hacky,但以简洁的方式展示了一些Delphi内部。顺便说一句:begin/end没有缩进,但是在左括号后面有空格,在右括号后面没有空格?(只是好奇为什么,因为我还在学习,不想引发代码格式化战争)。 - Jeroen Wiert Pluimers
1
格式仅仅是我的个人风格。也许有人想要批评并说它不流行或者不正统之类的。如果在StackOverflow上,我并不真的关心流行度。我会按照自己的方式去做。如果有人反对我的格式选择,他们完全可以忽略我的回答。 - Sean B. Durkin
个人风格没有问题(我的风格也不完全符合Delphi RTL / VCL的风格)。你有非常一致的风格。为此加1分! - Jeroen Wiert Pluimers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接