Archive

Archive for March, 2009

DeHL 2.2 Released

March 25th, 2009 alex 3 comments

In this release I have changed the name of the library from DelphiHelperLib to DeHL to be shorter. The most important changes in this release are:

  • Renamed the library to DeHL in hopes to remove the length of the unit names.
  • Renamed all classes to use the T prefix rather than H. It was an unfamiliar mode of naming things.
  • BigCardinal and BigInteger can be used within a Variant type. An implicit operator is declared to allow seamlessly to convert from the BigInteger/Cardinal to Variants.
  • Type Support has been extended to offer better information about the clean-up practices of a type.
  • Added methods to move a number of elements from an array safely (for ref-counted Delphi types). Also fixed the code to use these methods instead of Move directly.
  • Added a new array type: TFixedArray. It is an immutable array which is used only for reading. TDynamicArray can be created off TFixedArrays.
  • Added work-around fixes for a few Delphi bugs blocking the compilation.
  • The testing module was cleaned up a bit. Now using a base class for all our test cases. Also tests for new scenarios have been added.
  • All container classes now cleanup their contained values (when required). This means it is now safe to use them with types that require cleanup.
  • Cleaned up the binary tree implementation a bit and fixed a bug.
  • Externalized the last remaining hard-coded strings in the code.

More information and downloads can be found here.

Categories: Programming

How To: Invokable Variants

March 23rd, 2009 alex 2 comments

In this post I will try to explain how to create a “late-bound” dictionary based on invokable variants. Most of the techniques related to the creation of a custom Variant data type are already described in How To: Creating a custom Variant type.

To store the Key/Value pairs in my custom Variant, I will use TDictionary provided in Generics.Collections unit. Also note that the key of the dictionary is String since the we will use the late-bound properties to access specific values in the dictionary. The values are of type Variant, which enables me to store whatever information I want. The following code exemplifies the usage of the variant based dictionary:

var
  Person: Variant;
begin
  Person := VDict();

  Person.FullName := 'John Smith';
  Person.Age := 19;
  Person.NetWorth := 9000;

  WriteLn(Person.FullName);

  { Remove the Age property }

  Person.RemoveAge();

  { Write all information stored in dictionary }
  WriteLn(Person);
end.

And now let’s get to the actual coding process! The first thing is to create your class that will be held inside the TVarData structure. In my case, I use an already created class called TDictionary. Still, I declared an alias type for my String/Variant dictionary (makes life easier):


  { Declare the String/Variant dictionary that will
     hold the real data }
  TSVDictionary = TDictionary<String, Variant>;

The next step is the usual “declaring the TVarData record” that will map over our custom variants:

  { Mapping the TSVDictionary into TVarData structure }
  TSVDictionaryVarData = packed record
    { Var type, will be assigned at runtime }
    VType: TVarType;
    { Reserved stuff }
    Reserved1, Reserved2, Reserved3: Word;
    { A reference to the enclosed dictionary }
    FDictionary: TSVDictionary;
    { Reserved stuff }
    Reserved4: LongWord;
  end;

The FDictionary field in TSVDictionaryVarData record holds the dictionary instance used to hold the real key/value pairs. Now comes the important part: declaration of the proxy class that will manage the custom variant type I declared. To support late-binding capabilities in custom variants you have to descend from TInvokeableVariantType class. TInvokeableVariantType adds a few abstract methods which you have to override to intercept all calls made to methods/properties of your custom variant type. Note that TInvokeableVariantType derives from TCustomVariantType which means that you can add all the capabilities of a normal custom variant type on top of the dynamic ones.

