Environment Variables Unit
Demo Code

This document contains several examples of how to use the code in the Environment Variables Unit. They are:

  1. Example 1a: using the TPJEnvVars component with the old enumerator obtained from the EnumNames method.
  2. Example 1b: using the TPJEnvVars component with its new enumerator obtained from the GetEnumerator method.
  3. Example 2: using the GetEnvVarValue, SetEnvVarValue, DeleteEnvVar and GetAllEnvVars routines.
  4. Example 3: using the ExpandEnvVars function.
  5. Example 4: using the CreateEnvBlock function.
  6. Example 5: using the GetAllEnvVarNames and EnvBlockSize routines.

Demo projects that implement these examples are available from the EnvVarsDemo directory of the DelphiDabbler Doodlings Repository.

» Go to top

Example 1a

This example demonstrates the use of the TPJEnvVars component. The example uses the old EnumNames methods and its callback method in order to enumerate all the environment variable names.

Drop a TPJEnvVars component, a list box, a label, two edit boxes and two buttons on a form. Add the following private methods and event handlers:

procedure TForm1.EnvNamesEnum(const VarName: string; Data: Pointer);
  // Private callback method of type TPJEnvVarsEnum adds env var names to
  // list box
begin
  TStrings(Data).Add(VarName);
end;

procedure TForm1.UpdateList;
  // Displays all environment vars in list box
begin
  ListBox1.Clear;
  PJEnvVars1.EnumNames(EnvNamesEnum, ListBox1.Items);
  Label1.Caption := Format('%d environment variables', [PJEnvVars1.Count]);
  Edit1.Text := '';
  Edit2.Text := '';
end;

procedure TForm1.FormCreate(Sender: TObject);
  // Display program's environment vars in list
begin
  UpdateList;
end;

procedure TForm1.ListBox1Click(Sender: TObject);
  // Display selected env var name and value
begin
  Edit1.Text := ListBox1.Items[ListBox1.ItemIndex];
  Edit2.Text := PJEnvVars1.Values[Edit1.Text];
end;

procedure TForm1.Button1Click(Sender: TObject);
  // Update or create env var named in Edit1 with new  value per Edit2 (empty
  // string in Edit2 deletes var)
begin
  if Edit1.Text <> '' then
  begin
    PJEnvVars1.Values[Edit1.Text] := Edit2.Text;
    UpdateList;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
  // Delete env var named in Edit1
begin
  if Edit1.Text <> '' then
  begin
    PJEnvVars1.DeleteVar(Edit1.Text);
    UpdateList;
  end;
end;

The program behaves as follows:

Note: This example is similar to Example 2 which accomplishes the same task using the environment variable manipulation routines instead of the component.

» Go to top

Example 1b

This example also demonstrates the use of the TPJEnvVars component, but this time uses the newer TPJEnvVarsEnumerator class to enumerate the environment variable names. This enumerator is created by calling the GetEnumerator method. From Delphi 2005 onwards this enumerator enables the use of the for .. in loop construct. Warning: If GetEnumerator is called directly from code then the caller must free the enumerator instance.

Drop a TPJEnvVars component and a list view on a form. Add two columns named "Name" and "Value" to the list view and set the list view's properties as follows:

Now an OnCreate event handler for the form as follows:

procedure TForm1.FormCreate(Sender: TObject);

  procedure AddListItem(Name: string);
  var
    LI: TListItem;
  begin
    LI := ListView1.Items.Add;
    LI.Caption := Name;
    LI.SubItems.Add(PJEnvVars1.Values[Name]);
  end;

var
  Name: string;
  {$IFNDEF FORINSUPPORTED}
  Enum: TPJEnvVarsEnumerator;
  {$ENDIF}
begin
  {$IFDEF FORINSUPPORTED}
  for Name in PJEnvVars1 do
    AddListItem(Name);
  {$ELSE}
  Enum := PJEnvVars1.GetEnumerator;
  try
    while Enum.MoveNext do
    begin
      Name := Enum.Current;
      AddListItem(Name);
    end;
  finally
    Enum.Free;
  end;
  {$ENDIF}
