July 29, 2013

Breakpoint not honored while debugging a DLL

Using Delphi, it is very easy to debug a DLL having it called from a host application. This host application can be written using Delphi or any other language.

To debug your DLL in the context for a host application, just add the host application into the corresponding field in Delphi / Menu / Run / Parameters. Then run your project as usual. Delphi will not load the DLL but the host application and intercept the DLL when it is loaded by the host application. It will then honor any breakpoint you've set.

Easy? Yes, it is easy but sometimes, there is a glitch. For some reasons, Delphi won't honor your breakpoints. Among the reasons is that you forgot to use the debug build, or you forgot to enable debugging in the DLL project options.

Sometimes, there is another reason. It made me lose a lot of time playing with all options. Sometimes, I thought that I had found the right option. But later the breakpoints were still not honored.

Finally I found the correct solution. Why it works, I don't know. But so far it worked for me. The solution is: Set option "Include remote debug symbols" to true (Delphi compiler / Linking).

This was not obvious since I didn't used the remote debugger. Anyway, it works!

When option "Include remote debug symbols" is used, Delphi creates one more file with rsm extension in the same directory as the executable. It looks like the debugger always find the symbols there.


Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be

July 27, 2013

Using RTTI to convert record to/from string


Delphi RTTI can be easily used to convert a record (Or a class by the way) to a string representation without taking care of how the record is changed during software maintenance.

RTTI has a set of methods to handle metadata collected by the compiler at compile time. For example, you can iterate thru all fields of a record to find out his name, data type and get or set his value. I used those methods as a simple way to marshal record values between two different processes communicating using sockets.

To simplify my original problem without removing features, I designed a very simple sample program demonstrating the RTTI usage. This simple program simply adds two methods “ToString” and “FromString” to a record. Instead of hardcoding each field in the conversion, I used RTTI to iterate all fields.

Basically, to use RTTI with records, you need the following data types: TRttiContext, TRttiRecordType and TRttiField.

Assuming we handle a record type name “THdr”, the simplest code looks like this:

    AContext := TRttiContext.Create;
    try
        ARecord := AContext.GetType(TypeInfo(THdr)).AsRecord;
        for AField in ARecord.GetFields do begin
            AFldName := AField.Name;
        end;
    finally
        AContext.Free;
    end;



As you can see, we have to create a TRttiContext instance. We then use his method “GetType” to get an instance of TRttiRecord related to the record we are handling. Finally we can iterate thru all fields using a for..in construct. Each field has a corresponding TRttiField instance which can be used to get field name, set or get his value and so on.

My sample code use a loop similar to the above twice: one for converting all fields to a string and one for extracting values from a string and assigning the values to the corresponding fields.

To achieve the goal, I used two support functions: “ParamByNameAsString” and “EscapeQuotes”. They are required to be able to store any fieldname=”fieldvalue” pair into a simple string. There is a simple escape mechanism so that the value can contain embedded double quote.

Here is the full source code:

unit RttiTestForm;

interface

uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics,
    Controls, Forms, Dialogs, Rtti, TypInfo, Vcl.StdCtrls;

type
    THdr = record
        Name       : String;
        Phrase     : String;
        ActionDate : TDateTime;
        procedure FromString(const FromValue: String);
        function  ToString : String;
    end;

    TForm1 = class(TForm)
        Memo1: TMemo;
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
    end;

var
    Form1: TForm1;

implementation

{$R *.dfm}

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
// This will take an inut string having the format:
//  name1=value1;name2=value2;...
// Value may also be placed between double quotes if it contain spaces.
// Double quotes inside the value is escaped with a backslash
// Backslashes are double. See EscapeQuotes below.
function ParamByNameAsString(
    const Params     : String;
    const ParamName  : String;
    var   Status     : Boolean;
    const DefValue   : String) : String;
var
    I, J  : Integer;
    Ch    : Char;
