Archive

Archive for November, 2008

String reverse tests

November 23rd, 2008 alex No comments

This post will be a short one. Just giving a general recommendation for people doing String processing in Delphi – use PChar or ^Char for that.

I have made a simple test program that reverses a very big string with 4 different methods. Here’s the output of the tests:

Testing String method ... 5937ms.
Testing PChar method ... 2453ms.
Testing ^Char (standard) method ... 2202ms.
Testing ^Char (2009) method ... 3203ms.

I won’t detail the implementations of the functions here – but know that probably the tests are not 100% perfect so there might be some variations, but the main point is clear — use ^Char or PChar methods when you are doing some string manipulations. The amount of code the compiler emits for direct string manipulation is just absurd (but necessary).


Test program:
String vs PChar speed tests (254)

Categories: Programming

Strings, PChars and reference counting

November 18th, 2008 alex No comments

Hi folks, in today’s post I will continue with reference counting specifics of Delphi in the particular case of Strings.

We’ll start with a small example:

function ExcludeDigits(const S: String): String;
var
  NewS   : String;
  PNewS  : PChar;
  I      : Integer;
begin
  NewS := S;
  PNewS := PChar(NewS);

  for I := 0 to Length(S) - 1 do
    if CharInSet(PNewS[I], ['0' .. '9']) then
      PNewS[I] := '_';

  Result := NewS;
end;

var
  Initial, Result : String;

begin
  Initial := 'Hello 001 World';
  Result := ExcludeDigits(Initial);

  WriteLn('Initial: ', Initial);
  WriteLn('Result: ', Result);

  ReadLn;
end.

What will the output of this program be? The obvious result (the one we would expect) would be: “Initial: Hello 001 World” and “Result: Hello ___ World”. The actual result is: “Initial: Hello ___ World” and “Result: Hello ___ World”.

To understand why this happens we must dive deep into how reference counting works on strings. Let’s check out how things work by studying the ExcludeDigits function step by step:

  1. We assign S to NewS. And here starts Delphi’s magic! S is actually just a pointer into a heap object where the string is stored, so assigning it to NewS will just copy the pointer and will increase the reference count of that string (in the heap). Basically S and NewS would be 2 pointers that point to the same location in the heap. Current reference count of the string is 2.
  2. After assigning NewS (with conversion) to PNewS, PNewS will point to the first element (character) of the string in the heap. Remember that pointer types do not count as references so nothing else will happen!
  3. Next, the FOR loop will just check at each iteration  if each character is a digit and will replace it with _ character. This happens directly in the heap object.
  4. Assigning NewS to Result will do the same as in point 1 — just assign the address and increase the reference count by 1.
  5. At the end of the function, compiler will also add a few calls itself. These calls will decrease the reference count of all local strings and dispose them if that count reached 0. In our case, the reference count will be 2.

Now it should be pretty clear why this function also changed the original string. What you need to remember is that Delphi uses Copy-On-Write technique for the strings — meaning that unless you change the copied string, that copy will occupy the same heap space. This has a nice advantage of saving memory, but also a big disadvantage – you must be careful using pointers because you never know (well, …, actually you know) what you may break.

Let’s revisit our example and add one small line to the code:

function ExcludeDigits(const S: String): String;
var
  NewS   : String;
  PNewS  : PChar;
  I      : Integer;
begin
  NewS := S;

  { I have added the following line! }
  NewS[1] := NewS[1];

  PNewS := PChar(NewS);

  for I := 0 to Length(S) - 1 do
    if CharInSet(PNewS[I], ['0' .. '9']) then
      PNewS[I] := '_';

  Result := NewS;
end;

In the code above I have added a new line: “NewS[1] := NewS[1];”. I am modifying the copy so that Delphi copy-on-writes it — this means that NewS will contain a new address after this call happens. PNewS will now contain the address of the first element in the copy. This change will ensure that results are what we expected in the first place: “Initial: Hello 001 World” and “Result: Hello ___ World”.

