Delphi How to programatically detect installed versions of Delphi

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

How to programatically detect installed versions of Delphi

Introduction​

If you're wanting to execute Delphi or, more likely, its command line compiler from another application, you need a way of finding which Delphi versions are installed. You also need to know where the desired executables have been installed.
The information we need to detect Delphi installations and to execute the compiler is recorded in the registry.
We will take this in two stages. First we will enumerate all installed versions of Delphi and then we will find the full path to an available command line compiler.
Actually executing the compiler from within your program, in a meaningful way, is beyond the scope of this article. If you want another article about how to do that, Для просмотра ссылки Войди или Зарегистрируйся and ask!
In the meatime, you can see some code that does this in my Для просмотра ссылки Войди или Зарегистрируйся. Look for units named Compilers.xxx.pas
Note for users of older Delphi compilers: The example code in this article was written and tested in Delphi 11 Alexandria. The code uses features of the later Delphi compilers and will not compile on older version without change. In the main you may need to move all variable declarations out of the body of the code into a var section at the top of each routine. The code also uses for .. in loops quite extensively.

Find valid Delphi installations​

Enumerate registry keys​

First things first. Lets see where Delphi install information is stored in the registry. That depends on whether Delphi / RAD Studio was installed just for the current user or for all users of the machine.
When installed for all users the information we want is under the HKEY_LOCAL_MACHINE root key, but if installation was for only the current user we need to look in HKEY_CURRENT_USER.
The path to the required data differs for each version of Delphi, but is the same for each root key. The format of the path has changed over the years as Delphi has evolved. The following table gives the registry paths for each version from Delphi 2 to Delphi 11 Alexandria:

Delphi versionRegistry path
2\SOFTWARE\Borland\Delphi\2.0
3\SOFTWARE\Borland\Delphi\3.0
4\SOFTWARE\Borland\Delphi\4.0
5\SOFTWARE\Borland\Delphi\5.0
6\SOFTWARE\Borland\Delphi\6.0
7\SOFTWARE\Borland\Delphi\7.0
2005\SOFTWARE\Borland\BDS\3.0
2006\SOFTWARE\Borland\BDS\4.0
2007\SOFTWARE\Borland\BDS\5.0
2009\SOFTWARE\CodeGear\BDS\6.0
2010\SOFTWARE\CodeGear\BDS\7.0
XE\Software\Embarcadero\BDS\8.0
XE2\Software\Embarcadero\BDS\9.0
XE3\Software\Embarcadero\BDS\10.0
XE4\Software\Embarcadero\BDS\11.0
XE5\Software\Embarcadero\BDS\12.0
XE6\Software\Embarcadero\BDS\14.0
XE7\Software\Embarcadero\BDS\15.0
XE8\Software\Embarcadero\BDS\16.0
10 Seattle\Software\Embarcadero\BDS\17.0
10.1 Berlin\Software\Embarcadero\BDS\18.0
10.2 Tokyo\Software\Embarcadero\BDS\19.0
10.3 Rio\Software\Embarcadero\BDS\20.0
10.4 Sydney\Software\Embarcadero\BDS\21.0
11 Alexandria\Software\Embarcadero\BDS\22.0
This tabulation suggests a way of enumerating the installed versions of Delphi. We could use an array of records, with each record storing the Delphi version and registry path. We would then enumerate that array, checking whether each registry path exists and recording the indices of those that do exist. In code this would look something like this:
Код:
uses
  System.Win.Registry;
 
type
  TDelphiInfo = record
    Name: string;
    RegKey: string;
  end;
 