begin
    Status := FALSE;
    I := 1;
    while I <= Length(Params) do begin
        J := I;
        while (I <= Length(Params)) and (Params[I] <> '=')  do
            Inc(I);
        if I > Length(Params) then begin
            Result := DefValue;
            Exit;                  // Not found
        end;
        if SameText(ParamName, Trim(Copy(Params, J, I - J))) then begin
            // Found parameter name, extract value
            Inc(I); // Skip '='
            if (I <= Length(Params)) and (Params[I] = '"') then begin
                // Value is between double quotes
                // Embedded double quotes and backslashes are prefixed
                // by backslash
                Status := TRUE;
                Result := '';
                Inc(I);        // Skip starting delimiter
                while I <= Length(Params) do begin
                    Ch := Params[I];
                    if Ch = '\' then begin
                        Inc(I);          // Skip escape character
                        if I > Length(Params) then
                            break;
                        Ch := Params[I];
                    end
                    else if Ch = '"' then
                        break;
                    Result := Result + Ch;
                    Inc(I);
                end;
            end
            else begin
                // Value is up to semicolon or end of string
                J := I;
                while (I <= Length(Params)) and (Params[I] <> ';') do
                    Inc(I);
                Result := Copy(Params, J, I - J);
                Status := TRUE;
            end;
            Exit;
        end;
        // Not good parameter name, skip to next
        Inc(I); // Skip '='
        if (I <= Length(Params)) and (Params[I] = '"') then begin
            Inc(I);        // Skip starting delimiter
            while I <= Length(Params) do begin
                Ch := Params[I];
                if Ch = '\' then begin
                    Inc(I);          // Skip escape character
                    if I > Length(Params) then
                        break;
                end
                else if Ch = '"' then
                    break;
                Inc(I);
            end;
            Inc(I);        // Skip ending delimiter
        end;
        // Param ends with ';'
        while (I <= Length(Params)) and (Params[I] <> ';')  do
            Inc(I);
        Inc(I);  // Skip semicolon
    end;
    Result := DefValue;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function EscapeQuotes(const S: String) : String;