end;

This code detects whether for .. in loops are supported using conditional compilation and uses the construct if available. Therefore, add the following compiler directives to the unit before the definition of FormCreate:

{$UNDEF FORINSUPPORTED}
{$IFDEF CONDITIONALEXPRESSIONS}
  {$IF CompilerVersion >= 17.0} // >= Delphi 2005
    {$DEFINE FORINSUPPORTED}
  {$IFEND}
{$ENDIF}

The demo is quite simple. It merely adds the names and values of all the environment variables to the list view on start up. It uses the TPJEnvVarsEnumerator enumerator to get all the environment variable names. Note that any environment variables that have no name are ignored by this enumerator.

» Go to top

Example 2

This example demonstrates the use of the GetEnvVarValue, SetEnvVarValue, DeleteEnvVar and GetAllEnvVars routines.

Drop a list box, two edit boxes and two buttons on a form and add PJEnvVars to your uses clause. Now enter the following code:

procedure TForm1.UpdateList;
  // Displays all environment vars in list box
var
  SL: TStringList;
  Idx: Integer;
begin
  ListBox1.Clear;
  SL := TStringList.Create;
  try
    GetAllEnvVars(SL);
    for Idx := 0 to Pred(SL.Count) do
      ListBox1.Items.Add(SL.Names[Idx]);
    Edit1.Text := '';
    Edit2.Text := '';
  finally
    SL.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
  // Display program's environment vars in list
begin
  UpdateList;
end;

procedure TForm1.ListBox1Click(Sender: TObject);
  // Display selected env var name and value
begin
  Edit1.Text := ListBox1.Items[ListBox1.ItemIndex];
  Edit2.Text := GetEnvVarValue(Edit1.Text);
end;

procedure TForm1.Button1Click(Sender: TObject);
  // Update or create env var named in Edit1 with new  value per Edit2 (empty
  // string in Edit2 deletes var)
begin
  if Edit1.Text <> '' then
  begin
    SetEnvVarValue(Edit1.Text, Edit2.Text);
    UpdateList;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
  // Delete env var named in Edit1
begin
  if Edit1.Text <> '' then
  begin
    DeleteEnvVar(Edit1.Text);
    UpdateList;
  end;
end;

The program behaves identically to Example 1a which accomplishes a similar task using the TPJEnvVars component.

» Go to top

Example 3

This example demonstrates the use of the ExpandEnvVars function.

Drop two memo controls and one button on a form and add PJEnvVars to your uses clause. Now add the event handlers listed below:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Text := 'This is my temp folder: "%TEMP%"';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo2.Text := ExpandEnvVars(Memo1.Text);
end;

Enter some text, including environment variable references (such as %TEMP%), in the first memo. Clicking the button replaces the environment variable names with their values and displays the expanded string in the second memo.

» Go to top

Example 4

This example demonstrates the use of the CreateEnvBlock function. It involves creating two programs:

  1. A slave program that simply displays its environment variables.
  2. A master program that sets up an environment block and spawns a child process that uses the new block.

Slave program

Create a new application with a main form named SlaveForm. Drop a memo control onto the form, and set its Align property to alClient and clear the Lines property. Make sure the PJEnvVars unit is in your uses clause. Give the form the following OnCreate handler:

procedure TSlaveForm.FormCreate(Sender: TObject);
begin
  GetAllEnvVars(Memo1.Lines);
end;

Save the project as SlaveApp and compile. The memo will display all of SlaveApp's environment variables when the program is executed.

Master program

Create a new application with a main form named MasterForm. Drop a memo control onto the form and clear its text. Add a button and a check box below the memo. Ensure the PJEnvVars unit is referenced in the uses clause. Add the following helper procedure and button event handler:

procedure ExecProg(const ProgName: string; EnvBlock: Pointer);
  // Creates a new process for given program passing any given environment block