If you use the CPU mode while debugging this code, take a look what happens for that assignment line: you will notice a call like UniqueStringU– this function actually does the “dirty work”. UniqueString checks to see if the reference count of the given string is more than 1 and if so, it will create a copy of that string and modify the parameter it received (Note: UniqueStringU is the Unicode version of this overloaded function). Let’s try it:

function ExcludeDigits(const S: String): String;
var
  NewS   : String;
  PNewS  : PChar;
  I      : Integer;
begin
  NewS := S;

  { I have added the following line! Now using UniqueString }
  UniqueString(NewS);

  PNewS := PChar(NewS);

  for I := 0 to Length(S) - 1 do
    if CharInSet(PNewS[I], ['0' .. '9']) then
      PNewS[I] := '_';

  Result := NewS;
end;

Run it — yes the results are also what we expected in the first place!


General conclusion: Be very careful when you pass Strings to external DLLs by getting PChar references! You may think that you have passed a copy but actually you would pass a pointer into the original string.

Categories: Programming

C++ Builder TypeInfo alternative.

November 17th, 2008 alex 4 comments

I bet most Delphi developers know about TypeInfo operator. It provides type information somehow similar to reflection in Java or .NET (of course a lot more limited).

Take this simple example:

begin
  WriteLn(PTypeInfo(TypeInfo(Byte))^.Name);
end.

It will simply write “Byte” on the console, but it demonstrates how to use RTTI emitted type information properly.

Unfortunately C++ doesn’t emit this kind of RTTI for normal types but only for classes derived from TObject. Replicating the same code as in Delphi proves to be quite complicated. There is one method you can use to do it, and it involves some workarounds:

/*
Define a 2 macros to generate a local class that
exports RTTI info for a predefined property.
*/
#define MAKE_TYPEINFO(T) \
class T##_LOCAL_TYPE_INFO : public TObject \
{ \
private: \
  T m_var; \
__published: \
  __property T A_VAR = { read = m_var }; \
public: \
  static PTypeInfo __fastcall TypeInfo() \
  { \
    PTypeInfo pTInfo = (PTypeInfo)__typeinfo \
       (T##_LOCAL_TYPE_INFO); \
    PPropInfo pProp = Typinfo::GetPropInfo(pTInfo, "A_VAR"); \
    return *(pProp->PropType); \
  } \
  static int __fastcall TypeSize() \
  { \
    return sizeof(T); \
  } \
}

#define GET_TYPE_INFO(T) T##_LOCAL_TYPE_INFO::TypeInfo()

/* Generate RTTI hack class */
MAKE_TYPEINFO(String);
int _tmain(int argc, _TCHAR* argv[])
{
  cout << AnsiString(GET_TYPE_INFO(String)->Name).c_str() << endl;
}

Basically what happens is that MAKE_TYPEINFO macro will declare a new class that exposes a published property of the given type. The TypeInfo()function will then use this property to get to the actual RTTI of the type. GET_TYPE_INFO macro will simply invoke TypeInfo() function of the auto-generated class (to simplify coding). It’s not pretty but it works.


Node: You cannot use this technique on template classes unfortunately.

Categories: Programming

That stupid garbage collector

November 16th, 2008 alex No comments

I’ve heard this phrase a few times in regards to Delphi’s reference counting mechanics. Before continuing with this post please note — Delphi doesn’t have a Garbage Collector (as Java or .NET)! It uses Reference Counting as part of it’s automatic resource cleanup process. Even if reference counting is a “type of garbage collection” — in today’s world these concepts are totally different and present a separate set of problems which need to be solved.

I will not describe the differences between them, you can check this article to find out more.

OK, moving on … So what types are reference counted in Delphi?