const
  AllDelphis: array[0..24] of TDelphiInfo = (
    (Name: '2'; RegKey: '\SOFTWARE\Borland\Delphi\2.0'),
    (Name: '3'; RegKey: '\SOFTWARE\Borland\Delphi\3.0'),
    (Name: '4'; RegKey: '\SOFTWARE\Borland\Delphi\4.0'),
    (Name: '5'; RegKey: '\SOFTWARE\Borland\Delphi\5.0'),
    (Name: '6'; RegKey: '\SOFTWARE\Borland\Delphi\6.0'),
    (Name: '7'; RegKey: '\SOFTWARE\Borland\Delphi\7.0'),
    (Name: '2005'; RegKey: '\SOFTWARE\Borland\BDS\3.0'),
    (Name: '2006'; RegKey: '\SOFTWARE\Borland\BDS\4.0'),
    (Name: '2007'; RegKey: '\SOFTWARE\Borland\BDS\5.0'),
    (Name: '2009'; RegKey: '\SOFTWARE\CodeGear\BDS\6.0'),
    (Name: '2010'; RegKey: '\SOFTWARE\CodeGear\BDS\7.0'),
    (Name: 'XE'; RegKey: '\Software\Embarcadero\BDS\8.0'),
    (Name: 'XE2'; RegKey: '\Software\Embarcadero\BDS\9.0'),
    (Name: 'XE3'; RegKey: '\Software\Embarcadero\BDS\10.0'),
    (Name: 'XE4'; RegKey: '\Software\Embarcadero\BDS\11.0'),
    (Name: 'XE5'; RegKey: '\Software\Embarcadero\BDS\12.0'),
    (Name: 'XE6'; RegKey: '\Software\Embarcadero\BDS\14.0'),
    (Name: 'XE7'; RegKey: '\Software\Embarcadero\BDS\15.0'),
    (Name: 'XE8'; RegKey: '\Software\Embarcadero\BDS\16.0'),
    (Name: '10 Seattle'; RegKey: '\Software\Embarcadero\BDS\17.0'),
    (Name: '10.1 Berlin'; RegKey: '\Software\Embarcadero\BDS\18.0'),
    (Name: '10.2 Tokyo'; RegKey: '\Software\Embarcadero\BDS\19.0'),
    (Name: '10.3 Rio'; RegKey: '\Software\Embarcadero\BDS\20.0'),
    (Name: '10.4 Sydney'; RegKey: '\Software\Embarcadero\BDS\21.0'),
    (Name: '11 Alexandria'; RegKey: '\Software\Embarcadero\BDS\22.0')
  );
 
function IsRegisteredInRootKey(RootKey: HKEY; SubKey: string): Boolean;
begin
  var Reg: TRegistry := TRegistry.Create(KEY_READ);
  try
    Reg.RootKey := RootKey;
    Result := Reg.OpenKeyReadOnly(SubKey);
    if Result then
      Reg.CloseKey;
  finally
    Reg.Free;
  end;
end;
 
function IsRegistered(SubKey: string): Boolean;
begin
  Result := IsRegisteredInRootKey(HKEY_LOCAL_MACHINE, SubKey);
  if not Result then
    Result := IsRegisteredInRootKey(HKEY_CURRENT_USER, SubKey);
end;
 
function FindInstallations: TArray<TDelphiInfo>;
begin
  SetLength(Result, Length(AllDelphis));
  var FoundCount: Integer := 0;
  for var Rec: TDelphiInfo in AllDelphis do
  begin
    if IsRegistered(Rec.RegKey) then
    begin
      Result[FoundCount] := Rec;
      Inc(FoundCount);
    end;
  end;
  SetLength(Result, FoundCount);
end;
Listing 1
Here's how this works.
  • TDelphiInfo and AllDelphis together form the table of records mentioned above that stores the required registry information for each compiler.
  • FindInstallations checks the registry for every compiler in AllDelphis and returns an array of TDelphiInfo records for each compiler where its installation subkey is found in the registry. FindInstallations uses the IsRegistered helper routine to actually check the registry.
  • IsRegistered checks for the given subkey in both the HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER root keys. This is because Delphi can be installed for all users of a computer or for a specified user. In the former case the installation information is stored under HKEY_LOCAL_MACHINE and in the later case the information is stored under HKEY_CURRENT_USER.
  • IsRegistered calls IsRegisteredInRootKey to check whether the given subkey is found in each root key.
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

Validate the list​

