The Road to Delphi

Delphi – Free Pascal – Oxygene


8 Comments

Listing the running user applications under osx using Delphi

The NSWorkspace class provides a set of methods and properties which allow open and manipulate files, applications and others useful tasks. One of these properties can be used to list the running applications. The property is runningApplications , this will return an array of NSRunningApplication elements representing the running applications. Unfortunately the definition of this interface (NSRunningApplication) in the Macapi.AppKit is incomplete.

  NSRunningApplication = interface(NSObject)
    ['{96F4D9CA-0732-4557-BA1F-177958903B8F}']
    function activateWithOptions(options: NSApplicationActivationOptions): Boolean; cdecl;
    function activationPolicy: NSApplicationActivationPolicy; cdecl;
    function executableArchitecture: NSInteger; cdecl;
    function forceTerminate: Boolean; cdecl;
    function hide: Boolean; cdecl;
    function isActive: Boolean; cdecl;
    function isFinishedLaunching: Boolean; cdecl;
    function isHidden: Boolean; cdecl;
    function isTerminated: Boolean; cdecl;
    function processIdentifier: Integer; cdecl;
    function terminate: Boolean; cdecl;
    function unhide: Boolean; cdecl;
  end;

As you can see in the above definition there is not a property to retrieve the application name or path. So the first task in order to retrieve the list of the running applications is add the missing properties like so.

  NSRunningApplicationEx = interface(NSObject)
    ['{96F4D9CA-0732-4557-BA1F-177958903B8F}']
    function activateWithOptions(options: NSApplicationActivationOptions): Boolean; cdecl;
    function activationPolicy: NSApplicationActivationPolicy; cdecl;
    function executableArchitecture: NSInteger; cdecl;
    function forceTerminate: Boolean; cdecl;
    function hide: Boolean; cdecl;
    function isActive: Boolean; cdecl;
    function isFinishedLaunching: Boolean; cdecl;
    function isHidden: Boolean; cdecl;
    function isTerminated: Boolean; cdecl;
    function processIdentifier: Integer; cdecl;
    function terminate: Boolean; cdecl;
    function unhide: Boolean; cdecl;

    //Added functions(properties)
    //Indicates the URL to the application's executable.
    function executableURL : NSURL; cdecl;//@property (readonly) NSURL *executableURL;
    //Indicates the name of the application.  This is dependent on the current localization of the referenced app, and is suitable for presentation to the user.
    function localizedName : NSString; cdecl;//@property (readonly) NSString *localizedName;
    //Indicates the URL to the application's bundle, or nil if the application does not have a bundle.
    function bundleURL : NSURL; cdecl;//@property (readonly) NSURL *bundleURL;
    //Indicates the CFBundleIdentifier of the application, or nil if the application does not have an Info.plist.
    function bundleIdentifier : NSString; cdecl;//@property (readonly) NSString *bundleIdentifier;
    //Indicates the date when the application was launched.  This property is not available for all applications.  Specifically, it is not available for applications that were launched without going through LaunchServices.   */
    function launchDate : NSDate;cdecl;//@property (readonly) NSDate *launchDate;
    //Returns the icon of the application.
    function icon : NSImage;cdecl;//@property (readonly) NSImage *icon;
  end;
  TNSRunningApplicationEx = class(TOCGenericImport<NSRunningApplicationClass, NSRunningApplicationEx>)  end;

Now using a TStringGrid we can list all the user applications running

var
  LWorkSpace : NSWorkspace;
  LApp       : NSRunningApplicationEx;
  LFormatter : NSDateFormatter;
  i : integer;
  LArray     : NSArray;
begin
  LWorkSpace:=TNSWorkspace.create;//or TNsWorkspace.Wrap(TNsWorkSpace.OCClass.sharedWorkspace);
  LArray:=LWorkSpace.runningApplications;
  //NSDateFormatter Class Reference
  //https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/Reference/Reference.html
  TNSDateFormatter.OCClass.setDefaultFormatterBehavior(NSDateFormatterBehavior10_4);
  LFormatter:=TNSDateFormatter.Create;
  LFormatter.setDateFormat(NSSTR('HH:mm:ss YYYY/MM/dd'));
  if LArray<>nil then
  begin
    StringGrid1.RowCount:=LArray.count;
   for i := 0 to LArray.count-1 do
   begin
     LApp:= TNSRunningApplicationEx.Wrap(LArray.objectAtIndex(i));
     StringGrid1.Cells[0,i]:=LApp.processIdentifier.ToString();
     if LApp.launchDate<>nil then
     StringGrid1.Cells[1,i]:=string(LFormatter.stringFromDate(LApp.launchDate).UTF8String);
     StringGrid1.Cells[2,i]:=string(LApp.localizedName.UTF8String);
     StringGrid1.Cells[3,i]:=string(LApp.executableURL.path.UTF8String);

     if LApp.bundleIdentifier<>nil then
       StringGrid1.Cells[4,i]:=string(LApp.bundleIdentifier.UTF8String);

     if LApp.bundleURL<>nil then
       StringGrid1.Cells[5,i]:=string(LApp.bundleURL.path.UTF8String);

      case LApp.executableArchitecture of
        NSBundleExecutableArchitectureI386  :   StringGrid1.Cells[6,i]:='I386';
        NSBundleExecutableArchitecturePPC   :   StringGrid1.Cells[6,i]:='PPC';
        NSBundleExecutableArchitecturePPC64 :   StringGrid1.Cells[6,i]:='PPC64';
        NSBundleExecutableArchitectureX86_64:   StringGrid1.Cells[6,i]:='X86_64';
      end;
   end;
  end;
end;

And this is the final result.

Mac OS X Lion

Note : The runningApplications property only list the user applications and does not provide information about every process on the system. In order to access to all the process you can use the sysctl function with the CTL_KERN, KERN_PROC, KERN_PROC_ALL values.

Download the sample Firemonkey project from Github.


Leave a comment

Getting System information in OSX and iOS using Delphi (XE2, XE3, XE4) Part 2

This is the continuation of the Getting System information in OSX and iOS using Delphi (XE2, XE3, XE4) Part 1 article.

Inside of the Posix.SysSysctl unit (which is the translation for the sysctl.h file) you can find a set of arrays (CTL_NAMES, CTL_KERN_NAMES, CTL_HW_NAMES, CTL_USER_NAMES, CTL_VM_NAMES) which contains the alias names for the system information elements, so using the items of these arrays you can build the name parameter for the SysCtlByName method.

For example if you want to access the kernel version you can build the alias like so

  Name:=PAnsiChar(CTL_NAMES[CTL_KERN].ctl_name+'.'+CTL_KERN_NAMES[KERN_VERSION].ctl_name);//kern.version

And then using the SysCtlByName method

function KernelVersion: AnsiString;
var
  res : Integer;
  len : size_t;
  p, Name: PAnsiChar;
begin
  len := SizeOf(Result);
  Name:=PAnsiChar(CTL_NAMES[CTL_KERN].ctl_name+'.'+CTL_KERN_NAMES[KERN_VERSION].ctl_name);
  res:=SysCtlByName(Name, nil, @len, nil, 0);
   if (len>0) and (res=0)  then
   begin
     GetMem(p, len);
     try
       res:=SysCtlByName(Name, p, @len, nil, 0);
       if res=0 then
        Result:=p;
     finally
       FreeMem(p);
     end;
   end;
end;

Another option is use these definitions for build a set of methods to retrieve all the system info included in these structs.

Try the next sample project.

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.Classes,
  System.Types,
  Posix.Errno,
  Posix.SysTypes,
  Posix.SysSysctl,
  System.SysUtils;