The definition of the class I made follows:

  { Manager for our variant type }
  TSVDictionaryVariantType = class(TInvokeableVariantType)
  private
    function DictionaryToString(const ADict: TSVDictionary): String;
  public
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    procedure CastTo(var Dest: TVarData; const Source: TVarData;
      const AVarType: TVarType); override;
    function GetProperty(var Dest: TVarData; const V: TVarData;
      const Name: string): Boolean; override;
    function SetProperty(const V: TVarData; const Name:
      string; const Value: TVarData): Boolean; override;
    function DoFunction(var Dest: TVarData; const V: TVarData;
      const Name: string; const Arguments: TVarDataArray): Boolean; override;
    function DoProcedure(const V: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
  end;

As seen in the code above, I need the following capabilities for my variant based dictionary:

  • Clearing. When the variant requires clearing, freeing the TSVDictionary instance is required to avoid memory leaks.
  • Copying. Required for the assignments between variants. When copying from a variant to another, I do not copy the instance of the dictionary, rather I create a new one in the copy and copy all Key/Value pairs to it.
  • Casting. My variant based dictionary supports casting to strings. Casting to other types is done through string.
  • Property Get/Set Calls. Each late-bound property is actually a key in the dictionary. So I need to catch calls to get/set operations.
  • Function and Procedure Calls. My dictionary supports three custom operations (one to get the count of pairs, one to remove a pair from a dictionary and one to clear the dictionary).

The next step is to create a function which will create instances of our variant type. The following code shows all the relevant code pieces that are used in registering the proxy class, and creating custom instances of the variant based dictionary.

Most of this code pattern should be already known if you read the previous “How-To”:


...

interface
{ Creates a new Variant Dictionary }
function VDict(): Variant;

implementation

...

var
  { Our singleton that manages our variant type }
  SgtSVDictionaryVariantType: TSVDictionaryVariantType;

function VDict(): Variant;
begin
  { Clear out the result }
  VarClear(Result);

  with TSVDictionaryVarData(Result) do
  begin
    { Assign the new variant the var type that was allocated for us }
    VType := SgtSVDictionaryVariantType.VarType;

    { Create a new instance of the dictionary object }
    FDictionary := TSVDictionary.Create();
  end;
end;

...

initialization
  { Register our custom variant type }
  SgtSVDictionaryVariantType := TSVDictionaryVariantType.Create();

finalization
  { Uregister our custom variant }
  FreeAndNil(SgtSVDictionaryVariantType);

And now let’s see the most important pieces of code that make up the proxy class. Note that I will not present all the methods that make up that class. Most of them are pretty obvious, like CastTo, Clear and Copy.

The first victim is DoFunction, which I implemented to support a late-bound call that returns the count of items in the dictionary:

function TSVDictionaryVariantType.DoFunction(
  var Dest: TVarData; const V: TVarData; const Name: string;
  const Arguments: TVarDataArray): Boolean;
begin
  Result := False;

  { We do not support arguments here }
  if Length(Arguments) > 1 then
    Exit;

  { Is the first parameter an error variant? }
  if (Length(Arguments) = 1) and (Arguments[0].VType <> varError) then
    Exit;

  { Requesting dictionary count? }
  if Name = 'COUNT' then
  begin
    VariantInit(Dest);
    Dest.VType := varInteger;
    Dest.VInteger := TSVDictionaryVarData(V).FDictionary.Count;
    Exit(true);
  end;

  { Try to call the procedures }
  Result := DoProcedure(V, Name, Arguments);
end;

As you can see, I check whether a call is made to the Count function. If that is true, I return the number of pairs contained within this dictionary. Otherwise I forward the call to another method of my proxy class called DoProcedure. I do that because DoProcedure supports two more custom calls which can be made as functions or procedures (I don’t care):

function TSVDictionaryVariantType.DoProcedure(
  const V: TVarData; const Name: string;
  const Arguments: TVarDataArray): Boolean;
var
  Key: String;
begin
  Result := False;

  { We do not support arguments here }
  if Length(Arguments) > 1 then
   Exit;

  { Is the first parameter an error variant? }
  if (Length(Arguments) = 1) and (Arguments[0].VType <> varError) then
   Exit;

  { Check if this is a removal call }
  if Pos('REMOVE', Name) = 1 then
  begin
    { Remve the prefix }
    Key := System.Copy(Name, Length('REMOVE') + 1, Length(Name));
    TSVDictionaryVarData(V).FDictionary.Remove(Key);
    Exit(true);
  end;

  { Is this a CLEAR call? }
  if Name = 'NAME' then
  begin
    TSVDictionaryVarData(V).FDictionary.Clear();
    Exit(true);
  end;
end;

The previous code piece is a bit more interesting because I check whether a call is made to a function that starts with “REMOVE” and cosider the text that continues afterwards as being the name of the key to be removed. For example calls like “MyVar.RemoveAge()” will remove the key named “Age” from the dictionary. The second supported call is Clear which clears the enclosed dictionary.

The last important code piece that follows shows the implementation of property set/get processing methods:

function TSVDictionaryVariantType.GetProperty(
  var Dest: TVarData; const V: TVarData;
  const Name: string): Boolean;
begin
  { Type cast to our data type }
  with TSVDictionaryVarData(V) do
  begin
    { Try to read the value from the dictionary }
    if not FDictionary.TryGetValue(Name, Variant(Dest)) then
      Clear(Dest);
  end;

  { Return true in any possible case }
  Result := True;
end;

function TSVDictionaryVariantType.SetProperty(
  const V: TVarData; const Name: string;
  const Value: TVarData): Boolean;
begin
  { Type cast to our data type }
  with TSVDictionaryVarData(V) do
    { Update the value in dictionary dictionary }
    FDictionary.AddOrSetValue(Name, Variant(Value));

  { Return true in any possible case }
  Result := True;
end;

A few things to note about the code:

  • In DoProcedure and DoFunction methods, the Arguments array may contain one element even if no parameters are passed to the call. This happens because the compiler treats “MyVar.CallMe()” and “MyVar.CallMe” calls differently. In the first case it adds an argument of type varError, in the second it does not add any arguments.
  • The proposed variant dictionary implementation is case-insensitive. That means that “MyVar.Age := 5” is equal to “MyVar.AGE := 5“. If you need to make it case-sensitive, override the FixupIdent method in the proxy class. FixupIdent as implemented in TInvokeableVariantType coverts all identifiers to upper case.

That is all for now. Unfortunatelly I could not find an interesting example to demonstrate the power of late-binding and dynamic dispatch. Everything that can be done dynamically can be done normally anyway…



The full unit can be found here: Variant-based Dictionary (302)

Categories: Programming

Little warning

March 20th, 2009 alex No comments

If you’re ever going to write a custom invokable variant read the following, otherwise just skip this post. I will not detail on how to create a custom invokable variant type (that is a matter for another post), but simply give an advice related to how function and procedure calls should be treated.

When implementing the DoProcedure and DoFunction methods of your custom proxy class (the one derived from TInvokeableVariantType) be sure to take into account the fact that the Arguments parameter may contain one element even though the caller did not pass any. This little “anomaly” (call it as you like) happens because you can call a method in Delphi using the Pascal style or C style syntax:

{ pascal style syntax }
AVariant.CallMethod;

{ C-style syntax }
AVariant.CallMethod();

In the first case, everything works as expected. If no parameters are passed to the called function, the Arguments parameter contains zero elements. In the second case, the Arguments array contains one element of type varError, even though you did not pass any real parameters.

Make sure your code takes this case into account, otherwise obscure errors may crawl in later.

Categories: Programming

How to write a good email feedback (for Help)

March 5th, 2009 alex 7 comments

This post is here to explain how to properly write an email feedback for the Delphi Help system. So often people assume that nobody reads these emails. This leads to emails with little value to us (the Documentation team). Next I am going to describe what is a documentation feedback email, who reads those, why should you care and how to write them properly.

What is a feedback email:

  • It’s an email in which you tell us what we did wrong.
  • In the recent versions of the RAD Studio Help  at the bottom of the page you can see a “Send us feedback” link.
  • Pressing of that link will open up a new message window (with your email client). That mail contains some information about the topic in which you pressed the “Send us feedback” link.
  • You’re supposed to leave that information in the body, and add a few lines of description.

Why write feedback emails (aka “Why should you care”):

  1. It helps us identify the topics which are more important to our readers.
  2. Some topics are out of date or need some notes. Suggesting that in the feedback email helps us complete these topics with more precise “field” information. If you have more free time on your hands you can even suggest fixes for topics.
  3. You as a consumer of the help may know better which articles would need better “See Also” links.
  4. It generally takes less than one minute to wrote us an email.

Who is looking at them:

  • A person in the documentation team.  Usually the emails are processed once a month (it may be longer).
  • The QA step is performed by the person in the documentation team and the bug is registered in the internal bug-tracker directly.

How to write a feedback email:

  1. Leave the subject intact and keep the information in the body of the mail. By removing that information you make the life of the person reviewing that email a lot harder.
  2. Be specific. Emails that say “all the help is bad” are discarded immediately. If you’re targeting  more articles, be sure to specify that.
  3. Be sure that the email targets a documentation problem. Bugs in the product should be entered through QC. You’re not going to get an answer if you ask a non-documentation related question.
  4. If you are using a localized Help (French, German or Japanese), write about the problem in English. People reading these feedback emails do not know all the languages.
  5. Try to refrain from inflammatory phrases. It’s not that pretty to read people using curse words. These mails are sometimes discarded directly.
  6. Mails that simply praise Delphi 7 help and nothing more are discarded (no informational value).

Note for people using older help versions (like 2006, 2007). A lot of bugs in those versions have already been fixed. I would suggest you download newer versions at http://docs.embarcadero.com (you can get a CHM file for example).

Categories: Programming

Array constructor

March 2nd, 2009 alex 5 comments

Speaking of features which are sparse in documentation. There is an intersting and a bit useless feature in dynamic arrays laying around: array constructors.

Let’s check out this example:

type
  TMyArray = array of Integer;

var
  X: TMyArray;
begin
  X := TMyArray.Create(1, 2, 3);

  WriteLn(Length(X));
  ReadLn;
end;

This code will actually compile and run.
(May probably be a remnant of old .NET days)

Categories: Programming

“in” is dead, long live “CharInSet” (or maybe not)

March 2nd, 2009 alex 8 comments

I’ve this question come up a few times, so …

Since Delphi 2009 you have probably noticed this warning: “[DCC Warning] Unit1.pas(27): W1050 WideChar reduced to byte char in set expressions.  Consider using ‘CharInSet’ function in ‘SysUtils’ unit.” which suggests you to use the CharInSet function instead. CharInSet still requires a simple set of AnsiChars and fails (returns false) is the code of the passed character is greater than 255 which makes it useless for Unicode characters.

Putting it simple: replacing the “a in b” statement with CharInSet(a, b) will simply silence the compiler. Nothing more.

You should really consider using Character unit if you want to check whether a character is a letter, digit or is a part of another “standard set of chars”.

Take this example:

uses Character;
begin
  if C in ['A'..'Z'] then // <-- OLD WAY
    WriteLn('The character is an upper-cased letter.');

  if IsUpper(C) then // <-- UNICODE WAY
    WriteLn('The character is an upper-cased letter.');
end;
Categories: Programming