var
  SI: TStartupInfo;
  PI: TProcessInformation;
  CreateFlags: DWORD;
  SafeProgName: string;
begin
  // ensure ProgName has ref count of 1: its memory is writeable
  // re issue with CreateProcessW: see http://bit.ly/bTRc7D
  SafeProgName := ProgName;
  UniqueString(SafeProgName);
  // set up for CreateProcess
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  // set flags according to if unicode environment block
  {$IFDEF UNICODE}
  CreateFlags := CREATE_UNICODE_ENVIRONMENT;
  {$ELSE}
  CreateFlags := 0;
  {$ENDIF}
  CreateProcess(
    nil, PChar(SafeProgName), nil, nil, True,
    CreateFlags, EnvBlock, nil, SI, PI
  );
end;

procedure TMasterForm.Button1Click(Sender: TObject);
var
  EnvBlock: Pointer;
  BlockSize: Integer;
begin
  // Create the environment block
  BlockSize := CreateEnvBlock(Memo1.Lines, CheckBox1.Checked, nil, 0);
  // BlockSize is in Characters, not bytes: so needs converting to bytes
  GetMem(EnvBlock, BlockSize * SizeOf(Char));
  try
    CreateEnvBlock(Memo1.Lines, CheckBox1.Checked, EnvBlock, BlockSize);
    // Execute the slave app
    ExecProg('SlaveApp.exe', EnvBlock);
  finally
    FreeMem(EnvBlock);
  end;
end;

Save the project as MasterApp in the same folder as SlaveApp and compile.

There is some interesting code in both ExecProg and Button1Click to watch out for.

When MasterApp is run, enter some environment variables in NAME=VALUE format in the memo control. Place each NAME=VALUE pair on a different line.

Now click the button. A copy of SlaveApp should run, displaying just the environment variables you entered.

Return to MasterApp and check the check box. Click the button again to spawn another instance of SlaveApp. This new instance will have a copy of MasterApp's environment variables along with the new ones you entered in MasterApp's memo control.

» Go to top

Example 5

This example demonstrates the use of both overloaded versions of GetAllEnvVarNames and EnvBlockSize.

Drop three labels, two edit controls, two list boxes, one button and a TPJEnvVars component on a form. Set the following properties:

Create an OnCreate event handler for the form, an OnClick event handler for the button and an OnClick event handler that is attached to both list boxes. Also create a private method of the form class named UpdateListsEtc. Code the methods as follows:

procedure TForm1.UpdateListsEtc;
var
  Idx: Integer;
  Names: TStringDynArray;
begin
  GetAllEnvVarNames(ListBox1.Items);
  Names := GetAllEnvVarNames;
  ListBox2.Clear;
  for Idx := 0 to High(Names) do
    ListBox2.Items.Add(Names[Idx]);
  Label3.Caption := Format('Environment block size: %d', [EnvBlockSize]);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  UpdateListsEtc;
end;

procedure TForm1.ListBoxClick(Sender: TObject);
begin
  Edit1.Text := (Sender as TListBox).Items[(Sender as TListBox).ItemIndex];
  Edit2.Text := PJEnvVars1.Values[Edit1.Text];
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PJEnvVars1.Values[Trim(Edit1.Text)] := Trim(Edit2.Text);
  UpdateListsEtc;
end;

If the Types unit is provided in your version of Delphi, add it to the uses clause.

UpdateListsEtc populates the list boxes and displays the environment block size in Label3. ListBox1 is populated with all environment variable names using the TStrings overload of GetAllEnvVarNames while ListBox2 is populated by using GetAllEnvVarNames's string array overload. Both list boxes should contain exactly the same information.

Selecting a name in either list box copies the name into the first edit control and the environment variable's value in the second edit control. Entering a new name or value and clicking the Update button updates the environment variable, repopulates the lists and updates the environment block size.

Please let me know if you have any comments about this demo code, if you have found a bug, or you want to suggest any updates.