function GetsysctlIntValue(mib: TIntegerDynArray) : integer;
var
  len : size_t;
  res : integer;
begin
   len := sizeof(Result);
   res:=sysctl(@mib[0], 2, @Result, @len, nil, 0);
   if res<>0 then
    Result:=-1;// RaiseLastOSError;
end;

function GetsysctlInt64Value(mib: TIntegerDynArray) : Int64;
var
  len : size_t;
  res : integer;
begin
   len := sizeof(Result);
   res:=sysctl(@mib[0], 2, @Result, @len, nil, 0);
   if res<>0 then
     Result:=-1; //RaiseLastOSError;
end;

function GetsysctlStrValue(mib: TIntegerDynArray) : AnsiString;
var
  len : size_t;
  p   : PAnsiChar;
  res : integer;
begin
   Result:='';
   res:=sysctl(@mib[0], 2, nil, @len, nil, 0);
   if (len>0) and (res=0)  then
   begin
     GetMem(p, len);
     try
       res:=sysctl(@mib[0], 2, p, @len, nil, 0);
       if res=0 then
        Result:=p;
     finally
       FreeMem(p);
     end;
   end;
end;

procedure  ListKernelValues;
var
  mib : TIntegerDynArray;
  i   : Integer;
begin
 Writeln('High kernel limits');
 Writeln('------------------');
 for i:=0 to KERN_MAXID-1 do
 begin
    mib:=TIntegerDynArray.Create(CTL_KERN, i);
    case CTL_KERN_NAMES[i].ctl_type of
     CTLTYPE_NODE  :  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_KERN].ctl_name, CTL_KERN_NAMES[i].ctl_name, '[node]']));
     CTLTYPE_OPAQUE:  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_KERN].ctl_name, CTL_KERN_NAMES[i].ctl_name, '[structure]']));
     CTLTYPE_INT   :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_KERN].ctl_name, CTL_KERN_NAMES[i].ctl_name, GetsysctlIntValue(mib)]));
     CTLTYPE_QUAD  :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_KERN].ctl_name, CTL_KERN_NAMES[i].ctl_name, GetsysctlInt64Value(mib)]));
     CTLTYPE_STRING : Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_KERN].ctl_name, CTL_KERN_NAMES[i].ctl_name, GetsysctlStrValue(mib)]));
    end;
 end;
 Writeln;
end;

procedure  ListGenericCPU_IO_Values;
var
  mib : TIntegerDynArray;
  i   : Integer;
begin
 Writeln('Generic CPU, I/O');
 Writeln('-----------------');
 for i:=0 to HW_MAXID-1 do
 begin
    mib:=TIntegerDynArray.Create(CTL_HW, i);
    case CTL_HW_NAMES[i].ctl_type of
     CTLTYPE_NODE  :  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_HW].ctl_name, CTL_HW_NAMES[i].ctl_name, '[node]']));
     CTLTYPE_OPAQUE:  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_HW].ctl_name, CTL_HW_NAMES[i].ctl_name, '[structure]']));
     CTLTYPE_INT   :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_HW].ctl_name, CTL_HW_NAMES[i].ctl_name, GetsysctlIntValue(mib)]));
     CTLTYPE_QUAD  :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_HW].ctl_name, CTL_HW_NAMES[i].ctl_name, GetsysctlInt64Value(mib)]));
     CTLTYPE_STRING : Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_HW].ctl_name, CTL_HW_NAMES[i].ctl_name, GetsysctlStrValue(mib)]));
    end;
 end;
 Writeln;
end;

procedure  ListUserLevelValues;
var
  mib : TIntegerDynArray;
  i   : Integer;
begin
 mib:=TIntegerDynArray.Create(CTL_USER, 0);
 Writeln('User-level');
 Writeln('----------');
 for i:=0 to USER_MAXID-1 do
 begin
    mib[1]:=i;
    case CTL_USER_NAMES[i].ctl_type of
     CTLTYPE_NODE  :  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_USER].ctl_name, CTL_USER_NAMES[i].ctl_name, '[node]']));
     CTLTYPE_OPAQUE:  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_USER].ctl_name, CTL_USER_NAMES[i].ctl_name, '[structure]']));
     CTLTYPE_INT   :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_USER].ctl_name, CTL_USER_NAMES[i].ctl_name, GetsysctlIntValue(mib)]));
     CTLTYPE_QUAD  :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_USER].ctl_name, CTL_USER_NAMES[i].ctl_name, GetsysctlInt64Value(mib)]));
     CTLTYPE_STRING : Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_USER].ctl_name, CTL_USER_NAMES[i].ctl_name, GetsysctlStrValue(mib)]));
    end;
 end;
 Writeln;
end;

procedure  ListVMValues;
var
  mib : TIntegerDynArray;
  i   : Integer;
begin
 Writeln('Virtual memory');
 Writeln('-------------');
 for i:=0 to VM_MAXID-1 do
 begin
    mib:=TIntegerDynArray.Create(CTL_VM, i);
    case CTL_VM_NAMES[i].ctl_type of
     CTLTYPE_NODE  :  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_VM].ctl_name, CTL_VM_NAMES[i].ctl_name, '[node]']));
     CTLTYPE_OPAQUE:  Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_VM].ctl_name, CTL_VM_NAMES[i].ctl_name, '[structure]']));
     CTLTYPE_INT   :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_VM].ctl_name, CTL_VM_NAMES[i].ctl_name, GetsysctlIntValue(mib)]));
     CTLTYPE_QUAD  :  Writeln(Format('%s.%-18s %d',[CTL_NAMES[CTL_VM].ctl_name, CTL_VM_NAMES[i].ctl_name, GetsysctlInt64Value(mib)]));
     CTLTYPE_STRING : Writeln(Format('%s.%-18s %s',[CTL_NAMES[CTL_VM].ctl_name, CTL_VM_NAMES[i].ctl_name, GetsysctlStrValue(mib)]));
    end;
 end;
 Writeln;
end;

begin
  try
    ListKernelValues;
    ListGenericCPU_IO_Values;
    ListUserLevelValues;
    ListVMValues;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.


3 Comments

Getting System information in OSX and iOS using Delphi (XE2, XE3, XE4) Part 1

In the following article you can learn how to use the sysctl, sysctlbyname and sysctlnametomib functions to get system information (kernel values, hardware, networking, file system, machine specific and user related data) under the OSX and iOS systems.

sysctl

int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

The sysctl allows you retrieves and set system information, the data returned from the sysctl() function consists of integers (int32, int64), strings(AnsiStrings) and structs (records). this function is defined in the Posix.SysSysctl (SysSysctlAPI.inc) unit

Note : The Posix.SysSysctl unit is a partial translation the sysctl.h file.

function sysctl(name: PInteger; namelen: cardinal; oldp: Pointer; oldlen: Psize_t; newp: Pointer; newlen: size_t): Integer; cdecl; external libc name _PU + 'sysctl';

name : This parameter receive a pointer to a Management Information Base (MIB) style value, which is only a array of integers. Each element of this array must be filled with the values related to the info to read or write.

The number of elements of this array, depends of the data to be obtained or modified, most of times only we need to pass and fill a mib with 2 elements (integer values). The first element indicates the level(location) of the info and the second element indicates the value to retrieve.

These are the possible values for the first element of the mib.

           Name           Value            Description
           CTL_DEBUG      $00000005        Debugging
           CTL_VFS        $00000003        File system
           CTL_HW         $00000006        Generic CPU, I/O
           CTL_KERN       $00000001        High kernel limits
           CTL_MACHDEP    $00000007        Machine dependent
           CTL_NET        $00000004        Networking
           CTL_USER       $00000008        User-level
           CTL_VM         $00000002        Virtual memory 