  • Interfaces. Because all interfaces in Delphi are “heavy” (COM) they all derive from IUnknown interface which by default includes 2 methods used for reference counting: AddRef and Release. Delphi developers took a good decision in the early days and add compiler logic to support that.
  • Dynamic Arrays. I can’t tell for sure but I think the decision to reference count these objects had something to do with Strings (which are also dynamic arrays). Anyway, it also removes a lot of burden from the developer.
  • Strings. Pascal language had it’s “static/short string” that was fixed in size but was also just another kind of structure. Those strings were normally declared in the stack – so no cleanup was necessary. I think that was a thing that most Pascal developers loved – so Delphi needed to simulate the same thing but also make strings of any given length. This was probably the motivation to make strings reference counted.
  • Managed Method References. This is a new feature in Delphi 2009. I tried to explain in the previous article why are they reference counted.

It’s all good till now. But what happens when we have a more complicated scenario? Consider the following code:

var
  Arr : array of String;
  S : String;
begin
  { Extend array to 1 element }
  SetLength(Arr, 1);

  { Assign a string }
  S := 'Hello World';

  { Assign a new string (copy) to Arr[0] }
  Arr[0] := S + '. Boom!';
end;

This should probably leak memory you would think. At the function end the cleanup call for the array will be inserted which would free the memory allocated by it. Will the string reference contained in the first element be lost? No – the reason for this is simple: RTTI. The cleanup function for the array will inspect it’s type information and will decide if it must cleanup each element separately. If the element of the array is another reference counted type it will perform specific tasks on it too, and only then the memory of the array will be freed.

So far so good. Consider the next example:

type
  TMyRec = record
    S : String;
  end;

var
  Arr : array of TMyRec;
  S : String;
begin
  { Extend array to 1 element }
  SetLength(Arr, 1);

  { Assign a string }
  S := 'Hello World';

  { Assign a new string (copy) to Arr[0].S }
  Arr[0].S := S + '. Boom!';
end;

No leaks in this case either – records also have RTTI attached so array cleanup function can inspect the structure of that record and detect that another reference counted type is present in it and perform the necessary cleanup steps for it.

If you really want to achieve a leak, this is what you do:

type
  TMyRec = record
    S : String;
  end;

var
  Arr : array of ^TMyRec;
  S : String;

begin
  { Extend array to 1 element }
  SetLength(Arr, 1);

  { Assign a string }
  S := 'Hello World';

  { Assign a new string (copy) to Arr[0]^.S }
  New(Arr[0]);
  Arr[0]^.S := S + '. Boom!';
end;

In this case a pointer to a record will not be cleaned up so you’ve got yourself a leak.


So why do people refer to this technique as stupid? Well, …, some results of reference counting are not that intuitive at first. For example combining referenced counted types and pointers is the worst thing anyone can do in Delphi:

var
  p : ^String;

procedure InitString;
var
  s, x : String;
begin
  s := 'A cool string!';
  x := s + '. Append something to make a copy in' +
             'memory and generate a new string.';

  p := @x;
end;

begin
  { Call a function that will generate a string }
  InitString();

  { Write the value of the string (pointed to by p) }
  WriteLn(p^);

  { Wait for a key press }
  ReadLn;
end.

Can you spot the problem? Yes, I bet you’re thinking that passing a pointer to the string declared in a function to a global pointer is a bad idea. The fact is that strings are kept in the heap so that should be no problem. What goes wrong here is that s will be disposed upon exit from the function InitString because there are no more references to it anywhere else. The Pointer to String does not count as a reference! This is  just one of the reasons some people would consider Delphi to be stupid.

Another thing you would want to avoid is “playing” with reference counting in interfaces directly:

procedure Test();
var
  Obj : IInterface;

begin
  Obj := TInterfacedObject.Create();
  Obj._Release;
end;

This can result in big problems because at the end of the function another _Release will be added (but the one called directly would already kill the object).


General conclusions are simple:

  1. Either use Delphi’s reference counting mechanics the way these were supposed to be used, or always ensure there are another references to the object.
  2. Do not mix pointers and reference counted types. Pointers to those types do not count as references so you’re going to point disposed memory after it leaves the scope.
  3. If you want pure C-like strings, arrays or classes, use PChar, typed pointers, pointer arithmetic and do not use interfaces. If you do need interfaces, do not use TInterfacedObject as the base class but rather a new one that will not implement reference counting.



P.S. See System._DynArrayClear and System._FinalizeArray functions for more information on how arrays are being cleaned up automatically by Delphi (calls to functions like this are being automatically inserted by the compiler in the code).


Ok, that should be all for tonight :)

Categories: Programming

Delphi 2009: reference to

November 15th, 2008 alex No comments

It’s probably not news anymore but Delphi 2009 supports anonymous methods out of the box. To be able to support them, CodeGear developers added a new type of a “pointer to a method” called a managed method reference:

type
  TManagedProc = reference to procedure;
  TManagedIFunc = reference to function(const A : Byte) : Byte;

Many of you would say “oh no! another type of pointer to a function!”. Yes, that’s another one. But it’s much so more than a simple pointer!
But first let’s remember about the good old days when we had 2 types of pointers to functions in Delphi:

type
  TSimpleProc = procedure;
  TMethodProc = procedure of object;

The difference between these 2 is that TSimpleProc is a simple pointer to a procedure and that TMethodProc is actually a structure defined as follows (in System unit):

type
  TMethod = record
    Code: Pointer;
    Data: Pointer;
  end;

This means that TMethodProc actually contains 2 pointers: one for the method itself and one for the object it will be called upon. It should be pretty obvious why the distinction, but in any case let me “draw” an example:

procedure TForm1.Button1Click(Sender : TObject);
var
  Method : TMethod;
  MethodPtr : TNotifyEvent;
begin
  { Obtain the pointer to the function in TForm1
  (assuming that Button2Click exists and is of right format)}
  Method.Code := MethodAddress('Button2Click');

  { Set the pointer to TForm1 instance }
  Method.Data := Self;

  { Typecast this structure to a pointer to a method }
  MethodPtr := TNotifyEvent(Method);

  { Call the Button2Click(Sender). }
  MethodPtr(Sender);
end;


OK, so what’s with this “reference to procedure” stuff I’m talking about?

This new type is actually a “managed” object in Delphi — it’s an object which is reference counted and all this funkiness.

– Oh no! says the person in the back row; I say — Oh Yes! Because managed method references are objects they are instantiated and must be destroyed so Delphi MUST keep track of all references to those – otherwise it would be your job, and that would make the whole idea useless.

Some interesting facts about this new type:

  • You can assign a normal function pointer to a managed method reference.
  • You can assign a normal method pointer to a managed method reference.
  • Variables that are captured by anonymous methods are located in the heap and not local routine’s stack.
  • Because captured variables live long, one must be careful how to manage them properly.

A call to a managed method reference is expensive – and I will show you why. Let’s take the following code as an example:

type
  TMyProc = reference to procedure();
  TCarry  = record P : TMyProc end;

var
  C : TCarry;

procedure FX;
begin
end;

procedure Y(const MyProc : TMyProc);
begin
  MyProc();
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  C.P();
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  x : integer;
begin
  { Case 1: Local anonymous method }
  Y(procedure begin x:= 10; end);

  { Case 2: Simple function pointer }
  Y(FX);

  { Case 3: Simple method pointer }
  Y(IFX);

  { Case 4: Local anonymous method as a reference
     that will be called later }
  C.P := procedure begin x:=x+1; end;
end;

procedure TForm1.IFX;
begin

end;

