Wednesday, July 20, 2011

Property references in Delphi - possible?

What is the worst thing about how bindings can be realized in Delphi? One may argue but I think it's the magic strings specifying what property to bind. I cannot think of any solution that could get rid of that. Using magic strings (and not only in that case) makes your software vulnerable to certain problems. Just mentioning a few:
  • refactoring may break your code
  • typos may break your code
  • no possible compiler checks for correctness
  • no code completion support
Well, a few versions ago we got method references. You prolly know how they are implemented behind the scenes but let's take a quick look at them. You can do something like (as defined in SysUtils):
type
  TFunc<T> = reference to function: T;
As you also may know this is a reference counted type. Guess how it is implemented? Correct, with an interface. This is similar to:
type
  IFunc<T> = interface
    function Invoke(): T;
  end;
You can even use those method reference types as Interface when you implement a class:
type
  TMyFuncClass = class(TInterfacedObject, TFunc<Integer>)
  public
    function Invoke: Integer;
  end;
Or you can inherit your interface from it (and of course implement that interface into your class):
type
  IFunc<T> = interface(TFunc<T>)
  end;
Ok, but we were talking about properties, right? Well a property basically is not much more than the combination of a function and a procedure - the getter and the setter.
We could define it like this:
type
  IProp<T> = interface
    function GetValue: T;
    procedure SetValue(Value: string);
    property Value: T read GetValue write SetValue;
  end;
So it is nothing more than (of course this does not work):
type
  IProp<T> = interface(TFunc<T>, TProc<T>)
    property Value: T read Invoke write Invoke;
  end;
But what you can do is this:
type
  TProp<T> = class(TInterfacedObject, TFunc<T>, TProc<T>)
  private
    function Invoke: T; overload;
    procedure Invoke(Value: T); overload;
  public
    property Value: T read Invoke write Invoke;
  end;
We can take this and use it as property reference passing it around, getting and setting values using RTTI but there we are again, how to create an instance? Yes, specifying the instance and the property by string. Another possibility would be with anonymous methods wrapping the property.

How about some built in type like this?
type
  TProp<T> = reference to property: T;
So I could write something like this:
procedure ShowAndInc(AProp: TProp<NativeInt>);
begin
  ShowMessageFmt('%d', [AProp]);
  AProp := AProp + 1; 
end;

procedure Form1Button1Click(Sender: TObject);
begin
  ShowAndInc(Self.Tag);
end;

What the compiler actually needs to do is something like this (ignoring possible read or write only properties for now):
ShowAndInc(TProp<NativeInt>.Create(
  function: NativeInt
  begin
    Result := Self.Tag;
  end,
  procedure(Value: NativeInt)
  begin
    Self.Tag := Value;
  end));
This is the code of my actual working TProp class and interface:
type
  IProp<T> = interface
    function Invoke: T; overload;
    procedure Invoke(Value: T); overload;
    property Value: T read Invoke write Invoke;
  end;

  TProp<T> = class(TInterfacedObject, IProp<T>)
  private
    FGetter: TFunc<T>;
    FSetter: TProc<T>;
    function Invoke: T; overload;
    procedure Invoke(Value: T); overload;
  public
    constructor Create(Getter: TFunc<T>; Setter: TProc<T>);
    property Value: T read Invoke write Invoke;
  end;

{ TProp<T> }

constructor TProp<T>.Create(Getter: TFunc<T>; Setter: TProc<T>);
begin
  FGetter := Getter;
  FSetter := Setter;
end;

function TProp<T>.Invoke: T;
begin
  Result := FGetter;
end;

procedure TProp<T>.Invoke(Value: T);
begin
  FSetter(Value);
end;
The compiler could do it way better by directly putting the call to the getter and setter into the right places.

So what do you think? Do you want reference to property in one of the next Delphi versions? Any drawbacks I am not seeing right now?

Friday, July 8, 2011

Unattended Delphi installation - how?

Some while ago I was looking for a possibility to setup Delphi with certain customization but without having to enter anything or watching the PC during setup to confirm any dialog.

Unfortunately there is no documentation on the possible and required parameters for the setup. And I have to admit that I am not familiar with InstallAware (which is used since Delphi 2007). But there are some posts which got me on the right track. One of them explains how to get started.

I always install Delphi into the default directory, so no need to specify the TARGETDIR, but if you wish, you can. So we get the following:
Setup.exe /s LANGUAGE=English KEY1=XXXX KEY2=XXXXXX KEY3=XXXXXX KEY4=XXXX
Possible values for LANGUAGE are: English, German, French or Japanese (actually those you had to choose from at the very beginning of the setup until now). KEY1-4 are the 4 pieces of your serial that you normally have to enter in the setup.

So this is pretty much the minimum configuration and you can install Delphi... well and C++ Builder and all the third party tools that ship with RAD Studio and are checked by default. And actually some of them run as external setup that requires user interaction.

Unfortunately I could not find a complete list of all variables you can provide. When you run the setup.exe it extracts another setup.exe and if you open that with a text editor you can find out those variables by looking through the script code. Someone who is familiar with creating setup might explain this better. But at least we know some of the variable names now. I will list some of them I used. All variables are provided in form name=value.

Languages that will be installed (which you can change later by using BDSSetLang.exe): DE, EN, FR and JA (possible values: True or False)

You can change the values for USERNAME and USERCOMPANY which are those specified in windows by default.

To specify which personality to install you set PERSON_DW32 and PERSON_CPPB (both True by default).

Delphi also installs with a handful of 3rd party tools and components. You can specify them by setting (all True by default):
INSTALL_AQTIME
INSTALL_CODESITE
INSTALL_FINALBUILDER
INSTALL_RAVE_REPORTS
INSTALL_SVNCLIENT
If you want to install some of them in silent mode set the following to True:
AQTIME_SILENT
CODESITE_SILENT
FINALBUILDER_SILENT
RAVE_SILENT
SVNCLIENT_SILENT
Other options I use (True or False):
INSTALL_FEATURE_DatabaseDesktop
INSTALL_FEATURE_Indy
INSTALL_FEATURE_IntraWeb
INSTALL_FEATURE_SampleDataFiles
INSTALL_FEATURE_SamplePrograms
INSTALL_HELP
There are lots of other variables to specify much more but I did not touch them so far. If you are interested look for INSTALL_FEATURE_.

So finally my setup.bat looks as follows (when I use it at work I also add the company name):
Setup.exe /s LANGUAGE=English EN=TRUE DE=TRUE KEY1=XXXX KEY2=XXXXXX KEY3=XXXXXX
KEY4=XXXX USERNAME="Stefan Glienke" USERCOMPANY="" PERSON_DW32=TRUE PERSON_CPPB=FALSE
INSTALL_AQTIME=FALSE INSTALL_CODESITE=FALSE INSTALL_FINALBUILDER=FALSE
INSTALL_HELP=FALSE INSTALL_RAVE_REPORTS=FALSE INSTALL_SVNCLIENT=FALSE
INSTALL_FEATURE_DatabaseDesktop=FALSE INSTALL_FEATURE_IntraWeb=FALSE
INSTALL_FEATURE_SampleDataFiles=FALSE INSTALL_FEATURE_SamplePrograms=FALSE
This installs Delphi in english with additional german language files without all the 3rd party tools, without VCL for Web, without samples and without help.

Oh, you can write a logfile with /l="C:\Setup.log" but be aware that it may slow down the installation and produces a logfile of approx 40MB!

If you want to install the help use:
Help_Setup.exe /s LANGUAGE=English PERSON_DW32=TRUE PERSON_CPPB=FALSE
This pretty much makes installing Delphi a one click thing. ;)