Note: All these values are already defined in the Posix.SysSysctl unit.

So if we want access to the kernel related values we must fill the mib like so

var
  mib : array[0..1] of Integer;
...
...
 mib[0] := CTL_KERN;

The second element value is related to the first level and the possible values are defined in the Posix.SysSysctl unit as well, for example to get the max processes supported by the system we must use the KERN_MAXPROC($00000006) value.

var
  mib : array[0..1] of Integer;
...
...
 mib[0] := CTL_KERN;
 mib[1] := KERN_MAXPROC;

namelen : This parameter is the length of the mib structure.
oldp: Must be filled with a pointer to the buffer to receive. The info which can be a integer, int64, a AnsiString(MarshaledAString) or a record.
oldlen: Indicates the size of the oldp parameter.
newp: This parameter must be filled with a pointer to the buffer to with the info to set up, when you don’t modify the data you must pass a nil value.
newlen: Indicates the size of the newp parameter.

So with all the above info now you can write a function to retrieve the max processes supported, check this sample code which retrieves a integer value.

function MaxProcesses : Integer;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
begin

 mib[0] := CTL_KERN;
 mib[1] := KERN_MAXPROC;

 len := sizeof(Result);
 res:=sysctl(@mib, Length(mib), @Result, @len, nil, 0);
 if res<>0 then
  RaiseLastOSError;
end;

To get a Int64 value the code is very similar, check this sample which get the size of the memory installed on the system using the CTL_HW level and the HW_MEMSIZE value.

function MemSize : Int64;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
begin
 mib[0] := CTL_HW;
 mib[1] := HW_MEMSIZE;

 len := sizeof(Result);
 res := sysctl(@mib, Length(mib), @Result, @len, nil, 0);
 if res<>0 then
  RaiseLastOSError;
end;

If you want retrieve a string value, you must get the length of the value before to allocate the buffer, to do this you must pass a nil value in the oldp parameter like so.

sysctl(@mib, Length(mib), nil, @len, nil, 0)

The next code shows how to get a string(ascii) type using the sysctl function.

function KernelVersion : AnsiString;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
  p   : MarshaledAString;//in XE2 you can use the PAnsiChar type
begin
 mib[0] := CTL_KERN;
 mib[1] := KERN_VERSION;
 //get the length of the buffer 
 res := sysctl(@mib, Length(mib), nil, @len, nil, 0);
 if res<>0 then
   RaiseLastOSError;
 //allocates the buffer
 GetMem(p, len);
 try
   res := sysctl(@mib, Length(mib), p, @len, nil, 0);
   if res<>0 then
     RaiseLastOSError;
   Result:=p;
 finally
   FreeMem(p);
 end;
end;

Finally we can use the sysctl function to retrieve complex structures(records) passing a pointer to the record to hold the data.

Try this sample which get the clock rate values from the kernel.

procedure GetClockInfo;
type
 clockinfo = record
	hz      : Integer;
	tick    : Integer;
	tickadj : Integer;
	stathz  : Integer;
	profhz  : Integer;
  end;

(*
struct clockinfo {
	int	hz;	   	/* clock frequency */
	int	tick;		/* micro-seconds per hz tick */
	int	tickadj;/* clock skew rate for adjtime() */
	int	stathz;		/* statistics clock frequency */
	int	profhz;		/* profiling clock frequency */
};
*)

var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
  clock : clockinfo;
begin
 FillChar(clock, sizeof(clock), 0);
 mib[0] := CTL_KERN;
 mib[1] := KERN_CLOCKRATE;
 len := sizeof(clock);
 res:=sysctl(@mib, Length(mib), @clock, @len, nil, 0);
 if res<>0 then
   RaiseLastOSError;

 Writeln(Format('clock frequency             %d',[clock.hz]));
 Writeln(Format('micro-seconds per hz tick   %d',[clock.tick]));
 Writeln(Format('clock skew rate for adjtime %d',[clock.tickadj]));
 Writeln(Format('statistics clock frequency  %d',[clock.stathz]));
 Writeln(Format('profiling clock frequency   %d',[clock.profhz]));
end;

sysctlbyname

int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

The sysctlbyname works in the same way which the sysctl, the only difference is which the values are accessed using a alias string. because that you don’t need pass a mib structure and length to this function.

function sysctlbyname(Name: MarshaledAString; oldp: Pointer; oldlen: Psize_t; newp: Pointer; newlen: size_t): Integer; cdecl; external libc name _PU + 'sysctlbyname';

name: this parameter is the alias for the info to access and is composed by the level splus the string representation of the value to get, because that you don’t need pass a mib structure.

These are the possible values for the first level element

           Name           string 
           CTL_DEBUG      debug
           CTL_VFS        vfs
           CTL_HW         hw
           CTL_KERN       kern
           CTL_MACHDEP    machdep
           CTL_NET        net
           CTL_USER       user
           CTL_VM         vm

This is a sample list of some of the values which you can use in the name parameter of the sysctlbyname function.

     Name                            Type           
     kern.ostype                     string       
     kern.osrelease                  string       
     kern.osrevision                 integer      
     kern.version                    string       
     kern.maxvnodes                  integer       
     kern.maxproc                    integer       
     kern.maxfiles                   integer       
     kern.argmax                     integer      
     kern.securelevel                integer       
     kern.hostname                   string        
     kern.hostid                     integer       
     kern.clockrate                  struct       
     kern.posix1version              integer      
     kern.ngroups                    integer      
     kern.job_control                integer      
     kern.saved_ids                  integer      
     kern.link_max                   integer      
     kern.max_canon                  integer      
     kern.max_input                  integer      
     kern.name_max                   integer      
     kern.path_max                   integer      
     kern.pipe_buf                   integer      
     kern.chown_restricted           integer      
     kern.no_trunc                   integer      
     kern.vdisable                   integer      
     kern.boottime                   struct       
     vm.loadavg                      struct       
     vm.swapusage                    struct       
     machdep.console_device          dev_t        
     net.inet.ip.forwarding          integer       
     net.inet.ip.redirect            integer       
     net.inet.ip.ttl                 integer       
     net.inet.icmp.maskrepl          integer      
     net.inet.udp.checksum           integer       
     hw.machine                      string       
     hw.model                        string       
     hw.ncpu                         integer      
     hw.byteorder                    integer      
     hw.physmem                      integer      
     hw.usermem                      integer      
     hw.memsize                      integer      
     hw.pagesize                     integer      
     user.cs_path                    string       
     user.bc_base_max                integer      
     user.bc_dim_max                 integer      
     user.bc_scale_max               integer      
     user.bc_string_max              integer      
     user.coll_weights_max           integer      
     user.expr_nest_max              integer      
     user.line_max                   integer      
     user.re_dup_max                 integer      
     user.posix2_version             integer      
     user.posix2_c_bind              integer      
     user.posix2_c_dev               integer      
     user.posix2_char_term           integer      
     user.posix2_fort_dev            integer      
     user.posix2_fort_run            integer      
     user.posix2_localedef           integer      
     user.posix2_sw_dev              integer      
     user.posix2_upe                 integer      

Note : You can get a full list of the supported values running the sysctl -A command from a Terminal.

Finally this code shows how use the SysCtlByName function to retrieve the number of cpus installed.

function NumberOfCPU: Integer;
var
  res : Integer;
  len : size_t;
begin
  len := SizeOf(Result);
  res:=SysCtlByName('hw.ncpu', @Result, @len, nil, 0);
  if res<>0 then
    RaiseLastOSError;
end;

Note : The sysctl function runs in about a third the time as the same request made via the sysctlbyname, so when is possible uses sysctl instead.