Each case (call to Y()) behaves differently:

  1. Local anonymous method – will call Y with a reference to the “auto-generated” method in TForm1 class (with a pretty name like TForm1.FormCreate$ActRec.$0$Body) that will perform the assignment operation directly on variable X (in heap – no copy). Still, it produces 1 call, 1 jump and a few additional instructions until the control gets there. And no, I’m not counting the additional call made to function Y; with this one it would be 2 calls.
  2. Simple function pointer — This one is interesting because the compiler will actually auto-generate a new anonymous method and in that method it will perform a call to FX function. That’s very expensive because you get 2 calls, one jump and a few additional instructions just to make a simple call.
  3. Simple method pointer — This call performs as the previous one except the fact that the anonymous method that was generated, will first copy the “address of the object” into EAX register and then call the method – that is how a standard (register) call performs.
  4. Local anonymous method as a reference that will be called later — in this case the same as in point 1 happens, but the “frame” in which this anonymous method exists will not be destroyed on exit from FormCreate because it’s referenced in global variable C and called later in a button press event. The interesting part is that variable X will live long after FormCreate method terminates.


The conclusion is simple: even if this new fancy reference type finally promises to “unify” all method pointers types under one wing – try to avoid it when you can. Of course there are cases when you would want to write one single function that accepts all 3 types of references — but you can just make overloads for that.




P.S. At first wanted to paste assembler code here but then I realized that would be too much :) You can always use the CPU mode in Delphi’s debugger to follow the code yourselves.

Categories: Programming

Generics.Defaults: Bug

November 11th, 2008 alex No comments

There is a bug in Generics.Defaults unit in Delphi 2009. If you read my previous post: Generic Defaults you should know already about the “3 bytes long” data types and how are those passed as const parameters to a function. I know I had problems with that (had to create a special case for those) so how did CodeGear developers get around that? Simply – they didn’t.

Consider this piece of code:

type
 TMyStruct = packed record
   a1, a2, a3 : Byte;
 end;

procedure TestDefaults()
var
  comp   : IComparer<TMyStruct>;
  s1, s2 : TMyStruct;
  I      : Integer;
begin
  comp := TComparer<TMyStruct>.Default;
  FillChar(s1, 3, 0);
  FillChar(s2, 3, 1);

  for I := 0 to 10000 do
      comp.Compare(s1, s2);
end;

After a few loops you will get a pretty ugly “Access Violation” because the stack is corrupted. This happens because the TComparer.Default; will select the “generic binary” support class. These are defined as following:

  function Equals_Binary(Inst: PSimpleInstance;
      const Left, Right): Boolean;

  function Compare_Binary(Inst: PSimpleInstance;
      const Left, Right): Integer;

  function GetHashCode_Binary(Inst: PSimpleInstance;
      const Value): Integer;

Surely they expect Left and Right parameters to be passed as addresses in EDX/ECX registers. The problem is obvious: 3 byte data structures are passes in the stack! The called function is expected to cleanup the stack afterward using a “ret 8” instruction. This means that the stack keeps building up 2 integers on each call until it happens to address invalid memory (this is why I used a loop there).

I know it’s a very “exotic” case where you actually have to use a packed data-type (defaults are aligned to 4 bytes so there is no problem) but it’s still a bug.

More to come.

Categories: Programming

Generic Defaults

November 10th, 2008 alex No comments

One important part of any generic collection classes are the “type support” functions. And what I mean are the functions specific to each type that perform:

  1. Comparison – will return a value indicating if A > B or A <B or A = B. It’s usually a function that has this prototype: function Compare(const A, B : T) : Integer. The result value is less than 0 is A ie less than B, bigger than 0 is A is greater than B, and is 0 if the values match.
  2. Equality – will return a Boolean value of True if A and B are equal. The usual prototype is: function AreEqual(const A, B : T) : Boolean.
  3. Hash – the hash function is used by Dictionaries and Hash Sets. The usual prototype is function GetHashCode(const A : T) : Integer. Result is an integer that is supposed to be as unique as possible.