begin
    // Easy but not best performance
    Result := StringReplace(S, '\', '\\', [rfReplaceAll]);
    Result := StringReplace(Result, '"', '\"', [rfReplaceAll]);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure THdr.FromString(const FromValue: String);
var
    Status    : Boolean;
    Value     : String;
    AValue    : TValue;
    AContext  : TRttiContext;
    ARecord   : TRttiRecordType;
    AField    : TRttiField;
    AFldName  : String;
begin
    if FromValue = '' then
        Exit;

    // We use RTTI to iterate thru all fields of THdr and use each field name
    // to get field value from metadata string and then set value into Hdr.
    AContext := TRttiContext.Create;
    try
        ARecord := AContext.GetType(TypeInfo(THdr)).AsRecord;
        for AField in ARecord.GetFields do begin
            AFldName := AField.Name;
            Value    := ParamByNameAsString(FromValue, AFldName, Status, '0');
            if Status then begin
                try
                    case AField.FieldType.TypeKind of
                    tkFloat:               // Also for TDateTime !
                        begin
                            if Pos('/', Value) >= 1 then
                                AValue := StrToDateTime(Value)
                            else
                                AValue := StrToFloat(Value);
                            AField.SetValue(@Self, AValue);
                        end;
                    tkInteger:
                        begin
                            AValue := StrToInt(Value);
                            AField.SetValue(@Self, AValue);
                        end;
                    tkUString:
                        begin
                            AValue := Value;
                            AField.SetValue(@Self, AValue);
                        end;
                    // You should add other types as well
                    end;
                except
                    // Ignore any exception here. Likely to be caused by
                    // invalid value format
                end;
            end
            else begin
                // Do whatever you need if the string lacks a field
                // For example clear the field, or just do nothing
            end;
        end;
    finally
        AContext.Free;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function THdr.ToString: String;
var
    AContext  : TRttiContext;
    AField    : TRttiField;
    ARecord   : TRttiRecordType;
    AFldName  : String;
    AValue    : TValue;
begin
    Result := '';
    AContext := TRttiContext.Create;
    try
        ARecord := AContext.GetType(TypeInfo(THDR)).AsRecord;
        for AField in ARecord.GetFields do begin
            AFldName := AField.Name;
            AValue := AField.GetValue(@Self);
            Result := Result + AFldName + '="' +
                      EscapeQuotes(AValue.ToString) + '";';
        end;
    finally
        AContext.Free;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TForm1.Button1Click(Sender: TObject);
var
    Hdr1 : THdr;
    Hdr2 : THdr;
    Buf  : String;
begin
    Hdr1.Name       := 'Francois Piette';
    Hdr1.Phrase     := 'I said "\Hello\"';
    Hdr1.ActionDate := Now;

    Memo1.Lines.Add(Hdr1.Phrase);
    Buf := Hdr1.ToString;
    Memo1.Lines.Add(Buf);
    Hdr2.FromString(Buf);
    Memo1.Lines.Add(Hdr2.Phrase);
    Memo1.Lines.Add(Hdr2.ToString);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

end.



Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be

July 20, 2013

Creating an AVI file from bitmaps using Delphi

It is quite easy to create an AVI file from a number of individual bitmaps. The AVI file maybe compressed on the fly using any installed codec. An example application would be creating a video from computer generated images such as 3D view or discrete bitmaps such as those generated from a Mandelbrot fractal zoom in. Each zoom step produces a new bitmap. Taking all bitmap into an AVI makes a nice movie showing the zoom into the fractal deep space!

Windows has two API to handle AVI files: an old API named “Video For Windows” (VfW) and a new API named “DirectShow”. The old API is the easiest to use for the task.

A guy named Gopalakrishna Palemat wrote a Visual Studio C++ application to demonstrate that use of VfW API. I have ported his code to Delphi. I have slightly updated his code to fix shortcomings and make it “the Delphi way”.

The original article can be found at http://gopalakrishna.palem.in/createmovie.html
My Delphi source code can be found at http://www.overbyte.be/eng/blog_source_Code.html

I will speak only about my source code from now.

All the AVI creation code has been moved into a component named “TAviFromBitmaps”. This class handle everything required with VfW API to create an AVI movie, compressed or not. You just has to create an instance of the class and call his AppendNewFrame method to add a bitmap at the end of the already added bitmaps. The result is an AVI file.

As a simple sample, I wrote a procedure which generate an AVI file made of 25 bitmaps generated on the fly. Each bitmap contain the text “Delphi rocks!” and the number of the frame. This is for simple demo. As I said above, you could as well produce bitmap with a 3D scene viewed from a moving point of view, or a zoom into a Mandelbrot fractal.

procedure TCAviFileForm.GenerateAvi
var
    I          : Integer;
    Y          : Integer;
    H          : Integer;
    BackBitmap : Graphics.TBitMap;
    Avi        : TAviFromBitmaps;
begin
    BackBitmap             := Graphics.TBitMap.Create;
    BackBitmap.Width       := 320;
    BackBitmap.Height      := 240;
    BackBitmap.PixelFormat := TPixelFormat.pf32bit;

    Avi := TAviFromBitmaps.CreateAviFile(
                nil, 'output.avi',
                //MKFOURCC('x', 'v', 'i', 'd'),// XVID (MPEG-4) compression
                MKFOURCC('D', 'I', 'B', ' '),  // No compression
                2, 1);                         // 2 frames per second
    // First, add a blank frame
    Avi.AppendNewFrame(BackBitmap.Handle);
    // Then add frames with text
    BackBitmap.Canvas.Font.Size := 20;
    H := (BackBitmap.Canvas.TextHeight('I') * 15) div 10;
    Y := (BackBitmap.Height div 2) - H;
    BackBitmap.Canvas.TextOut(10, Y, 'Delphi rocks!');
    Y := (BackBitmap.Height div 2);
    for I := 1 to 25 do begin
        BackBitmap.Canvas.TextOut(10, Y, IntToStr(I));
        Avi.AppendNewFrame(BackBitmap.Handle);
    end;
    // Finally, add two blank frame
    // (MediaPlayer doesn't show the last two frames)
    BackBitmap.Canvas.FillRect(Rect(0, 0,
                                     BackBitmap.Width, BackBitmap.Height));
    Avi.AppendNewFrame(BackBitmap.Handle);
    Avi.AppendNewFrame(BackBitmap.Handle);

    FreeAndNil(Avi);
    FreeAndNil(BackBitmap);
end;


Compression of the AVI is very important. Without compression, your AVI file will be quickly very large. VfW API is able to compress with about any algorithm. It is enough to install a “codec” into Windows. I like the XVID codec because it produces MPEG-4 video with high quality and high compression. Xvid is an open-source research project focusing on video compression and is a collaborative development effort (http://www.xvid.org). All code is released under the terms of GNU GPL license.

To use XVID compressor, download the setup from http://www.xvid.org and install it. Microsoft Media Player is able to decode XVID compressed movie without installing xvid software because the result is a standard MPEG-4 file.

Each codec is identified by a four character code name “FourCC”. VfW API has a function to transform those four characters codes into an integer value suitable for the rest of the API. This is why you see “MKFOURCC(‘x’, ‘v’, ‘I’, ‘d’)” in the above code.

Of course compression comes with a price: image quality is not the same as the original. So in some applications, you may want to avoid compression. This is done by selecting a compressor of type “MKFOURCC(‘D’, ‘I’, ‘B’, ‘ ’)” (Fourth character is a space).


Download source code from: http://www.overbyte.be/eng/blog_source_code.html
Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be

July 8, 2013

Upgrade from ANY version to XE4

Users of ANY earlier version of RAD Studio, Delphi, C++ Builder or Borland Developer Studio can upgrade to XE4.

From now until 31 July 2013. RAD Studio XE4 gives you the tools you need to create multi-device, true native apps for iOS, Windows and Mac.

NEW! Completely new Delphi iOS visual development solution and mobile compiler
NEW! Create apps for PCs, tablets and smartphones from a single codebase
NEW! Access more databases on more devices with FireDAC
NEW! InterBase IBLite embeddable database for iOS with free unlimited distribution

…and much more.

Visit Embarcadero shop