sysctlnametomib

int sysctlnametomib(const char *name, int *mibp, size_t *sizep);

The sysctlnametomib function fill a mib structure using a alias a string. this function is intended for use by apps that want to repeatedly use the same variable.

function sysctlnametomib(name: MarshaledAString; mibp: PInteger; sizep: Psize_t): Integer; cdecl; external libc name _PU + 'sysctlnametomib';

name: ASCII representation of the value to retrieve.
mibp: pointer to the mib structure to fill.
sizep: pointer to the length of the mib structure to fill.

var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
begin
  len := Length(mib);
  sysctlnametomib('hw.physicalcpu', @mib, @len);
  //now the mib structure is filled with the proper values to call the sysctl function. 

Error Handling

All the above functions returns a 0 values when the execution was succefull, otherwise an error code is returned, this code can be obtained using the errno (Posix.Errno) or the GetLastError function.

The following errors may be reported (theses values and meanings are defined in the osx/ErrnoTypes.inc file)

     [EFAULT]           The buffer name, oldp, newp, or length pointer oldlenp contains an invalid address.
     [EINVAL]           The name array is less than two or greater than CTL_MAXNAME.
     [EINVAL]           A non-null newp is given and its specified length in newlen is too large or too
                        small.
     [ENOMEM]           The length pointed to by oldlenp is too short to hold the requested value.
     [ENOMEM]           The smaller of either the length pointed to by oldlenp or the estimated size of the
                        returned data exceeds the system limit on locked memory.
     [ENOMEM]           Locking the buffer oldp, or a portion of the buffer if the estimated size of the
                        data to be returned is smaller, would cause the process to exceed its per-process
                        locked memory limit.
     [ENOTDIR]          The name array specifies an intermediate rather than terminal name.
     [EISDIR]           The name array specifies a terminal name, but the actual name is not terminal.
     [ENOENT]           The name array specifies a value that is unknown.
     [EPERM]            An attempt is made to set a read-only value.
     [EPERM]            A process without appropriate privilege attempts to set a value.

This a full sample console app which summarizes this article.

{$APPTYPE CONSOLE}

uses
  //System.Classes,
  //System.Types,
  //Posix.Errno,
  Posix.SysTypes,
  Posix.SysSysctl,
  System.SysUtils;

//https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/sysctl.3.html
//https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/sysctl.8.html


function NumberOfCPU: Integer;
var
  res : Integer;
  len : size_t;
begin
  len := SizeOf(Result);
  res:=SysCtlByName('hw.ncpu', @Result, @len, nil, 0);
  if res<>0 then
    RaiseLastOSError;
end;


function MaxProcesses : Integer;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
begin

 mib[0] := CTL_KERN;
 mib[1] := KERN_MAXPROC;

 len := sizeof(Result);
 res:=sysctl(@mib, Length(mib), @Result, @len, nil, 0);
 if res<>0 then
  RaiseLastOSError;
end;

function MemSize : Int64;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
begin
 mib[0] := CTL_HW;
 mib[1] := HW_MEMSIZE;

 len := sizeof(Result);
 res := sysctl(@mib, Length(mib), @Result, @len, nil, 0);
 if res<>0 then
  RaiseLastOSError;
end;


function KernelVersion : AnsiString;
var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
  p   : MarshaledAString;//in XE2 use  PAnsiChar
begin
 mib[0] := CTL_KERN;
 mib[1] := KERN_VERSION;
 res := sysctl(@mib, Length(mib), nil, @len, nil, 0);
 if res<>0 then
   RaiseLastOSError;
 GetMem(p, len);
 try
   res := sysctl(@mib, Length(mib), p, @len, nil, 0);
   if res<>0 then
     RaiseLastOSError;
   Result:=p;
 finally
   FreeMem(p);
 end;
end;


procedure GetClockInfo;
type
 clockinfo = record
	hz      : Integer;
	tick    : Integer;
	tickadj : Integer;
	stathz  : Integer;
	profhz  : Integer;
  end;

(*
struct clockinfo {
	int	hz;	   	/* clock frequency */
	int	tick;		/* micro-seconds per hz tick */
	int	tickadj;/* clock skew rate for adjtime() */
	int	stathz;		/* statistics clock frequency */
	int	profhz;		/* profiling clock frequency */
};
*)

var
  mib : array[0..1] of Integer;
  res : Integer;
  len : size_t;
  clock : clockinfo;
begin
 FillChar(clock, sizeof(clock), 0);
 mib[0] := CTL_KERN;
 mib[1] := KERN_CLOCKRATE;
 len := sizeof(clock);
 res:=sysctl(@mib, Length(mib), @clock, @len, nil, 0);
 if res<>0 then
   RaiseLastOSError;

 Writeln(Format('clock frequency             %d',[clock.hz]));
 Writeln(Format('micro-seconds per hz tick   %d',[clock.tick]));
 Writeln(Format('clock skew rate for adjtime %d',[clock.tickadj]));
 Writeln(Format('statistics clock frequency  %d',[clock.stathz]));
 Writeln(Format('profiling clock frequency   %d',[clock.profhz]));
end;

begin
  try
    Writeln(Format('max processes     %d',[MaxProcesses]));
    Writeln(Format('number of cpus    %d',[NumberOfCPU]));
    Writeln(Format('physical ram size %s',[FormatFloat('#,', MemSize)]));
    Writeln(Format('Kernel Version    %s',[KernelVersion]));
    GetClockInfo;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.


2 Comments

Vcl Styles Utils updated to fix QC #114040, #114032 (XE2 and XE3)

I just commit in the Vcl Styles Project two new fixes to patch the QC 114040 and QC 114032 (these issues exist in Delphi XE2 and XE3), both reports are related to the Highlight colors used to draw the TColorBox and TComboBoxEx components when the Vcl Styles are active.

QC 114032

As you can see in the below image the TColorBox component doesn’t use the proper highlight color, but the TColorListBox uses the highlight color of the current Vcl Style.

TColorBoxQC

The TColorBox control doesn’t use a Style Hook, so the fix was done using a interposer class. To apply the path just add the Vcl.Styles.Fixes unit to your uses list after of the Vcl.ExtCtrls unit. And the result will be

TColorBoxFix

QC 114040

The TComboBoxEx control have a similar issue.

TcomboboxExQc

In this case fixing the Style Hook related to the TComboBoxEx control was the key.

TcomboboxExFix

To apply this fix, just register the TComboBoxExStyleHookFix style hook located in the Vcl.Styles.Fixes unit.


4 Comments

Added new vcl style hook to the Vcl Styles Utils to fix QC #108678, #108875 (XE2 and XE3)

I just added a new vcl style hook (TListViewStyleHookFix) for the TListView component in the Vcl Styles Utils project to fix the QC #108678, #108875 (XE2 and XE3)

The issue reported in both reports, is that the images are not displayed in the TListView header with the VCL Styles enabled.

When you uses the Windows Theme in a TListView with images in the header will look like so

LVWindows

But if you enable the Vcl Styles, the images in the header are lost.

LVStyles2

The issue is located in the TListViewStyleHook.DrawHeaderSection method, this method must paint the image and text of each section of the header of the ListView.

This is part of the code with the bug

  ...
  ...
  ImageList := SendMessage(Handle, HDM_GETIMAGELIST, 0, 0);
  Item.Mask := HDI_FORMAT or HDI_IMAGE;
  InflateRect(R, -2, -2);
  if (ImageList <> 0) and Header_GetItem(Handle, Index, Item) then
  begin
    if Item.fmt and HDF_IMAGE = HDF_IMAGE then
      ImageList_Draw(ImageList, Item.iImage, Canvas.Handle, R.Left, R.Top, ILD_TRANSPARENT);
    ImageList_GetIconSize(ImageList, IconWidth, IconHeight);
    Inc(R.Left, IconWidth + 5);
  end;
  ...
  ...