Now, in .NET or Java these functions are a part of any type (be it Integer, Object or etc.). This makes the development of generic collections easier because you can expect any type you’re operating on to have those functions exposed. It may be a little more work in some cases but anyway it’s always pretty simple.

In languages like C++ or Delphi there is a clear distinction between “value types” and “objects” which makes it quite impossible to operate on all data types with the same generic code. That’s why STL (C++) classes require you to provide support functions for each data type you want to use in your collection.

When I started working on HelperLib I designed a type called HTypeSupport which would provide all three support functions for all collections I create:

  MyDictionary := HDictionary<String, Integer>.Create(
                HStringTypeSupport.Create(),
                HIntegerTypeSupport.Create());

So, as you can see in the example above, you’d have to supply your “type support” object each time you create an instance of the collection class.

Later on I discovered that Delphi provides Collections.Generic and Collections.Defaults units. Which made me interested is the fact that you could create a List without specifically giving it the support functions:

  MyList := TList<Integer>.Create();

After some digging I found out how are the pulling it off – RTTI. Yes RTTI seemed to work for generic types! Hooray! You can write a piece of code like this actually:

function GenericClass<T>.NameOfType() : String
begin
  Result := TypeInfo(T)^.Name;
end;

which will actually return you the name of the type you’re operating at run-time (note that TypeInfo can also return nil in the cases when T doesn’t have RTTI attached – like for records.)

If you look deeper in Generics.Defaults you will notice that RTL developers are doing some weird stuff – they are creating instances of objects building them by-hand. It’s very very ugly and I wanted to avoid this.

My HelperLib.TypeSupport unit does the same thing as Generics.Defaults just that it does it in a more cleaner way. The way to use it is simple – if you need a “type support” for your “unknown-yet” type you just call:

  HTypeSupport<T>.Default;

It will take into account all the possible types you can throw at it. The generic “binary type support” will be selected in all the cases when a type doesn’t have RTTI attached or where it makes sense to be used.

The development of these classes took quite a lot – mostly debugging with CPU window enabled. The problem appeared when developing the HBinaryTypeSupport – there is no possibility to know the size of the input parameters in those support functions at compile time. The only information at the time you create a HBinaryTypeSupport is the size of the arguments to come:

{ Binary Support }
HBinaryTypeSupport = class(HTypeSupport<Pointer>)
private
  FSize : Cardinal;

public
  { Constructor }
  constructor Create(const Size: Cardinal);

  { Comparator }
  function Compare(const AValue1, AValue2 : Pointer) : Integer;

  { Equality Comparator }
  function AreEqual(const AValue1, AValue2 : Pointer) : Boolean;

  { Hash code provider }
  function GenerateHashCode(const AValue : Pointer) : Integer;
end;

The Pointer arguments are actually, in a sense, bogus, because AValue can be of any given size (depending on the Type it was created for). The fun starts at knowing how the compiler will put the values onto the stack/registers when trying to call any of these functions. Example:

  TMyRec = packed record
    I1, I2, I3, I4, I5, I6 : Integer;
  end;

  procedure TestRecord;
  var
    DefaultSupport : ITypeSupport<TMyRec>;
    v1, v2         : TMyRec;
  begin
    DefaultSupport := HTypeSupport<TMyRec>.Default;
    DefaultSupport.Compare(v1, v2)
  end;

In this case the size of AValue1, and AValue2 will be 24 bytes, while the HBinaryTypeSupport.Compare function expects 2 pointers! It will actually work because the compiler will pass v1 and v2 as addresses to the Compare function. This happens because AValue1 and AValue2 are expected to be const – which ensures that the compiler will pass the address of the variables when their size is bigger than the platform register size (in this case 32 bits). Also the compiler will put those addresses to ECX/EDX registers – these two will be mapped to AValue1 and AValue2 resulting in me having 2 valid pointers to the structures I need to compare.

