Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I determine if a unit has been compiled into a Delphi program?

I want to be able to determine if a particular unit has been compiled into a Delphi program, e.g. the unit SomeUnitName is part of some of my programs but not of others. I would like to have a function

function IsSomeUnitNameInProgram: boolean;

(which is of course not declared in SomeUnitName because in that case it would always be included) that at runtime returns true, if the unit has been compiled into the program, and false, if not.

My thoughts so far have gone along the lines of using the jcl debug information (compiled from a detailed map file) which I basically add to all my programs to determine this information, but I would prefer it, if jcl were not required.

Adding code to SomeUnitName is not an option.

This is currently for Delphi 2007 but preferably should also work for Delphi XE2.

Any thoughts?

some background on this since @DavidHeffernan asked:

This is not only for one program but for more than 100 different ones. Most of them are used internally but some also get delivered to customers. Since we use quite a few libraries, some bought others under various open source licenses, I wanted to be able to add a "credits" tab to the about box which displays only those libraries actually compiled into the program rather than all of them. Thanks to the answer from TOndrej this works now exactly as I wanted it to: The code checks for a unit which is always linked if a library is used by the program and if it is there, it adds the library name, the copyright and a link to it to the about box.

like image 715
dummzeuch Avatar asked Aug 24 '12 07:08

dummzeuch


2 Answers

Unit names are compiled into the 'PACKAGEINFO' resource where you can look it up:

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;

To do this for the current executable pass it HInstance:

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

(GetPackageInfo enumerates all units which may be inefficient for executables with many units, in that case you can dissect the implementation in SysUtils and write your own version which stops enumerating when the unit is found.)

like image 63
Ondrej Kelle Avatar answered Sep 22 '22 21:09

Ondrej Kelle


This function will return the list of unit names included in an application. Works in Delphi 2010. Not verified for other compilers.

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;

Example to use in the was suggested in the question...

function IsSomeUnitNameInProgram: boolean;
var
  UnitNamesStrs: TStrings;
begin
UnitNamesStrs := UnitNames;
result := UnitNamesStrs.IndexOf('MyUnitName') <> -1;
UnitNamesStrs.Free
end;
like image 30
Sean B. Durkin Avatar answered Sep 22 '22 21:09

Sean B. Durkin