The problem with the above code is that the SendMessage function with the HDM_GETIMAGELIST message (which is used to get the current imagelist) is not using the proper Handle. The above code is passing the handle of the ListView, but must pass the handle of the Header control, the same applies to the call to the Header_GetItem method.

The TListViewStyleHookFix introduces a new DrawHeaderSection method which passes the handle of the header control and fix the issue. You can use this Stylehook adding Vcl.Styles.Fixes unit to you uses clause and then register the hook on this way.

initialization
   TStyleManager.Engine.RegisterStyleHook(TListView, TListViewStyleHookFix);

LVStylesFix


6 Comments

Using custom colors in the TDBGrid columns with vcl styles enabled

The TDBGrid component allows you to customize the colors of the columns and fonts used to draw the data.

Unfortunately if you uses the vcl styles all these customizations are lost

This issue is caused because the TCustomDBGrid.DrawCell method ignores the custom colors of the columns when the vcl styles are enabled. So the solution is patch this method to allow use the proper colors. After of this you will get a result like so.

I just uploaded this patch as part of the vcl styles utils project. To use it you must add the Vcl.Styles.DbGrid unit to the uses part of your form after of the Vcl.DBGrids unit.


2 Comments

Added new unit to the vcl style utils to fix the QC #103708, #107764 reports

I just uploaded a new unit to the vcl style utils project called Vcl.Styles.Fixes, this unit contains the TButtonStyleHook style hook which fix these QC #103708, #107764 reports for Delphi XE2.

Note : The QC #103708 still exist in Delphi XE2 Update 4, even if appears as resolved (XE3 maybe?)


4 Comments

A quick guide to evaluate and compile expressions using the LiveBindings expression evaluator.

The LiveBindings technology introduced in Delphi XE2, includes a set of interfaces, classes and methods to evaluate and compile expressions.

Today I will show you how you can use these classes to build, compile and evaluate simple (and complex) expressions. You can use these expressions for example to define formulas to calculate taxes, generate hashes, encrypt data or use in any situation where you need calculate a value where the values or factors may change (anyway the possibilities are endless), Also you can store these expressions in a XML file or a database and use them as needed.

Note: In this article is not used the TBindingExpression class directly, instead are used the raw classes and methods of the of livebindings expression evaluator, because you can gain much more flexibility to build your expressions.

Before to begin is necessary know the basic elements to build, compile and evaluate an expression.

  • The IScope is the base Interface to hold the objects used to make the evaluation and compilation, here you store the methods , classes and values which will be used to build the expression.
  • The Compile method located in the System.Bindings.Evaluator unit, is used to compile the expression using a IScope interface, this method will return a ICompiledBinding interface which can be used to evaluate and get the result of the compilation.
  • The ICompiledBinding interface allows the evaluation of the compiled expression.
  • The TNestedScope class allows you to merge IScopes.

Basic Example

The more basic expression which you can use is based in the BasicOperators Scope (located in the System.Bindings.EvalSys unit), this allows you evaluate only numbers and the basic arithmetic operations, check this sample.

{$APPTYPE CONSOLE}

uses
  System.Rtti,
  System.Bindings.EvalProtocol,
  System.Bindings.Evaluator,
  System.Bindings.EvalSys,
  System.SysUtils;

procedure DoIt;
Var
  LScope : IScope;
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
begin
  LScope:= BasicOperators;
  LCompiledExpr:= Compile('((1+2+3+4)*(25/5))-(10)', LScope);
  LResult:=LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
  if not LResult.IsEmpty then
    Writeln(LResult.ToString);
end;

begin
 try
    DoIt;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.

Registering a Constant

Now if you need evaluate the value of a constant you must create a IScope descendent and add the constants to register, finally you must merge the new scope with the original using the TNestedScope.


{$APPTYPE CONSOLE}

uses
  System.Rtti,
  System.Bindings.EvalProtocol,
  System.Bindings.Evaluator,
  System.Bindings.EvalSys,
  System.SysUtils;

procedure DoIt;
Var
  LScope : IScope;
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
  LDictionaryScope: TDictionaryScope;
begin
  LScope:= TNestedScope.Create(BasicOperators, BasicConstants);
  LDictionaryScope := TDictionaryScope.Create;
  //add a set of constants to the Scope
  LDictionaryScope.Map.Add('MinsPerHour', TValueWrapper.Create(MinsPerHour));
  LDictionaryScope.Map.Add('MinsPerDay', TValueWrapper.Create(MinsPerDay));
  LDictionaryScope.Map.Add('MSecsPerSec', TValueWrapper.Create(MSecsPerSec));
  LDictionaryScope.Map.Add('MSecsPerDay', TValueWrapper.Create(MSecsPerDay));

  //merge the scopes
  LScope:= TNestedScope.Create(LScope, LDictionaryScope);

  LCompiledExpr:= Compile('MinsPerHour*24', LScope);
  LResult:=LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
  if not LResult.IsEmpty then
    Writeln(LResult.ToString);

end;

begin
 try
    DoIt;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.

In the above code the Scope is initialized using the TNestedScope class to merge the BasicOperators and BasicConstants (this Scope define the values True, False, nil, and Pi) scopes

  LScope:= TNestedScope.Create(BasicOperators, BasicConstants);

Tip 1 : The constants and identifiers in the expression are case-sensitive.

Using Methods

The livebindings expression evaluator include a set of basic methods (ToStr, ToVariant, ToNotifyEvent, Round, Format, UpperCase, LowerCase, FormatDateTime, StrToDateTime, Math_Min, Math_Max) which can be used in our expressions, these are defined in the System.Bindings.Methods unit and must be accessed the TBindingMethodsFactory class.

Check this sample code which uses the Format function.

{$APPTYPE CONSOLE}

uses
  System.Rtti,
  System.Bindings.EvalProtocol,
  System.Bindings.Evaluator,
  System.Bindings.EvalSys,
  System.Bindings.Methods,
  System.SysUtils;

procedure DoIt;
Var
  LScope : IScope;
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
  LDictionaryScope: TDictionaryScope;
begin
  LScope:= TNestedScope.Create(BasicOperators, BasicConstants);
  //add the registered methods
  LScope := TNestedScope.Create(LScope, TBindingMethodsFactory.GetMethodScope);
  LCompiledExpr:= Compile('Format("%s using the function %s, this function can take numbers like %d or %n as well","This is a formated string","Format",36, Pi)', LScope);
  LResult:=LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
  if not LResult.IsEmpty then
    Writeln(LResult.ToString);
end;

begin
 try
    DoIt;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.

Tip 2 : The strings in the expressions can be surrounded in double or single quotes.

Registering a Custom Method

Most of the times when you build an expression, you will need register a custom method, this can be easily done using the TBindingMethodsFactory class.

The first step is create a function wich returns a IInvokable interface. For this you can use the MakeInvokable method and then you write the implementation of your function as an anonymous method.

Finally using the TBindingMethodsFactory.RegisterMethod function you can register the custom method.

Check this sample code which implement a custom function called IfThen :)

{$APPTYPE CONSOLE}

uses
  System.Rtti,
  System.TypInfo,
  System.Bindings.Consts,
  System.Bindings.EvalProtocol,
  System.Bindings.Evaluator,
  System.Bindings.EvalSys,
  System.Bindings.Methods,
  System.SysUtils;