Of course this happens only if the size of the structure passed is bigger than 4 (size of the 32 bit register) – but HBinaryTypeSupport class is only instantiated for sizes which are bigger than 4 bytes anyway so there is no problem. (Note: See the implementation of HTypeSupport.Default to see what particular classes are selected for any given type.)

Another interesting effect is related to structures/arrays which are of 3 bytes length. Those are not passed as pointers and are not even passed by value in EDX/ECX. Compiler will actually push those onto stack (as 32 bit values). This meant that I had to create a special H3BytesTypeSupport class which would be instantiated for all types who’s sizes are 3 bytes (ex. packed array[0..2] of Byte). Using a HIntegerTypeSupport would not help because in that case the compiler will pass the integers using EDX/ECX.

There are also other interesting bits out there – like the comparison for dynamic arrays. This is done in a more logical way (expecting Pointers as AValue1 and AValue2) and exploiting the internal structure of dynamic arrays.


If you want to read more info on the calling convention used by Delphi by default, read this.

Categories: Programming

Delphi 2009 – Exit;

November 9th, 2008 alex 2 comments

A very useful and cool feature in Delphi 2009 is the Exit keyword. Yes, it’s that Exit that “returns” from a function call. So what’s so exiting about it? Nothing really – for people coming from C-like languages, but for Delphi people it’s the fact that you can now write this:

function f1(x : Integer) : Integer
begin
  if (x < 0)
    Exit(-1);

  Exit(x * x);
end;

instead of:

function f1(x : Integer) : Integer
begin
  if (x < 0)
    begin Result := -1; Exit; end;

  Result := x * x;
end;

Well maybe it wasn’t the best example of it’s use but you surely found so many cases when you needed to write something like begin Result := ….; Exit; End; – Exit will be very useful in those cases.

Categories: Programming

Delphi Generics: Problem 1

November 8th, 2008 alex 6 comments

Kids, don’t try this at home :)

class procedure DynamicArray<T>.Test();
var
  Arr : array of T;
begin
  { Fails miserably! }
end;

Error:
[DCC Fatal Error] Tests.DynamicArray.pas(772): F2084 Internal Error: URW1135

There you have it :)

The fix is rather simple but annoying:

Declare a new type in the class/record like this:

type
  DynamicArray<T> = class
  private
  type
    TArrayOfT = array of T;

  public
    { Lots of stuff here }
end;

And then simply make the variable Arr to be TArrayOfT:

class procedure DynamicArray<T>.Test();
var
  Arr : TArrayOfT;
begin
  { Works! }
end;

Till the next bug.

Categories: Programming

TSendMail Action

November 8th, 2008 alex No comments

If you’ve ever wondered how to use TSendMail action in ExtActns unit – drop it, it’s useless. I’ve had a few people complaining they don’t know how to set the Recipients and Subject set it – you can’t do it :)

Let’s look at the source code for the TSendMail.ExecuteTarget method:

First:

lpszSubject := nil;
lpszNoteText := PAnsiChar(AnsiString(Text.Text));
lpszMessageType := nil;
lpszDateReceived := nil;
lpszConversationID := nil;

and then:

MError := MapiSendMail(0, Application.Handle, MapiMessage,
    MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0);
if MError <> 0 then
    MessageDlg(SSendError, mtError, [mbOK], 0);

Three problems with this code:

  1. First it has no support for Subject and Recipients/CC and related.
  2. If the user closes the “Send mail window” of his mail client, a message box will pop up – and you can’t do anything about it!
  3. The text is converted to ANSI so no Unicode support for you email.

The first problem can be solved rather simply by the VCL developers but I think it’s not top priority anyway.

For the second problem it should at least have been an event like OnMailResult with a Boolean that would say – MessageWasSent so that you, the developer would have the control of what you would do in case of failure/cancel.

Anyway, let’s hope the VCL gets cleaned up a bit in the next years and such controls are removed or updated to actually be useful to someone.

Categories: Programming