The above code seems to provide us with a list of all installations of Delphi. While that list is correct in theory, there's a fly in the ointment.
Windows uninstallers are infamous for not cleaning up properly, and its entirely possible that an uninstaller would fail to delete the registry entries for a long gone Delphi installation. In this case our code would find the registry key and would report that version as being installed.
So what's the solution?
Within the registry subkey there is a value named RootDir that is present for all versions of Delphi to date. This value stores the full path of the directory where a given version of Delphi is installed. So, for our first test, we can make sure that the RootDir value exists and that the directory it points to also exists.
The following code reads the installation directory from the registry:

Код:
uses
 System.IOUtils,
 System.Win.Registry;

function GetRegRootKey(SubKey: string): HKEY;
begin
 if IsRegisteredInRootKey(HKEY_LOCAL_MACHINE, SubKey) then
 Result := HKEY_LOCAL_MACHINE
 else if IsRegisteredInRootKey(HKEY_CURRENT_USER, SubKey) then
 Result := HKEY_CURRENT_USER
 else
 Result := 0; // not registered
end;

function GetInstallDir(SubKey: string): string;
begin
 var RootKey: HKEY := GetRegRootKey(SubKey);
 if RootKey = 0 then
 Exit('');
 var Reg: TRegistry := TRegistry.Create(KEY_READ);
 try
 if not Reg.OpenKeyReadOnly(SubKey) then
 Exit('');
 Result := Reg.ReadString('RootDir');
 Reg.CloseKey;
 finally
 Reg.Free;
 end;
Listing 2

Note that this code should ideally be called only once you know that the installation information is in the registry.
Here, GetInstallDir attempts to read the RootDir value from the given subkey. Since we didn't record which root key the subkey belongs to earlier, we need to find it again. The GetRegRootKey helper routine does this.
I know, I know! Searching for the root key again is not the most efficient code - it would have been better to record it earlier and re-use it. But in my defence, I don't want to clutter the earlier examples with code we wouldn't need until later. If this was production code I'd wrap it all up in a class anyway, and would store the root key in a field. But it's not and I haven't!
Next we check if the installation directory exists with the following routine.

Код:
function InstallDirExists(SubKey: string): Boolean;
begin
 var InstDir: string := GetInstallDir(SubKey);
 if InstDir = '' then
 Exit(False);
 Result := TDirectory.Exists(InstDir);
end;
Listing 3

InstallDirExists simply calls GetInstallDir, checks that a value has been returned from that call, then checks if the directory exists.
We can now write a new version of FindInstallations, FindInstallations2, that checks that the installation directory exists:

Код:
function FindInstallations2: TArray<TDelphiInfo>;
begin
 SetLength(Result, Length(AllDelphis));
 var FoundCount: Integer := 0;
 for var Rec: TDelphiInfo in AllDelphis do
 begin
 if IsRegistered(Rec.RegKey) and InstallDirExists(Rec.RegKey) then
 begin
 Result[FoundCount] := Rec;
 Inc(FoundCount);
 end;
 end;
 SetLength(Result, FoundCount);
end;
Listing 4

FindInstallations2 varies from FindInstallations in the if statement on line 7 where we add a call to InstallDirExists.
We can go a little further and check for the presence of a known file within the Delphi installation. For example we can check if DCC32.exe exists. This file is present in all full versions of Delphi to date, so it's a good choice. DCC32.exe is always located in the Bin sub-directory of the installation directory.
I'm not sure if DCC32.exe is present in (some) community editions of Delphi. If anyone knows the answer to this, please Для просмотра ссылки Войди или Зарегистрируйся on this website's GitHub repo and let me know.
Here's a function to detect if DCC32.exe exists for a given registry subkey:

Код:
function DCC32Exists(SubKey: string): Boolean;
begin
 if not InstallDirExists(SubKey) then
 Exit(False);
 var FileName: string := IncludeTrailingPathDelimiter(GetInstallDir(SubKey))
 + 'Bin' + PathDelim + 'DCC32.exe';
 Result := TFile.Exists(FileName);
end;
Listing 5

We can now extend FindInstallations one last time to check for the presence of DCC32.exe:

Код:
function FindInstallations3: TArray<TDelphiInfo>;
begin
 SetLength(Result, Length(AllDelphis));
 var FoundCount: Integer := 0;
 for var Rec: TDelphiInfo in AllDelphis do
 begin
 if IsRegistered(Rec.RegKey) and DCC32Exists(Rec.RegKey) then
 begin
 Result[FoundCount] := Rec;
 Inc(FoundCount);
 end;
 end;
 SetLength(Result, FoundCount);
end;
Listing 6

Again, the only difference from FindInstallations is in line 7 with its call to DCC32Exists.
Let us end this section with a little VCL program that displays the names of all the Delphi installations available to the current user of a PC.
Create a new VCL application and drop a TListBox on the form. Add all the functions above to the implementation section of the form unit. Now add an OnCreate event handler to the form and code it as follows:

Код:
procedure TForm1.FormCreate(Sender: TObject);
begin
 for var Rec: TDelphiInfo in FindInstallations3 do
 ListBox1.Items.Add(Rec.Name);
end;
Listing 7

The code assumes that the default form and list box names were left unchanged.
This code gets a TDelphiInfo record for each installed version of Delphi and adds its name to the list box. Here's what I got:

Program window listing names of installed versions of Delphi

Image 1

Find the command line compilers​

This is the easiest step by far, and the solution has already been hinted at above where we tested for a valid install by looking for DCC32.exe, the 32 bit command line compiler. That code found the full path we were looking for.
What about the 64 bit compiler? We simply use the same technique as used above to check for the presence of DCC64.exe. Not all versions of Delphi have a 64 bit compiler, but we can use this code to find out which versions do support it.
The following code displays the locations of the 32bit and 64bit compilers (where available). This code uses the same project as before, with a main form containing a list box. We change the OnCreate event handler

Код:
function TryGetFilePath(BaseName, SubKey: string; out Path: string): Boolean;
begin
 Path := '';
 if not InstallDirExists(SubKey) then
 Exit(False);
 Path := IncludeTrailingPathDelimiter(GetInstallDir(SubKey))
 + 'Bin' + PathDelim + BaseName;
 Result := TFile.Exists(Path);
end;

procedure TForm1.FormCreate(Sender: TObject);
const
 DCC32FN = 'DCC32.exe';
 DCC64FN = 'DCC64.exe';

 procedure AddDCCInfo(DCC: string; Rec: TDelphiInfo);
 begin
 var Path: string;
 var Installed: Boolean := TryGetFilePath(DCC, Rec.RegKey, Path);
 if Installed then
 ListBox1.Items.Add(' ' + DCC + ' installed at: ' + Path)
 else
 ListBox1.Items.Add(' ' + DCC + ' not available');
 end;

begin
 var Installations: TArray<TDelphiInfo> := FindInstallations2;
 for var Rec: TDelphiInfo in Installations do
 begin
 ListBox1.Items.Add('Delphi ' + Rec.Name);
 AddDCCInfo(DCC32FN, Rec);
 AddDCCInfo(DCC64FN, Rec);
 end;
end;
Listing 8

Firstly, we have yet another helper routine, TryGetFilePath, that tries to get the full path of given file in a specified Delphi installation. BaseName is the name of the required file, without any path informaton; SubKey is the registry sub key that provides information about the Delphi installation and Path receives the fully specified path to the required file. TryGetFilePath returns True if the file exists and False if not. Where the file doesn't exists Path is set to ''.
In the TForm1.FormCreate event handler we first get a list of all installed versions of Delphi. The name of each version is added to the list box. We then check to see if DCC32.exe and DCC64.exe are included in the installation, using the local procedure AddDCCInfo. This procedure checks if the required programs exist and writes the full path to the executable file if so.
Here are the results of running the code on my system:

Program window listing which of DCC32.exe and DCC64.exe are available for each installed version of Delphi

Image 2

You now have all the information you need to be able to find various Delphi executable programs.
You could use a similar technique to find other compilers, like the C++ compiler providing you know the executable file name.