{
function IfThen(AValue: Boolean; const ATrue: Integer; const AFalse: Integer): Integer;
function IfThen(AValue: Boolean; const ATrue: Int64; const AFalse: Int64): Int64;
function IfThen(AValue: Boolean; const ATrue: UInt64; const AFalse: UInt64): UInt64;
function IfThen(AValue: Boolean; const ATrue: Single; const AFalse: Single): Single;
function IfThen(AValue: Boolean; const ATrue: Double; const AFalse: Double): Double;
function IfThen(AValue: Boolean; const ATrue: Extended; const AFalse: Extended): Extended;
}
function IfThen: IInvokable;
begin
  Result := MakeInvokable(
    function(Args: TArray<IValue>): IValue
      var
        IAValue: IValue;
        AValue: Boolean;
        IATrue, IAFalse: IValue;
     begin
        //check the number of passed parameters
        if Length(Args) <> 3 then
          raise EEvaluatorError.Create(sFormatArgError);

         IAValue:=Args[0];
         IATrue :=Args[1];
         IAFalse:=Args[2];

         //check if the parameters has values
         if IATrue.GetValue.IsEmpty or IAFalse.GetValue.IsEmpty then
          Exit(TValueWrapper.Create(nil))
         else
         //check if the parameters has the same types
         if IATrue.GetValue.Kind<>IAFalse.GetValue.Kind then
          raise EEvaluatorError.Create('The return values must be of the same type')
         else
         //check if the first parameter is boolean
         if (IAValue.GetType.Kind=tkEnumeration) and (IAValue.GetValue.TryAsType<Boolean>(AValue)) then //Boolean is returned as tkEnumeration
         begin
           if AValue then
            //return the value for True condition
            Exit(TValueWrapper.Create(IATrue.GetValue))
           else
            //return the value for the False condition
            Exit(TValueWrapper.Create(IAFalse.GetValue))
         end
         else raise EEvaluatorError.Create('The first parameter must be a boolean expression');
     end
     );
end;

procedure DoIt;
Var
  LScope : IScope;
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
  LDictionaryScope: TDictionaryScope;
begin
  LScope:= TNestedScope.Create(BasicOperators, BasicConstants);

    //add a custom method
    TBindingMethodsFactory.RegisterMethod(
        TMethodDescription.Create(
          IfThen,
          'IfThen',
          'IfThen',
          '',
          True,
          '',
          nil));

  //add the registered methods
  LScope := TNestedScope.Create(LScope, TBindingMethodsFactory.GetMethodScope);
  LCompiledExpr:= Compile('Format("The sentence is %s", IfThen(1>0,"True","False"))', LScope);
  LResult:=LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
  if not LResult.IsEmpty then
    Writeln(LResult.ToString);
end;

begin
 try
    DoIt;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.

Tip 3 : Remember use TBindingMethodsFactory.UnRegisterMethod function to unregister your custom method.

Registering a Class

In order to use your own class in an expression you must create a Scope using the TObjectWrapper class or the WrapObject method.

{$APPTYPE CONSOLE}

uses
  System.Rtti,
  System.TypInfo,
  System.Bindings.Consts,
  System.Bindings.EvalProtocol,
  System.Bindings.Evaluator,
  System.Bindings.EvalSys,
  System.Bindings.ObjEval,
  System.SysUtils;

Type
 TMyClass= class
  function Random(Value:Integer): Integer;
 end;

{ TMyClass }
function TMyClass.Random(Value:Integer): Integer;
begin
  Result:=System.Random(Value);
end;

procedure DoIt;
Var
  LScope : IScope;
  LCompiledExpr : ICompiledBinding;
  LResult : TValue;
  LDictionaryScope: TDictionaryScope;
  M : TMyClass;
begin
  M := TMyClass.Create;
  try
    LScope:= TNestedScope.Create(BasicOperators, BasicConstants);
    //add a object
    LDictionaryScope := TDictionaryScope.Create;
    LDictionaryScope.Map.Add('M', WrapObject(M));
    LScope := TNestedScope.Create(LScope, LDictionaryScope);
    LCompiledExpr:= Compile('M.Random(10000000)', LScope);
    LResult:=LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
    if not LResult.IsEmpty then
      Writeln(LResult.ToString);
  finally
    M.Free;
  end;
end;

begin
 try
    Randomize;
    DoIt;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.


4 Comments

Getting the environment variables of an external x86 and x64 process

On my last post was show how get the environment variables of an external x86 process, now it’s time to show how access the same information for x64 (and x86) process from a 64 bits application.

In order to access the environment variables of a x64 process, you must use a 64 bits application, So if you compile the code posted in the last article in a 64 bits app, the code will still working but only for read the environment variables of x64 processes, this is because the offset of the fields of the records changes due to the Pointer and THandle types now have a 8 bytes size, and the structures which hold the data in a 32 bits process still using 4 bytes to represent the adresses (Pointers) and Handles (THandle). So the first step is create 2 new types to replace the Pointer and THandle types like so.

  Pointer32 = ULONG;
  THANDLE32 = ULONG;

Now using these new types we can create a 32 bits version of the records to access the PEB.

type
  Pointer32 = ULONG;
  THANDLE32 = ULONG;

  _UNICODE_STRING32 = record
    Length: Word;
    MaximumLength: Word;
    Buffer: Pointer32;
  end;
  UNICODE_STRING32 = _UNICODE_STRING32;

  _RTL_DRIVE_LETTER_CURDIR32 = record
    Flags: Word;
    Length: Word;
    TimeStamp: ULONG;
    DosPath: UNICODE_STRING32;
  end;
  RTL_DRIVE_LETTER_CURDIR32 = _RTL_DRIVE_LETTER_CURDIR32;

   _CURDIR32 = record
    DosPath: UNICODE_STRING32;
    Handle: THANDLE32;
  end;
  CURDIR32 = _CURDIR32;

  _RTL_USER_PROCESS_PARAMETERS32 = record
    MaximumLength: ULONG;
    Length: ULONG;
    Flags: ULONG;
    DebugFlags: ULONG;
    ConsoleHandle: THANDLE32;
    ConsoleFlags: ULONG;
    StandardInput: THANDLE32;
    StandardOutput: THANDLE32;
    StandardError: THANDLE32;
    CurrentDirectory: CURDIR32;
    DllPath: UNICODE_STRING32;
    ImagePathName: UNICODE_STRING32;
    CommandLine: UNICODE_STRING32;
    Environment: Pointer32;
    StartingX: ULONG;
    StartingY: ULONG;
    CountX: ULONG;
    CountY: ULONG;
    CountCharsX: ULONG;
    CountCharsY: ULONG;
    FillAttribute: ULONG;
    WindowFlags: ULONG;
    ShowWindowFlags: ULONG;
    WindowTitle: UNICODE_STRING32;
    DesktopInfo: UNICODE_STRING32;
    ShellInfo: UNICODE_STRING32;
    RuntimeData: UNICODE_STRING32;
    CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR32;
  end;
  RTL_USER_PROCESS_PARAMETERS32 = _RTL_USER_PROCESS_PARAMETERS32;
  PRTL_USER_PROCESS_PARAMETERS32 = ^RTL_USER_PROCESS_PARAMETERS32;

  _PEB32 = record
    Reserved1     : array [0..1] of Byte;
    BeingDebugged : Byte;
    Reserved2     : Byte;
    Reserved3     : array [0..1] of Pointer32;
    Ldr           : Pointer32;
    ProcessParameters : Pointer32;//PRTL_USER_PROCESS_PARAMETERS;
    Reserved4     : array [0..102] of Byte;
    Reserved5     : array [0..51] of Pointer32;
    PostProcessInitRoutine : Pointer32;
    Reserved6     : array [0..127] of byte;
    Reserved7     : Pointer32;
    SessionId     : ULONG;
  end;
   PEB32=_PEB32;

Reading the PEB Address of a X86 process from a 64 bits app

Additional to the above changes we must modify the code to get the PEB address of a 32 bits process, this can be done using the NtQueryInformationProcess function passing ProcessWow64Information value, this will return the PEB Adresss of a process running under WOW64.

  NtQueryInformationProcess(ProcessHandle, ProcessWow64Information, @PEBBaseAddress32, SizeOf(PEBBaseAddress32), nil)

And now using the ReadProcessMemory function and the new defined types we can read the environment variables block.

  //read the PEB structure
  if not ReadProcessMemory(ProcessHandle, PEBBaseAddress32, @Peb32, sizeof(Peb32), lpNumberOfBytesRead) then
    RaiseLastOSError
  else
  begin
    //read the RTL_USER_PROCESS_PARAMETERS structure
    if not ReadProcessMemory(ProcessHandle, Pointer(Peb32.ProcessParameters), @Rtl32, SizeOf(Rtl32), lpNumberOfBytesRead) then
     RaiseLastOSError
    else
    begin
       //get the size of the Env. variables block
       if VirtualQueryEx(ProcessHandle, Pointer(Rtl32.Environment), Mbi, SizeOf(Mbi))=0 then
        RaiseLastOSError
       else
       EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Pointer(Rtl32.Environment)) - ULONG_PTR(mbi.BaseAddress)));

       SetLength(EnvStrBlock, EnvStrLength);
       //read the content of the env. variables block
       if not ReadProcessMemory(ProcessHandle, Pointer(Rtl32.Environment), @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then
        RaiseLastOSError
       else
       Result:=TEncoding.Unicode.GetString(EnvStrBlock);
    end;
  end;

The source code

Finally this is the full source code.

program NtQueryInformationProcess_EnvVarsX64;
//Author Rodrigo Ruz (RRUZ)
//2012-06-09
{$APPTYPE CONSOLE}

{$IFNDEF UNICODE} this code only runs under unicode delphi versions{$ENDIF}
{$R *.res}
uses
  Classes,
  SysUtils,
  Windows;

type
  Pointer32 = ULONG;
  THANDLE32 = ULONG;

  _UNICODE_STRING = record
    Length: Word;
    MaximumLength: Word;
    Buffer: LPWSTR;
  end;
  UNICODE_STRING = _UNICODE_STRING;

  //http://msdn.microsoft.com/en-us/library/windows/desktop/ms684280%28v=vs.85%29.aspx
  PROCESS_BASIC_INFORMATION = record
    Reserved1 : Pointer;
    PebBaseAddress: Pointer;
    Reserved2: array [0..1] of Pointer;
    UniqueProcessId: ULONG_PTR;
    Reserved3: Pointer;
  end;


  //http://undocumented.ntinternals.net/UserMode/Structures/RTL_DRIVE_LETTER_CURDIR.html
  _RTL_DRIVE_LETTER_CURDIR = record
    Flags: Word;
    Length: Word;
    TimeStamp: ULONG;
    DosPath: UNICODE_STRING;
  end;
  RTL_DRIVE_LETTER_CURDIR = _RTL_DRIVE_LETTER_CURDIR;

   _CURDIR = record
    DosPath: UNICODE_STRING;
    Handle: THANDLE;
  end;
  CURDIR = _CURDIR;

 //http://undocumented.ntinternals.net/UserMode/Structures/RTL_USER_PROCESS_PARAMETERS.html
  _RTL_USER_PROCESS_PARAMETERS = record
    MaximumLength: ULONG;
    Length: ULONG;
    Flags: ULONG;
    DebugFlags: ULONG;
    ConsoleHandle: THANDLE;
    ConsoleFlags: ULONG;
    StandardInput: THANDLE;
    StandardOutput: THANDLE;
    StandardError: THANDLE;
    CurrentDirectory: CURDIR;
    DllPath: UNICODE_STRING;
    ImagePathName: UNICODE_STRING;
    CommandLine: UNICODE_STRING;
    Environment: Pointer;
    StartingX: ULONG;
    StartingY: ULONG;
    CountX: ULONG;
    CountY: ULONG;
    CountCharsX: ULONG;
    CountCharsY: ULONG;
    FillAttribute: ULONG;
    WindowFlags: ULONG;
    ShowWindowFlags: ULONG;
    WindowTitle: UNICODE_STRING;
    DesktopInfo: UNICODE_STRING;
    ShellInfo: UNICODE_STRING;
    RuntimeData: UNICODE_STRING;
    CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR;
  end;
  RTL_USER_PROCESS_PARAMETERS = _RTL_USER_PROCESS_PARAMETERS;
  PRTL_USER_PROCESS_PARAMETERS = ^RTL_USER_PROCESS_PARAMETERS;

  _PEB = record
    Reserved1     : array [0..1] of Byte;
    BeingDebugged : Byte;
    Reserved2     : Byte;
    Reserved3     : array [0..1] of Pointer;
    Ldr           : Pointer;
    ProcessParameters : PRTL_USER_PROCESS_PARAMETERS;
    Reserved4     : array [0..102] of Byte;
    Reserved5     : array [0..51] of Pointer;
    PostProcessInitRoutine : Pointer;
    Reserved6     : array [0..127] of byte;
    Reserved7     : Pointer;
    SessionId     : ULONG;
  end;
   PEB=_PEB;

{$IFDEF CPUX64}
  _UNICODE_STRING32 = record
    Length: Word;
    MaximumLength: Word;
    Buffer: Pointer32;
  end;
  UNICODE_STRING32 = _UNICODE_STRING32;

  _RTL_DRIVE_LETTER_CURDIR32 = record
    Flags: Word;
    Length: Word;
    TimeStamp: ULONG;
    DosPath: UNICODE_STRING32;
  end;
  RTL_DRIVE_LETTER_CURDIR32 = _RTL_DRIVE_LETTER_CURDIR32;

   _CURDIR32 = record
    DosPath: UNICODE_STRING32;
    Handle: THANDLE32;
  end;
  CURDIR32 = _CURDIR32;

  _RTL_USER_PROCESS_PARAMETERS32 = record
    MaximumLength: ULONG;
    Length: ULONG;
    Flags: ULONG;
    DebugFlags: ULONG;
    ConsoleHandle: THANDLE32;
    ConsoleFlags: ULONG;
    StandardInput: THANDLE32;
    StandardOutput: THANDLE32;
    StandardError: THANDLE32;
    CurrentDirectory: CURDIR32;
    DllPath: UNICODE_STRING32;
    ImagePathName: UNICODE_STRING32;
    CommandLine: UNICODE_STRING32;
    Environment: Pointer32;
    StartingX: ULONG;
    StartingY: ULONG;
    CountX: ULONG;
    CountY: ULONG;
    CountCharsX: ULONG;
    CountCharsY: ULONG;
    FillAttribute: ULONG;
    WindowFlags: ULONG;
    ShowWindowFlags: ULONG;
    WindowTitle: UNICODE_STRING32;
    DesktopInfo: UNICODE_STRING32;
    ShellInfo: UNICODE_STRING32;
    RuntimeData: UNICODE_STRING32;
    CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR32;
  end;
  RTL_USER_PROCESS_PARAMETERS32 = _RTL_USER_PROCESS_PARAMETERS32;
  PRTL_USER_PROCESS_PARAMETERS32 = ^RTL_USER_PROCESS_PARAMETERS32;

  _PEB32 = record
    Reserved1     : array [0..1] of Byte;
    BeingDebugged : Byte;
    Reserved2     : Byte;
    Reserved3     : array [0..1] of Pointer32;
    Ldr           : Pointer32;
    ProcessParameters : Pointer32;//PRTL_USER_PROCESS_PARAMETERS;
    Reserved4     : array [0..102] of Byte;
    Reserved5     : array [0..51] of Pointer32;
    PostProcessInitRoutine : Pointer32;
    Reserved6     : array [0..127] of byte;
    Reserved7     : Pointer32;
    SessionId     : ULONG;
  end;
   PEB32=_PEB32;
{$ENDIF}
  function  NtQueryInformationProcess(ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll';
  function  NtQueryVirtualMemory(ProcessHandle : THandle; BaseAddress : Pointer;  MemoryInformationClass : DWORD;  MemoryInformation : Pointer;  MemoryInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll';

type
  TIsWow64Process = function(Handle:THandle; var IsWow64 : BOOL) : BOOL; stdcall;
var
  _IsWow64Process  : TIsWow64Process;

procedure Init_IsWow64Process;
var
  hKernel32      : Integer;
begin
  hKernel32 := LoadLibrary(kernel32);
  if (hKernel32 = 0) then RaiseLastOSError;
  try
    _IsWow64Process := GetProcAddress(hkernel32, 'IsWow64Process');
  finally
    FreeLibrary(hKernel32);
  end;
end;

function ProcessIsX64(hProcess: DWORD): Boolean;
var
  IsWow64        : BOOL;
begin
  Result := False;
  {$IFNDEF CPUX64}
    exit;
  {$ENDIF}
  if not Assigned(_IsWow64Process) then
   Init_IsWow64Process;

  if Assigned(_IsWow64Process) then
  begin
    if (_IsWow64Process(hProcess, IsWow64)) then
      Result := not IsWow64
    else
      RaiseLastOSError;
  end;
end;

function GetEnvVarsPid(dwProcessId : DWORD): string;
const
  STATUS_SUCCESS             = $00000000;
  SE_DEBUG_NAME              = 'SeDebugPrivilege';
  ProcessWow64Information    = 26;
var
  ProcessHandle        : THandle;
  ProcessBasicInfo     : PROCESS_BASIC_INFORMATION;
  ReturnLength         : DWORD;
  lpNumberOfBytesRead  : ULONG_PTR;
  TokenHandle          : THandle;
  lpLuid               : TOKEN_PRIVILEGES;
  OldlpLuid            : TOKEN_PRIVILEGES;

  Rtl : RTL_USER_PROCESS_PARAMETERS;
  Mbi : TMemoryBasicInformation;
  Peb : _PEB;
  EnvStrBlock  : TBytes;
  EnvStrLength : ULONG;
  IsProcessx64 : Boolean;
  {$IFDEF CPUX64}
  PEBBaseAddress32 : Pointer;
  Peb32 : _PEB32;
  Rtl32 : RTL_USER_PROCESS_PARAMETERS32;
  {$ENDIF}
begin
  Result:='';
  if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
  begin
    try
      if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then
        RaiseLastOSError
      else
      begin
        lpLuid.PrivilegeCount := 1;
        lpLuid.Privileges[0].Attributes  := SE_PRIVILEGE_ENABLED;
        ReturnLength := 0;
        OldlpLuid    := lpLuid;
        //Set the SeDebugPrivilege privilege
        if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError;
      end;

      ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, dwProcessId);
      if ProcessHandle=0 then RaiseLastOSError
      else
      try
        IsProcessx64 :=ProcessIsX64(ProcessHandle);

        {$IFNDEF CPUX64}
        if IsProcessx64 then
          raise Exception.Create('Only 32 bits processes are supported');
        {$ENDIF}

        {$IFDEF CPUX64}
        if IsProcessx64 then
        begin
        {$ENDIF}
          // get the PROCESS_BASIC_INFORMATION to access to the PEB Address
          if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, SizeOf(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then
          begin
            //read the PEB struture
            if not ReadProcessMemory(ProcessHandle, ProcessBasicInfo.PEBBaseAddress, @Peb, sizeof(Peb), lpNumberOfBytesRead) then
              RaiseLastOSError
            else
            begin
              //read the RTL_USER_PROCESS_PARAMETERS structure
              if not ReadProcessMemory(ProcessHandle, Peb.ProcessParameters, @Rtl, SizeOf(Rtl), lpNumberOfBytesRead) then
               RaiseLastOSError
              else
              begin
                 //get the size of the Env. variables block
                 if VirtualQueryEx(ProcessHandle, Rtl.Environment, Mbi, SizeOf(Mbi))=0 then
                  RaiseLastOSError
                 else
                 EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Rtl.Environment) - ULONG_PTR(mbi.BaseAddress)));

                 SetLength(EnvStrBlock, EnvStrLength);
                 //read the content of the env. variables block
                 if not ReadProcessMemory(ProcessHandle, Rtl.Environment, @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then
                  RaiseLastOSError
                 else
                 Result:=TEncoding.Unicode.GetString(EnvStrBlock);
              end;
            end;
          end
          else
          RaiseLastOSError;
        {$IFDEF CPUX64}
        end
        else
        begin
          //get the PEB address
          if  NtQueryInformationProcess(ProcessHandle, ProcessWow64Information, @PEBBaseAddress32, SizeOf(PEBBaseAddress32), nil)=STATUS_SUCCESS then
          begin
            //read the PEB structure
            if not ReadProcessMemory(ProcessHandle, PEBBaseAddress32, @Peb32, sizeof(Peb32), lpNumberOfBytesRead) then
              RaiseLastOSError
            else
            begin
              //read the RTL_USER_PROCESS_PARAMETERS structure
              if not ReadProcessMemory(ProcessHandle, Pointer(Peb32.ProcessParameters), @Rtl32, SizeOf(Rtl32), lpNumberOfBytesRead) then
               RaiseLastOSError
              else
              begin
                 //get the size of the Env. variables block
                 if VirtualQueryEx(ProcessHandle, Pointer(Rtl32.Environment), Mbi, SizeOf(Mbi))=0 then
                  RaiseLastOSError
                 else
                 EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Pointer(Rtl32.Environment)) - ULONG_PTR(mbi.BaseAddress)));

                 SetLength(EnvStrBlock, EnvStrLength);
                 //read the content of the env. variables block
                 if not ReadProcessMemory(ProcessHandle, Pointer(Rtl32.Environment), @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then
                  RaiseLastOSError
                 else
                 Result:=TEncoding.Unicode.GetString(EnvStrBlock);
              end;
            end;
          end
          else
          RaiseLastOSError;
        end;
       {$ENDIF}
      finally
        CloseHandle(ProcessHandle);
      end;
    finally
      CloseHandle(TokenHandle);
    end;
  end
  else
  RaiseLastOSError;
end;

function GetEnvVarsPidList(dwProcessId : DWORD): TStringList;
var
  PEnvVars: PChar;
  PEnvEntry: PChar;
begin
  Result:=TStringList.Create;
  PEnvVars := PChar(GetEnvVarsPid(dwProcessId));
  PEnvEntry := PEnvVars;
  while PEnvEntry^ <> #0 do
  begin
    Result.Add(PEnvEntry);
    Inc(PEnvEntry, StrLen(PEnvEntry) + 1);
  end;
end;

Var
  EnvVars : TStringList;
begin
  ReportMemoryLeaksOnShutdown:=True;
 try
   //Pass a valid pid here
   EnvVars:=GetEnvVarsPidList(5732);
   try
     Writeln(EnvVars.Text);
   finally
     EnvVars.Free;
   end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

Recommended resources