2004. augusztus 31., kedd

How to load and launch a Control Panel applet


Problem/Question/Abstract:

How to load and launch a Control Panel applet

Answer:

{
Unit AppletLauncher

Version:
1.0  Created: 23.08.98, 11:14:39
Last Modified: 23.08.98, 11:14:39

Author : P. Below
Project: Win32 utilities
Delphi version: 3.x (not tested on 4.x)

Description:
Provides a class to load and launch a control panel applet. The applet is loaded when an
instance of this class is created and unloaded when it is destroyed. Applet dialogs are not
shown unless explicitely requested. The required API definitions are obtained from the Delphi
unit CPL. Processing is complicated by the fact that even Win32 applets may not respond to
CPL_NEWINQUIRE. This requires storage for both old (CPL_INQUIRE) and new style info
records. For a dialog only one of the list will contain a valid pointer to an info record, the other
will contain Nil in that slot.

Note:
There are several errors in the win32 docs for the return values of CPL_ messages. Generally
the CPlApplet entry point will return 0 (False) if the function fails and <> 0 (TRUE) if it succeeds.
The docs indicate the inverse.
}

unit AppletLauncher;

interface

uses
  Classes, Windows, CPL, Graphics, Sysutils;

type
  TAppletLauncher = class
  private
    hCPL: HMODULE; { applet module handle }
    hParent: HWND; { container window handle }
    CPlApplet: TCPlApplet; { applet entry point }
    FDialogCount: Integer; { number of dialogs available }
    FDialogData: TList; { stores TNewCPLnfo records for the dialogs }
    FOldDialogData: TList; { stores TCPlInfo records for the dialog }
    FAppletName: string; { filename of applet }
  protected
    function GetDialogNames(index: Integer): string;
    function GetDialogInfotext(index: Integer): string;
    function GetDialogIcon(index: Integer): HICON;
    function GetDeleteIcon(index: Integer): Boolean;
    function DialogLData(index: Integer): Longint;
    function LoadCPLResourceString(strid: Integer): string;
    procedure ValidateIndex(index: Integer); virtual;
  public
    constructor Create(anAppletName: string; hwndParent: HWND);
    destructor Destroy; override;
    procedure ShowDialog(index: Integer);
    property DialogCount: Integer read FDialogCount;
    property DialogNames[index: Integer]: string read GetDialogNames; default;
    property DialogInfotext[index: Integer]: string read GetDialogInfotext;
    property DialogIcons[index: Integer]: HIcon read GetDialogIcon;
    property DeleteIcon[index: Integer]: Boolean read GetDeleteIcon;
    property AppletName: string read FAppletName;
  end;

  EAppletError = class(Exception);

implementation

resourcestring { change to Const for Win16 }
  errCannotLoadApplet = 'TAppletLauncher: cannot load applet %s, reason: %s.';
  errInvalidApplet = 'TAppletLauncher: %s is not a control panel applet, it does ' + 'not export
    the CPlApplet entry point.';
    errAppletInitializationFailed = 'TAppletLauncher: initialization of applet %s failed.';
  errAppletHasNoDialogs = 'TAppletLauncher: applet %s provides no dialogs.';
  errCannotGetDialogInfo = 'TAppletLauncher: applet %s does not respond to CPL_INQUIRE, ' +
    'the launcher is unable to obtain the required information about ' +
    'the applets dialogs.';
  errSelectDialogFailed = 'TAppletLauncher: applet %s failed to open dialog #%d (%s).';
  errIndexOutOfBounds = 'TAppletLauncher: dialog index %d is invalid for applet %s, ' +
    'the allowed range is 0..%d.';

  {Methods of TAppletLauncher}

  {
  Constructor TAppletLauncher.Create

  Parameters:
  anAppletName:
  file name of the applet to load. This name must contain the CPL extention and can contain a
  full path, if the applet does not reside in the windows or system directories.
  hwndParent:
  handle of window that serves as control panel replacement. Use the main forms handle, for
  example. This window is used as parent for the applets dialogs. If 0 is passed we use the
  active window as parent.

  Call method: static

  Description:
  Loads the applet DLL, creates the internal list and fills it with information records for the dialogs
  the applet provides. Several messages are send to the applets entry point during construction.

  Error Conditions:
  An exception will be raised if the applet could not be loaded or if it does not respond in the
  expected way to the send messages. The object is destroyed automatically in this case.
  }

constructor TAppletLauncher.Create(anAppletName: string; hwndParent: HWND);
var
  i: Integer;
  pData: PNewCPlInfo;
  pOldData: PCPlInfo;
begin
  inherited Create;
  if hwndParent = 0 then
  begin
    hwndparent := GetActiveWindow;
  end;
  hParent := hwndParent;
  { Try to load the applet DLL.  }
  hCPL := LoadLibrary(Pchar(anAppletName));
  if hCPL = 0 then
  begin
    { Error, applet not found. Note: change logic for Win16! }
    raise EAppletError.CreateFmt(errCannotLoadApplet, [anAppletName,
      SysErrorMessage(GetLastError)]);
  end;
  FAppletName := anAppletName;
  { Find applet entry point }
  @CPlApplet := GetProcAddress(hCPL, 'CPlApplet');
  if @CPlApplet = nil then
  begin
    { Entry point not found, this is not a control panel applet! }
    raise EAppletError.CreateFmt(errInvalidApplet, [anAppletName]);
  end;
  { Send CPL_INIT to the applet }
  if CPlApplet(hParent, CPL_INIT, 0, 0) = 0 then
  begin
    { Applet failed to initialize, bail out. }
    raise EAppletError.CreateFmt(errAppletInitializationFailed, [anAppletName]);
  end;
  { Get number of dialogs the applet supports }
  FDialogCount := CPlApplet(hParent, CPL_GETCOUNT, 0, 0);
  if FDialogCount = 0 then
  begin
    raise EAppletError.CreateFmt(errAppletHasNoDialogs, [anAppletName]);
  end;
  { Create list for the dialog information }
  FDialogData := TList.Create;
  FDialogData.Capacity := FDialogCount;
  FOldDialogData := TList.Create;
  FOldDialogData.Capacity := FDialogCount;
  { Get the information for the dialogs }
  for i := 0 to FDialogCount - 1 do
  begin
    New(pData);
    FillChar(pData^, Sizeof(pData^), 0);
    pData^.dwSize := Sizeof(pData^);
    if CPlApplet(hParent, CPL_NEWINQUIRE, i, longint(pData)) = 0 then
    begin
      { Failed, try CPL_INQUIRE instead }
      Dispose(pData);
      New(pOldData);
      if CPlApplet(hParent, CPL_INQUIRE, i, longint(pOldData)) = 0 then
      begin
        { Failed also, bail out }
        Dispose(pOldData);
        raise EAppletError.CreateFmt(errCannotGetDialogInfo, [anAppletName]);
      end
      else
      begin
        FOldDialogData.Add(pOldData);
        FDialogData.Add(nil);
      end;
    end { If }
    else
    begin
      { CPL_NEWINQUIRE succeeded, store the data }
      FDialogData.Add(pData);
      FOldDialogData.Add(nil);
    end;
  end;
  { Setup is complete }
end;

{
Destructor TAppletLauncher.Destroy

Parameters: none

Call method: virtual, overridden

Description:
Releases memory for the dialog data records, destroys the list holding the records, tells the
applet to clean up its act and finally unloads the applet. The destructor can be called on
a partially initialized object if an exception is raised in the constructor.

Error Conditions: none
}

destructor TAppletLauncher.Destroy;
var
  i: Integer;
begin
  { Tell applet to clean up its dialogs and release the dialog data, if initialization
  completed successfully }
  if Assigned(FDialogData) then
  begin
    for i := 0 to FDialogData.Count - 1 do
    begin
      CPlApplet(hParent, CPL_STOP, i, DialogLData(i));
      if Assigned(FDialogData[i]) then
      begin
        Dispose(pNewCPlInfo(FDialogData[i]));
      end;
    end;
    FDialogData.Free;
  end;
  if Assigned(FOldDialogData) then
  begin
    for i := 0 to FOldDialogData.Count - 1 do
    begin
      if Assigned(FOldDialogData[i]) then
      begin
        Dispose(pCPlInfo(FOldDialogData[i]));
      end;
    end;
    FOldDialogData.Free;
  end;
  { Tell applet to clean up, if load was successful. Note: this code is executed even if CPL_INIT
  failed, I don't know if this may cause a problem. }
  if Assigned(@CPlApplet) then
  begin
    CPlApplet(hParent, CPL_EXIT, 0, 0);
  end;
  { Unload the applet, if it was loaded }
  if hCPL <> 0 then
  begin
    FreeLibrary(hCPL);
  end;
  inherited Destroy;
end;

{
Procedure TAppletLauncher.ShowDialog

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Call method: static

Description:
Tells the applet to open the requested dialog.

Error Conditions:
Exceptions will be raised if the passed index is out of bounds or the applet fails to launch the dialog.
}

procedure TAppletLauncher.ShowDialog(index: Integer);
begin
  ValidateIndex(index);
  if CPlApplet(hParent, CPL_DBLCLK, index, DialogLData(index)) = 0 then
  begin
    raise EAppletError.CreateFmt(errSelectDialogFailed, [FAppletName, index, DialogNames[index]]);
  end;
end;

{
Function TAppletLauncher.GetDialogNames

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Returns:
the szname field of the dialog info record for this dialog.

Call method: static

Description:
This method implements read access to the DialogNames property.

Error Conditions:
An exceptions will be raised if the passed index is out of bounds.
}

function TAppletLauncher.GetDialogNames(index: Integer): string;
begin
  ValidateIndex(index);
  if Assigned(FDialogData[index]) then
  begin
    result := Strpas(pNewCPlInfo(FDialogData[index])^.szName);
  end
  else
    result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idName);
end;

{
Function TAppletLauncher.GetDialogInfotext

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Returns:
the szinfo field of the dialog info record for this dialog.

Call method: static

Description:
This method implements read access to the DialogInfotext property.

Error Conditions:
An exceptions will be raised if the passed index is out of bounds.
}

function TAppletLauncher.GetDialogInfotext(index: Integer): string;
begin
  ValidateIndex(index);
  if Assigned(FDialogData[index]) then
  begin
    result := Strpas(pNewCPlInfo(FDialogData[index])^.szInfo);
  end
  else
    result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idInfo);
end;

{
Function TAppletLauncher.GetDialogIcon

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Returns:
the icon handle for the icon to display for this dialog. Note that the handle can be 0!
Use DrawIconEx to display this icon on a canvas, or create a TIcon and assign the
return value to its Handle property.

Call method: static

Description:
This method implements read access to the DialogIcons property.
PROBLEM ALERT!
For applets that respond to CPL_NEWINQUIRE the icon handle is owned by the applet and
must not be deleted by the application. For old-style applets that respond only to CPL_INQUIRE,
however, the icon is created from a resource and the application must delete it to prevent a
resource leak! Check the DeleteIcon property to determine what to do.

Error Conditions:
An exceptions will be raised if the passed index is out of bounds.
}

function TAppletLauncher.GetDialogIcon(index: Integer): HICon;
begin
  ValidateIndex(index);
  if Assigned(FDialogData[index]) then
  begin
    result := pNewCPlInfo(FDialogData[index])^.hIcon;
  end
  else
    result := LoadIcon(hCPL, MakeIntResource(pCPlInfo(FOldDialogData[index])^.idIcon));
end;

{
Function TAppletLauncher.GetDeleteIcon

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Returns:
True if caller needs to delete an icon retrieved via DialogIcons for this index, false otherwise.

Call method: static

Description:
See Problem Alert entry under GetDialogIcon.

Error Conditions:
An exceptions will be raised if the passed index is out of bounds.
}

function TAppletLauncher.GetDeleteIcon(index: Integer): Boolean;
begin
  ValidateIndex(index);
  Result := not Assigned(FDialogData[index]);
end;

{
Procedure TAppletLauncher.ValidateIndex

Parameters:
index: index to validate

Call method: virtual

Error Conditions:
raises an exception if the passed index is out of bounds. You can override this method to
change the behaviour.
}

procedure TAppletLauncher.ValidateIndex(index: Integer);
begin
  if (index < 0) or (index >= FDialogCount) then
  begin
    raise EAppletError.CreateFmt(errIndexOutOfBounds, [index, FAppletName, FDialogCount - 1]);
  end;
end;

{
Function TAppletLauncher.DialogLData

Parameters:
index: dialog index, has to be in the range 0..DialogCount-1

Returns:
the ldata member of the dialog data.

Call method: static

Description:
Helper function to deal with the different data record formats we can have.

Error Conditions: none
}

function TAppletLauncher.DialogLData(index: Integer): Longint;
begin
  if Assigned(FDialogData[index]) then
    Result := pNewCPlInfo(FDialogData[index])^.ldata
  else
    Result := pCPlInfo(FOldDialogData[index])^.ldata
end;

{
Function TAppletLauncher.LoadCPLResourceString

Parameters:
strid: resource id of string to load

Returns:
the string

Call method: static

Description:
Helper function to get a resource string from the applet.

Error Conditions: none
}

function TAppletLauncher.LoadCPLResourceString(strid: Integer): string;
begin
  SetLength(result, 1024);
  SetLength(result, LoadString(hCPL, strid, @result[1], 1024));
end;

end.

2004. augusztus 30., hétfő

Remote port scanner


Problem/Question/Abstract:

Ever needed to test open ports on your machine?

Answer:

You can write a small utility for this purpose in Delphi, using sockects... here's my approach.

Use this code under you own risk, I present this article for educational purposes only, I take no responsability for the use of it.

I'll put a link to the whole demo, here's the unit, I'm sure you can recreate the form and run this:

unit PortScanU;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ScktComp;

type
  TMainForm = class(TForm)
    LblIPAddress: TLabel;
    IPAddressE: TEdit;
    lblScanRange: TLabel;
    MinPortE: TEdit;
    lblPorttoport: TLabel;
    MaxPortE: TEdit;
    StatusL: TLabel;
    ActivityLB: TListBox;
    StartBtn: TButton;
    WSsocket: TClientSocket;
    StopBtn: TButton;
    OpenOnlyCB: TCheckBox;
    procedure StartBtnClick(Sender: TObject);
    procedure WSsocketConnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure WSsocketError(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    procedure StopBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    PortX, MaxPort: Integer;
    IsRunning: Boolean;
    procedure SetStuffOnOff(const St: Boolean);
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;
implementation

{$R *.dfm}

procedure TMainForm.SetStuffOnOff(const St: Boolean);
begin
  IsRunning := St;
  StopBtn.Enabled := St;
  StartBtn.Enabled := not St;
  if not (St) then
  begin
    ActivityLB.Items.Add('Done Scanning ' + IPAddressE.text);
    StatusL.Caption := 'Status:'
  end
end;

procedure TMainForm.StartBtnClick(Sender: TObject);
begin
  ActivityLB.Items.Clear;
  PortX := StrToInt(MinPortE.text);
  MaxPort := StrToInt(MaxPortE.text);

  wsSocket.Address := IPAddressE.text;
  wsSocket.Port := PortX;
  wsSocket.Active := True;

  SetStuffOnOff(True);
  ActivityLB.Items.Add('Beginning scan: ' + IPAddressE.text)
end;

procedure TMainForm.WSsocketConnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  //socket connection made
  //port must be open!
  ActivityLB.Items.Add('PORT: ' + inttostr(PortX) + '; OPEN!');

  //try next port...
  wsSocket.Active := False;
  PortX := PortX + 1;
  wsSocket.Port := PortX;
  StatusL.Caption := 'Scanning port:[' + IntToStr(PortX) + ']';

  if (IsRunning) then
    if (PortX > MaxPort) then
      SetStuffOnOff(False)
    else
      wsSocket.Active := True //test the new port
end;

procedure TMainForm.WSsocketError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
  //connection failed....
  ErrorCode := 0; //handle the error
  if not (OpenOnlyCB.Checked) then
    ActivityLB.Items.Add('Port: ' + inttostr(PortX) + '; Closed.');

  //try next port
  wsSocket.Active := False; //close it
  PortX := PortX + 1; //new port to check
  wsSocket.Port := PortX; //put the port in the socket
  StatusL.Caption := 'Scanning port:[' + IntToStr(PortX) + ']';

  if (IsRunning) then
    if (PortX > MaxPort) then
      SetStuffOnOff(False)
    else
      wsSocket.Active := True //test the new port
end;

procedure TMainForm.StopBtnClick(Sender: TObject);
begin
  SetStuffOnOff(False);
  wssocket.Active := False;
  ActivityLB.Items.Add('Stoped scan; port ' + inttostr(PortX) + '!')
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  IsRunning := False
end;

end.

as you can see, the idea is pretty easy...

set the address
set the port
try to activate the socket
and check if it went good
next port, repeat steps

note:to test ports on your local machine you need to set  IPAddressE.Text:='localhost'

2004. augusztus 29., vasárnap

Get the time stamp of a directory


Problem/Question/Abstract:

How to get the time stamp of a directory

Answer:

Solve 1:

program Project1;

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

function GetTimeStamp(Directory: string): TDateTime;
var
  Search: TSearchRec;
begin
  Result := 0;
  if FindFirst(Directory + '.', faDirectory, Search) = 0 then
  begin
    Result := FileDateToDateTime(Search.Time);
  end
  else
    MessageDlg('Directory doesnt exist', mtWarning, [mbOK], 0);
  FindClose(Search);
end;

begin
  MessageDlg('Folder Create Date: ' + DateTimeToStr(GetTimeStamp('c:\temp\')),
    mtWarning, [mbOK], 0);
end.


Solve 2:

function FileTimeToDateTime(FileTime: TFileTime): TDateTime;
var
  LocalFileTime: TFileTime;
  SystemTime: TSystemTime;
begin
  FileTimeToLocalFileTime(FileTime, LocalFileTime);
  FileTimeToSystemTime(LocalFileTime, SystemTime);
  Result := SystemTimeToDateTime(SystemTime);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  FSearchRec: TSearchRec;
  DirDate: TDateTime;
begin
  FindFirst('C:\Temp', faDirectory, FSearchRec);
  DirDate := FileTimeToDateTime(FSearchRec.FindData.ftCreationTime);
  ShowMessage(DateTimeToStr(DirDate));
end;

2004. augusztus 28., szombat

The Observer pattern


Problem/Question/Abstract:

How can many objects be notified by an event?

Answer:

Sometimes, there is a need to notify many different objects about a state change. My favorite solution to this problem is the Observer pattern, described in Design Patterns. (I highly recommend this book). Roughly, this patterns describe a way for objects that want to be notified of a change (called the Observers) to register themselves with the subject (called the Subject :). This subject then has the responsability to notify all it's observers when it's internal state changes.

And this is where we encounter our first problem. How does the Subject notify it's Observers? By calling one of the Observer's method. But this means the Subject has to know the Observer somehow, not very flexible. We could use an abstract Observer class, in that way our Subject would always be able to call a known method, but this would force us to always descend observers from the same hiearchy, which is not very practical (and sometimes completely impossible).

Fortunately, for each problem there is a solution, and in our case there are at least TWO!

The first solution is to use Interfaces. Our Subject would accept an IObserver interface. The neat thing about interfaces is that any class can implement any interface, and as long as an object would implement IObserver, it would be able to connect to any subject. (If anyone is interested in this implementation, let me know and I'll post another article, but there are gazillions of examples on the net if you search a little). This works perfectly, but after working with interfaces a little, I decided not to use them. The main reason for this is that code navigation in the IDE is harder, Ctrl-Click brings you to the interface definition, not the implementation. No big deal. But I wanted something better.

The second solution, the one I'm describing in this article, is a little different. The Observer is no longer an object, it's a method. Just like standard VCL events. This means that a single event handler could be used with a component and a Subject at the same time.

Let say we have a TSubject with an event called OnChange. This event is of type TMultiNotifyEvent. Here is how a user would connect to this event. ChangeHandler is a procedure matching a TNotifyEvent.

MySubject.OnChange.Attach(ChangeHandler);

From that point on, every time MySubject changes, it will call ChangeHandler, just like a normal OnChange event. The difference is that there might be many observers of that event. When the object no longer wish to receive updates from MySubject, it detaches:

MySubject.OnChange.Detach(ChangeHandler);

In order for TMySubject to use a TMultiNotifyEvent, it must create it like this:

type
  TMySubject = class
  private
    FOnChange: TMultiNotifyEvent;
  protected
    procedure DoChange; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    property OnChange: TMultiNotifyEvent read FOnChange;
  end;

implementation

constructor TMySubject.Create;
begin
  inherited;
  FOnChange := TMultiNotifyEvent.Create;
end;

destructor TMySubject.Destroy;
begin
  FOnChange.Free;
  inherited;
end;

procedure TMySubject.DoChange;
begin
  { Signal is the method that notify every observers }
  OnChange.Signal(Self);
end;

In order to use a new type of event, one must declare a new class inheriting from TMultiEvent. Why am I doing this? Because TMultiEvent only stores and knows about TMethod records, it does not know what type of event is used, what are it's parameters, etc. By having the SignalObserver method Abstract, each concrete class can typecast it to the type of events it handles and pass all the required parameters. Also, creating a new class provices a certain level of type by making sure you register a compatible method with the subject. See TMultiNotifyEvent at the end of this article to see how to create customized events, it's pretty straight forward.

That's it for the explication, if you have any questions just leave a comment and I'll try to update the article accordingly.

unit uMultiEvent;

interface

uses
  Classes, SysUtils;

type
  TMultiEvent = class
  private
    FObservers: TList;
  protected
    function FindObserver(Observer: TMethod): integer;
    function GetObserver(Index: integer): TMethod;
    procedure SignalObserver(Observer: TMethod); virtual;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Attach(Observer: TMethod);
    procedure Detach(Observer: TMethod);

    procedure Signal;
  end;

  TMultiNotifyEvent = class(TMultiEvent)
  private
    FSender: TObject;
  protected
    procedure SignalObserver(Observer: TMethod); override;
  public
    procedure Attach(Observer: TNotifyEvent);
    procedure Detach(Observer: TNotifyEvent);

    procedure Signal(Sender: TObject);
  end;

implementation

{ TEvent }

procedure TMultiEvent.Attach(Observer: TMethod);
var
  Index: integer;
begin
  Index := FindObserver(Observer);

  { This assertion is facultative, we could just ignore observers  }
  { already attached, but it's a good way to detect problems early }
  { and avoid unnecessary processing.                              }

  Assert(Index < 0, 'This observer was already attached to this event');

  { A method contains two pointers:                              }
  { - The code pointer, that's where the procedure is in memory  }
  { - The data pointer, this tells Delphi what instance of the   }
  {   object calls the procedure                                 }
  { We must store both pointers in order to use that callback.   }

  if Index < 0 then
  begin
    FObservers.Add(Observer.Code);
    FObservers.Add(Observer.Data);
  end;
end;

constructor TMultiEvent.Create;
begin
  inherited;
  FObservers := TList.Create;
end;

destructor TMultiEvent.Destroy;
begin
  { This assertion is facultative, but I prefer when all my objects }
  { are "clean" when they are destroyed.                            }
  Assert(FObservers.Count = 0, 'Not all observers were detached');
  FreeAndNil(FObservers);
  inherited;
end;

procedure TMultiEvent.Detach(Observer: TMethod);
var
  Index: integer;
begin
  Index := FindObserver(Observer) * 2;

  { Again, the assertion is facultative, nothing would be broken }
  { if we just ignored it.                                       }
  Assert(Index >= 0, 'The observer was not attached to this event');

  if Index >= 0 then
  begin
    FObservers.Delete(Index); // Delete code pointer
    FObservers.Delete(Index); // Delete data pointer
  end;
end;

function TMultiEvent.FindObserver(Observer: TMethod): integer;
var
  i: integer;
begin
  { Search fails by default, if there is a match, result will be updated. }
  Result := -1;
  for i := (FObservers.Count div 2) - 1 downto 0 do
  begin
    { We have a match only if both the Code and Data pointers are the same. }
    if (Observer.Code = FObservers[i * 2]) and (Observer.Data = FObservers[i * 2 + 1])
      then
    begin
      Result := i;
      break;
    end;
  end;
end;

function TMultiEvent.GetObserver(Index: integer): TMethod;
begin
  { Fill the TMethod record with the code and data pointers. }
  Result.Code := FObservers[Index * 2];
  Result.Data := FObservers[Index * 2 + 1];
end;

procedure TMultiEvent.SignalObserver(Observer: TMethod);
begin
  { Descendants must take care to notify the Observer by themselves }
  { because we cannot know the parameters required by the event.    }

  Assert(Assigned(@Observer));
  { We could make this method Abstract and force descendants, but   }
  { I prefer to do a run-time check to validate the passe methods   }
end;

procedure TMultiEvent.Signal;
var
  i: integer;
begin
  { Call SignalObserver for each stored observers in reverse order. }

  { SignalObserver (which is declared in sub-classes) will typecast }
  { the TMethod record into whatever procedure type it handles.     }
  { See the TMultiNotifyEvent below for an example.                 }

  for i := (FObservers.Count div 2) - 1 downto 0 do
  begin
    SignalObserver(GetObserver(i));
  end;
end;

{ TMultiNotifyEvent }

procedure TMultiNotifyEvent.Attach(Observer: TNotifyEvent);
begin
  inherited Attach(TMethod(Observer));
end;

procedure TMultiNotifyEvent.Detach(Observer: TNotifyEvent);
begin
  inherited Detach(TMethod(Observer));
end;

procedure TMultiNotifyEvent.Signal(Sender: TObject);
begin
  FSender := Sender;
  inherited Signal;
end;

procedure TMultiNotifyEvent.SignalObserver(Observer: TMethod);
begin
  inherited;
  TNotifyEvent(Observer)(FSender);
end;

end.

2004. augusztus 27., péntek

Attributes/Advantages Of COM


Problem/Question/Abstract:

The Advantages of using COM.

Answer:

Nowadays, COM is gaining popularity as most of the languages support development of COM objects including Delphi. In future, all the activities of an Operating system and applications would be services provided by COM. So the users can use those services as easily as Plug-And-Play.

In this article, let me present you some of the advantages/attributes (but most of  them are attributes) of COM that I learned.

1. Object Oriented

All the COM objects are Object Oriented as its name implies. There are three basic Object Oriented concepts:

Encapsulation: This is implemented by the interfaces provided by the COM Objects. Those interfaces hide the implementation details from the end user and provides the functionality to the user. By that, the user just needs to know what interface methods to call, to do a specific operation  provided by that COM object.

Polymorphism: This is often called &#8220;One Method Multiple Interfaces&#8221;. A COM object could define a single method to perform a specific operation; but that operation could be implemented through various interfaces/ways.

Inheritance: When we want to incorporate some additional functionalities to an existing COM object, we can enhance the existing COM object by inheriting a new COM object from it.

COM incorporates all those three concepts in it.

2. Loose Coupling

In a software, that uses COM objects, we can easily replace an existing COM object with another COM object written in entirely another language as long as the signatures of the methods in both the COM objects remain same. In that case, there will not be any change in the existing software code that uses the COM object.

3. Easy Transition

Let us suppose that a software uses a specific COM object provided by a third party. At some point of time, that third party might upgrade that COM object to incorporate some additional functionalities. At this time, if we would like to use the latest version of that COM object in our software, we can just remove the existing (old) version of that COM object and place this new version of COM into our software as long as the signatures of the methods in the old version of the COM object do not change in the new version. But this is a standard for any COM objects provided by third parties and for anybody who develops COM objects. If they upgrade the existing version of COM object, they will not alter the existing signatures but might add some optional parameters to those methods; But that will not affect the existing system to function properly. So even if we develop a COM object and would like to upgrade, then we should keep in mind the existing method signatures. At any point of time the existing system should not get affected by upgrading to a newer version of COM. This is rather a defined norm in developing COM objects. So transition is easy.

4. Binary Language

Since most of the COM libraries are in binary language, it could be used by any application written in any language. So COM is language neutral. This is one of the main advantages of COM; it's language independent.

5. Resource utilization

Every COM object will be destroyed automatically as long as no client is using that object actively. This is implemented by COM using a technique called reference counting. Every COM object will maintain a reference count(the number of clients using that COM object); Once that count reaches zero(that means no clients actively using that COM object), then that COM object will be destroyed automatically. With this approach, we can increase resource utilization in an application. Resources such as memory will be best utilized by releasing the inactive/unused COM objects.  

The above listing is just an overview of what COM offers us.

2004. augusztus 26., csütörtök

JIT Activation/Deactivation in COM+


Problem/Question/Abstract:

What is JIT Activation/Deactivation? Why do we need this?

Answer:

Why do we need this service?

In an application using COM objects, a COM object will be created when you call a method like CreateCOMObject etc., So once that method is called, that specific COM object will be created and the application can call the methods of that object. In other words, that COM object will be activated after that call to CreateCOMObject. Right.. That object will be in memory as long as any one of the applications is holding a reference to that object irrespective of whether that application is calling the object's methods or not. So the bottomline is a valuable memory resource is wasted if the application is not actively using the COM object.

JIT Activation/Deactivation is introduced in COM+ mainly for the effective usage of reources and to avoid the resource wastage as mentioned above.

What is JIT Activation/Deactivation?

It is one of the services provided by COM+ runtime.JIT stands for Just-In-Time. In COM+, we can set this service to a COM object through the Component Services Explorer.

How does it function?

Once you set this service to a COM object through the Component Services Explorer, the activation of that COM object is delayed until one of its methods is first called. So even though you call the CreateCOMObject methods and get a reference to that COM object, that object will not be activated until you call one of its methods. Also once every method call is completed, that COM object will be deactivated to conserve resource.

Pros/Cons

As I mentioned above, the main advantage is that the reources will be effectively used. Since every time you call a method/property of that COM object, that COM object will be activated every time. Consider the following scenerio:

begin
  COMObj := CreateCOMObject("...");
  COMObj.Name := 'My Name';
  COMObj.SSN := '737387289';
  { ..................................
   ...............................etc., }
  COMObj.UpdateDetails;
end;

In the sample code above, I created a COM Object and set some properties and call UpdateDetails method to update the Name and SSN details. Let us assume that the COMObj is using the JIT Activation/Deactivation service provided in the Component Services Explorer. So every time a property value is assigned, a new COM object will be activated and deactivated once that value is assigned. Finally when you call the UpdateDetails method, a new COM object will be activated and it doesn't know anything of the previous assignment and so the values will not be updated. Then what is the use of this JIT Activation/Deactivation? We have to change the approach in writing COM object. The alternate for the above code would be like this:

begin
  COMObj := CreateCOMObject("");
  COMObj.UpdateDetails "My Name", "737387289", ...etc.,
end;

So after that call is over, the object will be deactivated. The method UpdateDetails will get all the info as parameters to that method. We can consider this either as an advantage or disadvantage.

Another question that comes to our mind now is how much time will that COM+ runtime take to activate/deactivate a COM object? But people using COM+ are claiming that the time taken to activate/deactivate a COM object is very less. How much? Do you people at Delphi3000 community have any ideas about that? Is it worth having JIT Activation/Deactivation service? Please feel free to post your views and letz start a discussion on that as I continue to explore more into COM and COM+. Also if any one of our community people have any experience on using these services, please feel free to share.

2004. augusztus 25., szerda

PAS 2 HTML converter


Problem/Question/Abstract:

actually... source-code2HTML converter...

Answer:

I created this unit that has only one procedure:

procedure Source2Htm(Strs: TStrings);

why did I call it "source2htm" and not "PAS2HTM"?
well, because I think you can use this unit not only to convert Pascal-Delphi source code to html, if you modify the "keywords" you can use the same source code to hightlight pretty much any source code... so... given that...
oh... there's also some constants that you might want to look at:

ModifB (Modificator begin "<B>" default)
ModifE (Modificator end "</B>"default)

this modificators get inserted on  your source code to highlight the keywords that you want, but, you could change those to make the sourcecode look hightlighted and italic style, or whatever you want to
another constan is

s2hFontModif (default "verdana size=2")... I put that so I could put source code that looks "aligned" or whatever special font you want to use fo it
Comments (default to "//"): I just fixed this to not-highlight whatever is after that
AddBR (Default to True) defines if BR is added at the end of each line

...here's the unit (I processed this unit with my own unit!)

------------------------------------Unit Src2htm.pas----------------
unit src2htm;
//- Programmed by: EberSys
// - 03-21-2002 - v1.1
// - Added support for Delphi comments
// - Added - User may specify s2hFontModif='' so no font will be specified for the html
// - use it freely and if you make any improvements I'd appreciate if you would let me know
//

interface

uses classes;

var
  KeyWords: TStrings;

const
  ConvertExact: Boolean = True;
  AddBR: Boolean = True;
  ModifB: string = '<b>';
  ModifE: string = '</b>';
  s2hFontModif: string = ''; //'face="verdana" size="2"';
  Comments: string = '//';

procedure Source2Htm(Strs: TStrings);

implementation

uses SysUtils;

procedure Source2Htm(Strs: TStrings);
var
  X, Y: Integer;
  ALine: string;

  function Line2Html(ALine: string): string;
  var
    OriLine, KW, RealKW: string;
    X, Posi, Len, SmallestPosi, CommentPosi: Integer;
    Found: Boolean;
  begin
    Result := '';
    if not (ALine = '') then
    begin
      OriLine := ALine;
      ALine := UpperCase(ALine);
      Found := False;
      SmallestPosi := Length(ALine);
      //we have to lookup all the keywords
      //to validate code like:
      //For X:=0 To 40 Do
      //if you find "Do" first, it wouldn't
      //fix the words "For" and "To"
      //this way, we keep looking 'til we find out
      //that "For" is in the Smallest Position
      RealKW := '';
      for X := 0 to KeyWords.Count - 1 do
      begin
        Posi := Pos(UpperCase(KeyWords[X]), ALine);
        if (Posi > 0) and (Posi < SmallestPosi) then
        begin
          KW := KeyWords[X];
          Len := Length(KeyWords[X]);
          if ((Posi = 1) or (ALine[Posi - 1] in [' ', '=', '(']))
            and not (ALine[Posi + Len] in ['a'..'z', 'A'..'Z', '0'..'9', '_']) then
          begin
            Found := True;
            SmallestPosi := Posi;
            RealKW := KW;
            if (Posi = 1) then //there's no smaller than 1
              Break
          end
        end
      end;
      CommentPosi := Pos(Comments, ALine);
      if (CommentPosi = 0) then
        CommentPosi := SmallestPosi;

      if (Found) and (CommentPosi >= SmallestPosi) then
      begin
        if (SmallestPosi = 1)
          or (ALine[SmallestPosi - 1] = ' ')
          or (ALine[SmallestPosi - 1] = '(')
          or (ALine[SmallestPosi - 1] = '=') then
        begin
          Len := Length(RealKW);
          Result := Result + Copy(OriLine, 1, SmallestPosi - 1) + ModifB + RealKW +
            ModifE;
          Delete(OriLine, 1, SmallestPosi + Len - 1)
        end;
        Result := Result + Line2Html(OriLine)
      end
      else
        Result := Result + OriLine
    end
  end;

  function ReplaceAll(FindStr, ReplaceWith, ReplaceIn: string): string;
  var
    Posi: Integer;
  begin
    Y := 1;
    Result := '';
    if not (ReplaceIn = '') then
    begin
      Posi := Pos(FindStr, ReplaceIn);
      if (Posi > 0) then
      begin
        Result := Result + Copy(ReplaceIn, 1, Posi - 1) + ReplaceWith;
        Delete(ReplaceIn, 1, Posi);
        Result := Result + ReplaceAll(FindStr, ReplaceWith, ReplaceIn)
      end
      else
        Result := ReplaceIn
    end
  end;

begin
  Strs.Add('');
  for X := 0 to Strs.Count - 1 do
  begin
    ALine := Strs[X];
    ALine := ReplaceAll('&', '&amp', ALine);
    ALine := ReplaceAll('<', '&lt', ALine);
    ALine := Line2Html(ALine);
    Y := 1;
    if not (ALine = '') then
      while (ALine[Y] = ' ') do
      begin
        Delete(ALine, Y, 1);
        Insert('&nbsp;', ALine, 1);
        Inc(Y, 6)
      end;
    if (AddBR) then
      Strs[X] := ALine + '<br>'
    else
      Strs[X] := ALine
  end;
  if not (s2hFontModif = '') then
  begin
    Strs.Insert(0, '<font ' + s2hFontModif + '>');
    strs.Add('</font>')
  end
end;

initialization
  KeyWords := TStringList.Create;

  KeyWords.Add('And');
  KeyWords.Add('Array');
  KeyWords.Add('As');
  KeyWords.Add('Asm');
  KeyWords.Add('Automated');
  KeyWords.Add('Begin');
  KeyWords.Add('Case');
  KeyWords.Add('Class');
  KeyWords.Add('Const');
  KeyWords.Add('Constructor');
  KeyWords.Add('Destructor');
  KeyWords.Add('dispinterface');
  KeyWords.Add('Div');
  KeyWords.Add('For'); //fix it
  KeyWords.Add('To'); //fix it
  KeyWords.Add('While'); //fix it
  KeyWords.Add('On'); //fix it
  KeyWords.Add('DownTo'); //fix it
  KeyWords.Add('Do');
  KeyWords.Add('Else');
  KeyWords.Add('End');
  KeyWords.Add('Except');
  KeyWords.Add('Exports');
  KeyWords.Add('File');
  KeyWords.Add('Finalization');
  KeyWords.Add('Finally');
  KeyWords.Add('Function');
  KeyWords.Add('Goto');
  KeyWords.Add('If');
  KeyWords.Add('Implementation');
  KeyWords.Add('In');
  KeyWords.Add('Inherited');
  KeyWords.Add('Initialization');
  KeyWords.Add('Inline');
  KeyWords.Add('Interface');
  KeyWords.Add('Is');
  KeyWords.Add('Label');
  KeyWords.Add('Library');
  KeyWords.Add('Message');
  KeyWords.Add('Mod');
  KeyWords.Add('Nil');
  KeyWords.Add('Not');
  KeyWords.Add('Object');
  KeyWords.Add('Of');
  KeyWords.Add('Or'); //fixed
  KeyWords.Add('Out');
  KeyWords.Add('Packed');
  KeyWords.Add('Private');
  KeyWords.Add('Procedure');
  KeyWords.Add('Program');
  KeyWords.Add('Property');
  KeyWords.Add('Protected');
  KeyWords.Add('Public');
  KeyWords.Add('Published');
  KeyWords.Add('Raise');
  KeyWords.Add('Record');
  KeyWords.Add('Repeat');
  KeyWords.Add('ResourceString');
  KeyWords.Add('Set');
  KeyWords.Add('Shl');
  KeyWords.Add('Shr');
  KeyWords.Add('String');
  KeyWords.Add('Then');
  KeyWords.Add('Threadvar');
  KeyWords.Add('Try');
  KeyWords.Add('Type');
  KeyWords.Add('Unit');
  KeyWords.Add('Until');
  KeyWords.Add('Uses');
  KeyWords.Add('Var');
  KeyWords.Add('Whith');
  KeyWords.Add('Xor');

finalization
  KeyWords.Free

end.
------------------------------------Unit Src2htm.pas----------------

...ok, now for those people who like examples... here it is...
just create a new project, drop a TMemo and a TButton on your form... add src2htm to the uses seccion... on the onclick event of your button put this code:

procedure TForm1.Button1Click(Sender: TObject);
begin
  AddBR := False;
    //set this to false if you are going to upload an article to Delphi3000!!!
  Source2Htm(Memo1.Lines)
end;

and run the program!

now, while the program is running go back to your source code, copy it all and paste it on the Memo of your running program, click the button... and voila!...

I hope this is useful, there's still some improvements that can be made... I'll update it if you are interested

2004. augusztus 24., kedd

Checking for a LAN connection


Problem/Question/Abstract:

Checking for a LAN connection

Answer:

To check for a LAN connection, various things can be done, like checking for your user name, or retrieving logon information from the registry, but they can all fail if you consider a dock able notebook. The following function searches for actual existing connections.

const
  MAX_NEIGHBORS = 20;

function NetAvailable: Boolean;
var
  NetRes: array[0..MAX_NEIGHBORS] of TNetResource;
  NNError,
    hEnum,
    EntryCount,
    NetResLen: DWORD;
  loop1: Integer;
begin
  hEnum := 0;
  Result := FALSE;
  try
    NNError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, nil, hEnum);
    if NNError = NO_ERROR then
    begin
      while NNError <> ERROR_NO_MORE_ITEMS do
      begin
        EntryCount := 1;
        NetResLen := SizeOf(NetRes);
        NNError := WNetEnumResource(hEnum, EntryCount, @NetRes, NetResLen);
        if (NNError = NO_ERROR) then
        begin
          for loop1 := 1 to EntryCount do
          begin
            if Pos('Microsoft', NetRes[0].lpProvider) = 1 then
            begin
              Result := TRUE;
              Break
            end
          end
        end
        else
        begin
          Break
        end
      end;
      WNetCloseEnum(hEnum)
        // close enum
    end
  except
    on exception do
      begin
        ShowMessage('Network Neighborhood Detection Failed.')
      end;
  end
end;

2004. augusztus 23., hétfő

Create a colored caret in a TMemo


Problem/Question/Abstract:

How to create a colored caret in a TMemo

Answer:

It is possible to color the caret. Windows uses inverse color values for its carets. Black for white, white for black, etc. I made a blue caret by making a bitmap 7 pixels wide and 14 pixels tall with a black background and yellow foreground. Here's how I handled it: I'm not sure if I am handling the destruction correctly or not, that is something I will leave up to you to figure out...

unit caret1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

const
  MY_POST_ENTER = WM_USER + 500;

type

  TForm1 = class(TForm)
    Edit1: TEdit;
    Memo1: TMemo;
    procedure Memo1Exit(Sender: TObject);
    procedure Memo1Change(Sender: TObject);
    procedure Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure Memo1Enter(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure MyPost_Enter(var Message: TMessage); message MY_POST_ENTER;
  end;

var
  Form1: TForm1;
  hCaret: HBITMAP;

implementation

{$R *.DFM}
{$R caret2.res} // caret2.res has the 7 x 14 bitmap in it

procedure TForm1.MyPost_Enter(var Message: TMessage);
var
  CaretHeight: Integer;
begin
  hCaret := LoadBitmap(hInstance, MAKEINTRESOURCE(150));
  CreateCaret(TWinControl(ActiveControl).Handle, hCaret, 0, 0);
  ShowCaret(TWinControl(ActiveControl).Handle);
end;

procedure TForm1.Memo1Exit(Sender: TObject);
begin
  DestroyCaret;
  CreateCaret(TWinControl(ActiveControl).Handle, 1, 1, 1);
  ShowCaret(TWinControl(ActiveControl).Handle);
end;

procedure TForm1.Memo1Change(Sender: TObject);
begin
  PostMessage(Handle, MY_POST_ENTER, 0, 0);
end;

procedure TForm1.Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  PostMessage(Handle, MY_POST_ENTER, 0, 0);
end;

procedure TForm1.Memo1Enter(Sender: TObject);
begin
  PostMessage(Handle, MY_POST_ENTER, 0, 0);
end;

end.

DFM Text:

object Form1: TForm1
  Left = 270
    Top = 97
    Width = 214
    Height = 192
    Caption = 'Form1'
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    OldCreateOrder = False
    PixelsPerInch = 96
    TextHeight = 13
    object Edit1: TEdit
    Left = 16
      Top = 112
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'Edit1'
      OnChange = Memo1Change
      OnEnter = Memo1Enter
      OnExit = Memo1Exit
  end
  object Memo1: TMemo
    Left = 8
      Top = 8
      Width = 185
      Height = 89
      Lines.Strings = ('Memo1')
      TabOrder = 1
      OnChange = Memo1Change
      OnEnter = Memo1Enter
      OnExit = Memo1Exit
  end
end

2004. augusztus 22., vasárnap

Create PDF files from your QReports with Adobe Acrobat


Problem/Question/Abstract:

How to print programmatically from my Delphi app to my PDFWriter driver, setting the correct filename?

Answer:

Overview

Last week we have seen how to create programmatically a report with a PDF report engine. Now let's try to use the original solution from Adobe for the same purpose and how to leverage the existing reports without having to recode them (the main drawback of a pdf-engine based solution).

If you want to print a report (e.g. QReport) directly to PDF without annoying "Save as..." pop up windows, you've got to face 2 different problems.

First of all, you must set programmatically the printer inside the exe and force the Report Engine to print directly. The difficulty and the technique depends on the report engine you have choosen.

Then, PDFWriter wasn't designed for automation of simple tasks, for use by developers or for unattended operations, like the ones that occur on servers and batch processing systems, so you will have to use some workarounds.

Worst of all, although there are some techniques to print to a specific filename with Acrobat, these techniques change from version to version of Adove Acrobat and even depending on the Operating System you use (pls note that I haven't tested yet the new Acrobat 5.0 version).

Solution

Let's start with the first problem. Every Report Engine has its own rules, however I will use for my example QR, the most commonly used VCL solution.
You can set the printer with the PrinterSettings.PrinterIndex property; if set to -1 QRprints directly tothe default printer. Pls note that you aren't forced to show the form with QR (but, of course, you have to create it, which can be a problema in some kind of applications who don't admit forms)

QRLabelForm.QuickRep1.PrinterSettings.PrinterIndex := -1; // use the default printer
QRLabelForm.QuickRep1.Print; // print

(Please note that depending on the reporting engine and the PDF Driver, a progression windows could appear)

We have solved the first little problem, let's look for the second.
Pay attention: you must act differently whether you use WinNT/2K or Win9x.

If you have Acrobat 3.0x or + on Win9x the solution is to modify from your delphi exe/dll the Win.ini as follows:

[Acrobat PDFWriter]
PDFFileName=C:\WINDOWS\TEMP\TMP.PDF // your favourite path...
bDocInfo=0

To print to a particular filename, you should take care to modify the win.ini file every time, being sure that no one else is using the Printer Driver in the same moment (should it happen, abnormal errors, wrong filnames and other problems will surely arise). Of course, as web applications are multithreaded by nature, you must also prepare a lock/unlock system to avoid concurrent requests in this case. (I think that only a mad would use a printer driver in a web app, but "never say never")

If you have Acrobat 4.0 or + and WinNT/2K things are slightly different.
Though the Adobe documentation affirms that concurrent printing is supported in this combination od Product/OS, my tests have showed that after a 3/4 users a multithreaded server app would crash or behave erratically; I would suggest to use this solution on a Server, but it is almost theoretically possible.

If you your OS is WinNT/2K you have to modify registry settings instead of .ini files.
In brief: you should look for the key "HKEY_CURRENT_USER\Software\Adobe\Acrobat PDFWriter" and adding/modifying a key like this (second parameter is a string value of course):

"PDFFileName", "c:\typethefilenameyoupreferhere.pdf"; pay attention, please: if you don't use the asterisk as last character, the string could be killed in regedit!

Now add a second key pair, which will be "bDocInfo", "0" (again string value)

As I don't think you're going to modify the registry programmatically, I would suggest to make these changes to obtain a default file destination, and after every pdf file creation I would rename the file. (Of course, if you want to feel new sensations and love risk, there are some VCL components which permits you to modify the registry; just don' use this on your web server or your boss' PC!)

Recently heard of the possibility to change via ini file the dest PDFFileName in WinNT, too. It uses the file
"c:\winnt\system32\spool\drivers\w32x86\2\__pdf.ini" and uses the same syntax used for W9x. Haven't tried yet, but looks interesting.

Conclusions

Using the PDFWriter driver gives you the best quality available and leverages your investiments on report engines, but remember that the solution lacks in programmatical control and can be a risk in unattended operations and/or multithreaded/multiuser environments.
Also, keep in mind that the cost of the driver is high; if you want to use a printer driver based soution, yoou should consider to substitute the Adobe one with other ones, like the shareware from Dane Prairie (only Win2K) or Zeon (also Win9x), which are more affordable and have a similar behaviour in front of developer's needs.
Another similar solution could be the use of Ghostscript, but remember that it ***isn't free for commercial use*** (many people infringe the licence thinking of it as a free product).

Reference

From Adobe's KB:
  http://www.adobe.com/support/techdocs/66ca.htm

From West Wind Technologies
  http://www.west-wind.com/presentations/pdfwriter/pdfwriter.htm
  (example with FoxPro and ASP, with semaphore system to avoid concurrency problems)

2004. augusztus 21., szombat

Save a TBitmap to a WBMP file


Problem/Question/Abstract:

I needed to save WBMP files for WAP applications. Here is the way I found to do it...

Answer:

Note that the bitmap contained in the TBitmap must be black and white.

function BitmapToWBMP(Bmp: TBitmap; Filename: string): Boolean;
var
  F: file;
  Buf: array[0..256] of Byte;
  BufLen: LongInt;
  BPos: LongInt;
  B: LongInt;
  X: LongInt;
  Y: LongInt;
  procedure Write_To_Header(L: LongInt);
  var
    Extra: LongInt;
    B: Byte;
  begin
    Extra := 0;
    while L >= 128 do
    begin
      Inc(Extra);
      Dec(l, 128);
    end;
    if Extra > 0 then
    begin
      B := 128 + Extra;
      BlockWrite(F, B, 1);
    end;
    B := l;
    BlockWrite(F, B, 1);
  end;
begin
  Result := False;
  if Bmp = nil then
    EXIT;
  if Bmp.Empty then
    EXIT;
  if Bmp.Width = 0 then
    EXIT;
  if Bmp.Height = 0 then
    EXIT;
  AssignFile(F, FileName);
  Rewrite(F, 1);
  if IOResult <> 0 then
    EXIT;
  BufLen := Bmp.Width shr 3 + Byte(Bmp.Width and 7 > 0);
  Write_To_Header(0);
  Write_To_Header(0);
  Write_To_Header(Bmp.Width);
  Write_To_Header(Bmp.Height);
  for Y := 0 to Bmp.Height - 1 do
  begin
    FillChar(Buf, SizeOf(Buf), 0);
    BPos := 0;
    B := 128;
    for X := 0 to Bmp.Width - 1 do
    begin
      if Bmp.Canvas.Pixels[X, Y] <> clBlack then
        Inc(Buf[BPos], B);
      if B > 1 then
        B := B shr 1
      else
      begin
        B := 128;
        Inc(BPos);
      end;
    end;
    BlockWrite(F, Buf, BufLen);
  end;
  CloseFile(F);
  Result := True;
end;

2004. augusztus 20., péntek

Create an ODBC datasource for an Access database


Problem/Question/Abstract:

How to create an ODBC datasource for an Access database

Answer:

unit odbcsetup;
{Written By Aaron Miles 2002
Creates ODBC for Access Database}

interface

uses
  sysutils, windows;

procedure CreateDSN(Name, Database: string);
procedure RemoveDSN(Name: string);

implementation

const
  ODBC_ADD_DSN = 1; {Add data source}
  ODBC_CONFIG_DSN = 2; {Configure (edit) data}
  ODBC_REMOVE_DSN = 3; {Remove data source}
  ODBC_ADD_SYS_DSN = 4; {Add a system DSN}
  ODBC_CONFIG_SYS_DSN = 5; {Configure a system DSN}
  ODBC_REMOVE_SYS_DSN = 6; {Remove a system DSN}
  ODBC_REMOVE_DEFAULT_DSN = 7; {Remove the default DSN}

function SQLConfigDataSource(hwndParent: HWND; fRequest: WORD; lpszDriver: LPCSTR;
  lpszAttributes: LPCSTR): BOOL; stdcall; external 'ODBCCP32.DLL';

procedure CreateDSN(Name, Database: string);
begin
  SQLConfigDataSource(0, ODBC_ADD_DSN, 'Microsoft Access Driver (*.mdb)', PChar('DSN =
    '+Name+ #0 + ' Driver = ODBCJT32.DLL '#0 + ' DBQ = '+Database+ #0 +
    'DefaultDir=' + ExtractFilePath(Database) + #0 + 'Description=Auto Setup
    Aaron Miles '#0 + ' FIL = MS Access '#0 + ' UID = Admin'#0));
end;

procedure RemoveDSN(Name: string);
begin
  SQLConfigDataSource(0, ODBC_REMOVE_DSN, 'Microsoft Access Driver (*.mdb)',
    PChar('DSN=' + Name + #0));
end;

end.

2004. augusztus 19., csütörtök

Using MS Word as report generator


Problem/Question/Abstract:

How to use MS Word as report generator?

Answer:

Why not use the MS Word as report generator in your projects? We can easyly build the report and allow user to modify it using well known editor in any way he wants.

The example below demonstartes how to build the report based on StringGrid contents.

procedure TsiGridReporter.ShowReport;
var
  Range: Variant;
  i, j: integer;
begin
  if FGrid = nil then
    raise Exception.Create('No grid selected!');
  try
    FWordApp := CreateOleObject('Word.Application');
  except
    raise Exception.Create('Cannot start MS Word!');
  end;
  FWordApp.Visible := True;
  FWordApp.Documents.Add;
  if FShowDate then
  begin
    Range := FWordApp.Documents.Item(1);
    Range := Range.Sections.Item(1);
    Range := Range.Headers.Item(1).Range;
    Range.Text := 'Date: ' + DateToStr(Date) + ' Time: ' + TimeToStr(Time);
  end;
  Range := FWordApp.Documents.Item(1);
  Range := Range.Sections.Item(1);
  Range := Range.Footers.Item(1);
  Range.Range.Text := 'Page:';
  Range.Range.ParagraphFormat.Alignment := ord(waAlignParagraphRight);
  Range.PageNumbers.Add;
  FWordApp.Documents.Item(1).Paragraphs.Add;
  Range := FWordApp.Documents.Item(1).Range(
    FWordApp.Documents.Item(1).Paragraphs.Item(FWordApp.Documents.Item(1).Paragraphs.Count - 1).Range.End,
    FWordApp.Documents.Item(1).Paragraphs.Item(FWordApp.Documents.Item(1).Paragraphs.Count - 1).Range.End);
  Range.Text := FTitle;
  Range.Bold := fsBold in FTitleFont.Style;
  Range.Italic := fsItalic in FTitleFont.Style;
  Range.Underline := fsUnderline in FTitleFont.Style;
  Range.Font.StrikeThrough := fsStrikeOut in FTitleFont.Style;
  Range.Font.Name := FTitleFont.Name;
  Range.Font.Size := FTitleFont.Size;
  Range.Font.ColorIndex := ord(FTitleColor);
  Range.ParagraphFormat.Alignment := ord(FTitleAlignment);
  FWordApp.Documents.Item(1).Paragraphs.Add;
  FWordApp.Documents.Item(1).Paragraphs.Add;
  Range := FWordApp.Documents.Item(1).Range(
    FWordApp.Documents.Item(1).Paragraphs.Item(FWordApp.Documents.Item(1).Paragraphs.Count - 1).Range.End,
    FWordApp.Documents.Item(1).Paragraphs.Item(FWordApp.Documents.Item(1).Paragraphs.Count - 1).Range.End);
  FWordApp.Documents.Item(1).Tables.Add(Range, FGrid.RowCount, FGrid.ColCount);
  Range :=
    FWordApp.Documents.Item(1).Tables.Item(FWordApp.Documents.Item(1).Tables.Count);
  for i := 1 to FGrid.RowCount do
    for j := 1 to FGrid.ColCount do
    begin
      Range.Cell(i, j).Range.InsertAfter(FGrid.Cells[j - 1, i - 1]);
      if (i <= FGrid.FixedRows) or (j <= FGrid.FixedCols) then
      begin
        Range.Cell(i, j).Range.Bold := True;
        Range.Cell(i, j).Range.Shading.BackgroundPatternColorIndex := ord(wcGray25);
      end
      else
      begin
        Range.Cell(i, j).Range.Bold := fsBold in FCellFont.Style;
        Range.Cell(i, j).Range.Italic := fsItalic in FCellFont.Style;
        Range.Cell(i, j).Range.Underline := fsUnderline in FCellFont.Style;
        Range.Cell(i, j).Range.Font.StrikeThrough := fsStrikeOut in FCellFont.Style;
        Range.Cell(i, j).Range.Font.Name := FCellFont.Name;
        Range.Cell(i, j).Range.Font.Size := FCellFont.Size;
        //          Range.Cell(i, j).Range.Font.ColorIndex := ord(FCellColor);
        Range.Cell(i, j).Range.Shading.BackgroundPatternColorIndex := FCellColor;
      end;
    end;
end;

This example is just one method of component attached to this article. This component also has the PrintReport and PrintPreview methods.
See attached source code for details. This component could give you just the first step for creating your own full featured report generator based on using MS Word.

P.S. Component and source code are FREEWARE, so you can use it as you want.


Component Download: http://www.sicomponents.com/soft/sireport.zip

2004. augusztus 18., szerda

Open the password-protected xls-file and save without password


Problem/Question/Abstract:

Open the password-protected xls-file and save without password

Answer:

Today I want to show how you may load some xls-file that is password-protected, and how to save xls into another file but without protection.

var
  xls, xlw: Variant;
begin
  {load MS Excel}
  xls := CreateOLEObject('Excel.Application');

  {open your xls-file}
  xlw := xls.WorkBooks.Open(FileName := 'd:\book1.xls', Password := 'qq',
    ReadOnly := True);
  {save with other file name}
  xlw.SaveAs(FileName := 'd:\book2.xls', Password := '');

  {unload MS Excel}
  xlw := UnAssigned;
  xls := UnAssigned;
end;

Just replace there file names and password

2004. augusztus 17., kedd

Coping a record from one Table to Another


Problem/Question/Abstract:

How can I copy the content of a record of a Table to another with the same or similar structure ?

Answer:

When we want to copy a record from a table to another with the same structure we can use the next function: FILLDBF.

For example: I have two tables: TSource and TDest, I want to copy the current record of TSource to TDest. The code is:

TDest.Append;
FillDbf(TSource, TDest);
TDest.Post;

and the function is

procedure FillDbf(xSource, xDestination: TTable);
var
  xList: TStringList;
  i: Integer;
  xF: TField;
begin
  xList := TStringList.Create;
  xSource.GetFieldNames(xList);
  for i := 0 to xList.Count - 1 do
  begin
    xF := xDestination.FindField(xList[i]);
    if xF <> nil then
      xF.AsVariant := xSource.FieldValues[xList[i]];
  end;
  xList.Free;
end;

The function search for the existence on TDest of every field of TSource, and replace them. TSource and TDest doesnt need to have the same structure because only the fields that match on both are replaced.

With this function is very easy to write a class for improve the performance.

2004. augusztus 16., hétfő

StatusBar: any number of panels and use the same procedure to update


Problem/Question/Abstract:

Some times the use of a StatusBar is a hassle, when using 2 or more panels... and probably you write your procedure to update the statusbar... but then you notice you have to add another panel, then you have to change all the occurrences...

Answer:

you can put this procedure in a separate unit ("goodys.pas" in this example)

procedure SetStatusBar(var StB: TStatusBar; Strs: array of string);
var
  X: Byte;
begin
  for X := Low(Strs) to High(Strs) do
    if not (Strs[X] = '') then
      StB.Panels[X].Text := Strs[X];
  Application.ProcessMessages
end;

Then include that unit in your formunit

implementation

uses goodys;

and whenever you need to update your status bar, make a call

SetStatusBar(MyStatusBar, ['panel', '', 'another panel'])

no matter how many panels the StatusBar has, you can use the same procedure:

SetStatusBar(MyStatusBar, ['update my first panel only'])

Notes:
- to empty a panel, make a call with an space:

SetStatusBar(MyStatus, ['panel 2 should be empty', ' '])

- and make sure you have at least one panel in your statusbar, and the simplepanel property is False

2004. augusztus 15., vasárnap

Adding Licencing after control creation


Problem/Question/Abstract:

How do you add licencing to a control after you've created the control?

Answer:

If you want to add licensing to an ActiveForm control after you've created the control, or you want to change the generated license key, here is what you must do.

Go to the Initialization section of the control, it should look like this,

initialization
  TActiveFormFactory.Create(
    ComServer,
    TActiveFormControl,
    TActiveFormX,
    Class_ActiveFormX,
    1,
    '', // license key goes here!!
    OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
    tmApartment);

The empty string is used to hold the licensing key.  If you had the key auto-generated by Delphi this will be a GUID.  Enter what-ever you want as the key (must be a string).

Next you must create a lic file.  The name should be the same as your control's file name.

Example:

  MyControl.ocx
  MyControl.lic

Open the file with your favorite test editor (this could be Notepad or event Delphi), the file will contain 2 lines.  

The first line will be the libraries GUID.  To get this GUID, open up the TypeLibrary editor, click on the top-most node (should be a blue icon that looks like something from Quebert), the Delphi editor refers to these as Modules.  In the panel on the right will be the GUID that you need.  Copy the hole thing (including the brackets).

The second line contains your license key.

When you are done the lic file should look like this

{C874878F-9008-11D4-B838-0050DA72DCF6}
MyLicenseKey

Note: If you send this control to another developer to use you will also have to send the lic file.  It should reside in the same directory as the control.

Note2: If you are distributing this control as part of an application do not include the lic file.  The licensing information to run the control is compiled into the executable.

2004. augusztus 14., szombat

Creating component arrays


Problem/Question/Abstract:

In Visual Basic I can create component arrays, but I can't seem to find a way to in Delphi. Does Delphi have this capability?

Answer:

Do You Really Need a Component Array?

Visual Basic has no way to share events among components other than through component arrays, which is why it's so easy to program in Visual Basic. But with Delphi, groups of components can share a common event handler merely by assigning the event handler code for the event in the Object Inspector. Unlike VB, which requires the components to be of the same type when assigning a shared event handler, dissimilar components in Delphi can share the same event handler.

For instance, let's say you want a TSpeedButton to behave exactly like a TMenuItem. If you already have code for the TMenuItem, all you have to do is select the button and go to its events page. Under its OnClick event, drop down the list of available event handlers and select the event handler for the OnClick Event of the TMenuItem.

What I discussed above is usually the reason for wanting to build a component array. However, there are certain circumstances under which you would want a component array. One of those circumstances is handling processing for a group of like objects in a loop. For example, let's say you have a bunch of TLabels on a form whose appearance and text you want to update at runtime in response to a mouse click. Rather than creating one big switch statement or several if..then statements to change the text and appearance of the TLabels, it's much easier to process the changes in a loop.

However, in spite of that, a component or object array could be quite useful; especially if you're performing the same thing on a group of the same types of components sequentially. A good example of this is how instantiate a series of threads of the same type all at once in my article Waiting for Threads. Instead of making a separate variable declaration for each thread that I need to create, I instead create an array of the threads, and instantiate them with a FOR loop. This is not only much more convenient, the coding it saves makes this approach much more efficient with respect to productivity. So there's a good argument why you might want to create an array of components or objects. However, keep in mind what I said above. If you're using an array merely to apply a group-based event handling mechanism, it's probably better to stick with my example below.

Creating an Array of TLabels

The first thing that needs to be done is to make declaration of the array of TLabels. For this example, I've dropped eight TLabel components on a form. I want the array index to match the TLabel indexes, so I declare the array as follows under the private section of my form:

arLbl: array[1..8] of TLabel;

Next, because I know I'll be needing the label array almost immediately, I'll pop some code into the form's OnCreate event to initialize the values of the label array elements:

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  for I := 1 to 8 do
    arLbl[I] := TLabel(FindComponent('Label' + IntToStr(I)));
end;

Notice that I performed a typecast around the FindComponent function. FindComponent returns the component specified by the name input into its formal parameter. However, without the typecast, FindComponent will return a type of TComponent, and a type mismatch compiler error will occur, despite the fact that it returns the TLabel we're looking for. So by forcing the typecast of TLabel, we ensure that the type assigned to the label array element is correct.

Then, because the TLabels will share the same event handler, all we need to do is set up an event handler for the OnClick event of one of the TLabels, then set up the other labels' OnClick event handlers to point to the OnClick event of the label we wrote the code under:

procedure TForm1.Label1Click(Sender: TObject);
var
  I: Integer;
begin

  for I := 1 to 8 do
  begin
    arLbl[I].Caption := TLabel(Sender).Name;
    arLbl[I].Font.Style := [];
    arLbl[I].Font.Color := clWindowText;
    arLbl[I].Color := clBtnFace;
  end;

  with TLabel(Sender) do
  begin
    Font.Style := [fsBold];
    Font.Color := clWhite;
    Color := clBlue;
  end;

end;

In the OnClick event handler above, the program goes through the entire array and sets some basic properties. These are essentially the default properties of the labels when they're dropped onto the form. Then, the procedure takes the Sender, the label that fired off the event, and changes its appearance to white, bold text with a blue background. This is much simpler than doing a big case or if..then. Here's the entire unit code:

unit lblform;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    procedure Label1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    arLbl: array[1..8] of TLabel;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Label1Click(Sender: TObject);
var
  I: Integer;
begin

  for I := 1 to 8 do
  begin
    arLbl[I].Caption := TLabel(Sender).Name;
    arLbl[I].Font.Style := [];
    arLbl[I].Font.Color := clWindowText;
    arLbl[I].Color := clBtnFace;
  end;

  with TLabel(Sender) do
  begin
    Font.Style := [fsBold];
    Font.Color := clWhite;
    Color := clBlue;
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  for I := 1 to 8 do
    arLbl[I] := TLabel(FindComponent('Label' + IntToStr(I)));
end;

end.

2004. augusztus 13., péntek

Technique for handling mouse actions


Problem/Question/Abstract:

For example a paint program requires that the mouse do different things depending on what mode you are in, or maybe you have built a 3D world editor or vector editor. Want a clean way to handle all those mouse actions without cluttering your form code?

Answer:

The answer to the problem is to create mouse handler classes which your form passes mouse actions to it - or even key strokes.
A mouse handler class would look like this. You can add key events of other events if you like but I'll keep this simple.

TMouseHandler = class
  procedure MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer); virtual; abstract;
  procedure MouseMove(Sender: TObject; Shift: TShiftState; X,
    Y: Integer); virtual; abstract;
  procedure MouseUp(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer); virtual; abstract;
end;

Then you would make descendents of this class to handle diffent "modes" for example:

TDrawLine = class(TMouseHandler) {...};
TPaint = class(TMouseHandler) {...};
TDrawSquare = class(TMouseHandler) {...};

You do not have to apply this to just a paint program, the teqnique can be applied to any app where the mouse must do different things.

The mouse events of the control will be forwarded to the current mouse handler.

For example:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Handler.MouseDown(Sender, Button, Shift, X, Y)
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  Handler.MouseMove(Sender, Shift, X, Y)
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Handler.MouseUp(Sender, Button, Shift, X, Y);
end;

I'll make a note here that you may also want to include the ability for the handler to paint. For example when drawing a line you may want to display a line as the mouse moves.

When it is time to switch modes, simply reassign the "Handler" variable with a different type instance and your main form code is kept clean.

2004. augusztus 12., csütörtök

Downloading any URL using default network configuration


Problem/Question/Abstract:

How do I access files through a proxy? How can I use cached files downloads?

Answer:

Starting with Internet Explorer 3, Microsoft provides a very useful API, Wininet. It contains the core functionality of IE. When you use those functions, you automatically benefit from features of IE, such as Proxy configuration, file Caching, etc.

Here is a demonstration of using those functions to download a file using it's URL. It can be any valid URL, ftp://, http://, gopher://, etc.

Consult the Win32 Internet API Functions reference on MSDN online for more information.

function DownloadFile(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1024] of char;
  BytesRead: cardinal;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if Assigned(NetHandle) then
  begin

    UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD,
      0);

    if Assigned(UrlHandle) then
      { UrlHandle valid? Proceed with download }
    begin
      FillChar(Buffer, SizeOf(Buffer), 0);
      repeat
        Result := Result + Buffer;
        FillChar(Buffer, SizeOf(Buffer), 0);
        InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
      until BytesRead = 0;
      InternetCloseHandle(UrlHandle);
    end
    else
    begin
      { UrlHandle is not valid. Raise an exception. }
      raise Exception.CreateFmt('Cannot open URL %s', [Url]);
    end;

    InternetCloseHandle(NetHandle);
  end
  else
    { NetHandle is not valid. Raise an exception }
    raise Exception.Create('Unable to initialize Wininet');
end;

2004. augusztus 11., szerda

How to know if your application is running in SafeMode


Problem/Question/Abstract:

Sometimes an application uses resources that doesn�t exist when windows is running in SafeMode, so how do you detect this situation?

Answer:

Use Windows API GetSystemMetrics with SM_CLEANBOOT parameter, this specifies how the system was started, in your project�s code use:

program Project1;

uses
  Forms,
  Windows,
  Dialogs,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);

  case GetSystemMetrics(SM_CLEANBOOT) of
    1:
      begin
        ShowMessage('Running in Safe Mode: Fail-Safe Boot');
        Application.Terminate;
      end;
    2:
      begin
        ShowMessage('Running in Safe Mode: Fail-safe with network boot');
        Application.Terminate;
      end;
  end;

  Application.Run;
end.

This way you can prevent the user to use your application and avoid havoc!

2004. augusztus 10., kedd

Creating data in Excel and copying it to Word from MS Office 97


Problem/Question/Abstract:

How to transfer data from Excel to Word?

Answer:

Below is demonstration how to perform this task.

Add these global variables:

XLApp: Variant;
WordApp: Variant;

Add these constants:

xlWBATWorksheet = -4167;
wdDoNotSaveChanges = 0;

For creating data in Excel we shall start it first:

//Starting Excel application:
XLApp := CreateOleObject('Excel.Application');
// Making it visible:
XLApp.Visible := True;
// Adding workbook:
XLApp.Workbooks.Add[XLWBatWorksheet];
// Specifying name of worksheet:
XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';

Now inserting data to Excel:

procedure TForm1.InsertData2Excel;
var
  Sheet: Variant;
  i: Integer;
begin
  Sheet := XLApp.Workbooks[1].Worksheets['Delphi Data'];
  for i := 1 to 10 do
    Sheet.Cells[i, 1] := i;
end;

And copying data from Excel to Word.
This process consists of two phrases:

Data should be copied from Excel into Windows clipboard.
Data should be pasted from Windows clipboard into the Word.

For successful completion both Excel and Word should be running.

Copying data from Excel into Windows clipboard:

procedure TForm1.CopyData;
var
  Sheets: Variant;
begin
  SetFocus;

  Sheets := XLApp.Sheets;
  // Selecting our worksheet:
  Sheets.Item['Delphi Data'].Activate;
  // Selecting our cells:
  Sheets.Item['Delphi Data'].Range['A1:A10'].Select;
  // Copying selected cells into clipboard:
  Sheets.Item['Delphi Data'].UsedRange.Copy;
  // Inserting copied data into Word
  InserData2Word;
end;

procedure TForm1.InsertData2Word;
var
  Range: Variant;
  i: Integer;
begin
  // Starting Word:
  WordApp := CreateOleObject('Word.Application');
  // Making it visible:
  WordApp.Visible := True;
  // Adding new document:
  WordApp.Documents.Add;
  // Inserting description text into new document:
  Range := WordApp.Documents.Item(1).Range;
  Range.Text := 'This is a column from a spreadsheet: ';
  for i := 1 to 3 do
    WordApp.Documents.Item(1).Paragraphs.Add;
  // Inserting data from clipboard
  Range :=   WordApp.Documents.Item(1).Range(WordApp.Documents.Item(1).Paragraphs.Item(3).Range.Start);
  Range.Paste;
  for i := 1 to 3 do
    WordApp.Documents.Item(1).Paragraphs.Add;
end;

Don't forget to close Excel and Word by your program termination:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if not VarIsEmpty(XLApp) then
  begin
    XLApp.DisplayAlerts := False; // Discard unsaved files....
    XLApp.Quit;
  end;

  if not VarIsEmpty(WordApp) then
  begin
    WordApp.Documents.Item(1).Close(wdDoNotSaveChanges);
    WordApp.Quit;
  end;
end;

2004. augusztus 9., hétfő

Get INTEL Chip Features using CPUID Call (asm)

Problem/Question/Abstract:

Get the INTEL chip features via the CPUID call. The function can return results both in a string list and a feature SET, or one or the other. The call only works with INTEL chips 486 or greater. Other chips will return [cpuNonIntel] or [cpuNoCPUID].

example on my machine ...

GetCpuFeatures(memo1.Lines);

FEATURES SUPPORTED

CPU Family 6
CPU Model 7
CPU Stepping 3
On-Chip FPU
VirtualMode Extensions
Debugging Extensions
Page Size Extensions
Time Stamp Counter
Model Specific Registers
Physical Address Extensions
Machine Check Extensions
CMPXCHG8B Instruction
Fast System Call
Memory Type Range Registers
Page Global Enable
Machine Check Architecture
Conditional Move Instruction
Page Attribute Table
32 Bit Page Size Extension
Intel MMX Technology
Fast Floating Point Save and Restore
Streaming SIMD Extensions

Amyone have the algorythms for AMD and other chips ?

Answer:

unit Unit2;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;

type
TForm2 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;

TCpuFeature = (cpuNoCPUID, cpuNonIntel, cpuOnChipFPU,
cpuVirtualModeExtensions, cpuDebuggingExtensions,
cpuPageSizeExtensions, cpuTimeStampCounter,
cpuModelSpecificRegisters, cpuPhysicalAddressExtensions,
cpuMachineCheckExtensions, cpuCMPXCHG8B, cpuOnChipAPIC,
cpuFastSystemCall, cpuMemoryRangeRegisters, cpuPageGlobalEnable,
cpuMachineCheckArchitecture, cpuConditionalMoveInstruction,
cpuPageAttributeTable, cpu32bitPageSzExtension,
cpuProcessorSerialNum, cpuMMXTechnology, cpuFastFloatingPoint,
cpuSIMDExtensions);

TCpuFeatures = set of TCpuFeature;

function GetCpuFeatures(FeatureList: TStrings = nil): TCpuFeatures;

var
Form2: TForm2;
implementation
{$R *.DFM}

// ===================================================
// Get INTEL chip features using CPUID call
// ===================================================

function GetCpuFeatures(FeatureList: TStrings = nil): TCpuFeatures;
const
FPU_FLAG = $0001;
VME_FLAG = $0002;
DE_FLAG = $0004;
PSE_FLAG = $0008;
TSC_FLAG = $0010;
MSR_FLAG = $0020;
PAE_FLAG = $0040;
MCE_FLAG = $0080;
CX8_FLAG = $0100;
APIC_FLAG = $0200;
SEP_FLAG = $0800;
MTRR_FLAG = $1000;
PGE_FLAG = $2000;
MCA_FLAG = $4000;
CMOV_FLAG = $8000;
PAT_FLAG = $10000;
PSE36_FLAG = $20000;
PSNUM_FLAG = $40000;
MMX_FLAG = $800000;
FXSR_FLAG = $1000000;
SIMD_FLAG = $2000000;

var
IsIntel: boolean;
VendorID: array[0..12] of char;
IntelID: array[0..12] of char;
FeaturesFlag, CpuSignature: DWord;
Temp: DWord;
RetVar: TCpuFeatures;
CpuType: byte;

// Local routine to add to List and Return SET
procedure CheckFeature(FeatureFlag: DWord;
const Item: string;
cpuFeatureType: TCpuFeature);
begin
if FeaturesFlag and FeatureFlag = FeatureFlag then
begin
if FeatureList <> nil then
FeatureList.Add(Item);
include(RetVar, cpuFeatureType);
end;
end;

begin
RetVar := [];
if FeatureList <> nil then
FeatureList.Clear;
IsIntel := false;
IntelId := 'GenuineIntel'#0;
VendorID := '------------'#0;

try
asm
// Determine Intel CPUID support.

push ebx
push esi
push edi
mov  eax,0                     // Set up for CPUID instruction
db 00fh                        // CPUID - Get Vendor and check INTEL
db 0a2h
mov dword ptr VendorId,ebx
mov dword ptr VendorId[+4],edx
mov dword ptr VendorId[+8],ecx
cmp dword ptr IntelId,ebx      // Check if it is INTEL
jne @@EndCPUID
cmp dword ptr IntelId[+4],edx
jne @@EndCPUID
cmp dword ptr IntelId[+8],ecx
jne @@EndCPUID                 // Not an Intel processor

mov byte ptr IsIntel,1         // Set IsIntel to true
cmp eax,1                      // Ensure 1 is valid input for CPUID
jl  @@EndCPUID                 // Else jump to end

mov eax,1
db 00fh                        // CPUID -  Get features,family etc.
db 0a2h
mov CpuSignature,eax
mov FeaturesFlag,edx
shr eax,8                      // Isolate family
and eax,0fh
mov byte ptr CpuType,al        // Set cputype with family

@@EndCPUID :

pop edi                        // Restore registers
pop esi
pop ebx
end;

// Check Features Mask if Intel
if IsIntel then
begin
if FeatureList <> nil then
begin
FeatureList.Add('CPU Family ' + IntToStr(CpuType));
Temp := (CpuSignature shr 4) and $0F;
FeatureList.Add('CPU Model ' + IntToStr(Temp));
Temp := CpuSignature and $0F;
FeatureList.Add('CPU Stepping ' + IntToStr(Temp));
end;

CheckFeature(FPU_FLAG, 'On-Chip FPU', cpuOnChipFPU);
CheckFeature(VME_FLAG,
'VirtualMode Extensions', cpuVirtualModeExtensions);
CheckFeature(DE_FLAG, 'Debugging Extensions', cpuDebuggingExtensions);
CheckFeature(PSE_FLAG, 'Page Size Extensions', cpuPageSizeExtensions);
CheckFeature(TSC_FLAG, 'Time Stamp Counter', cpuTimeStampCounter);
CheckFeature(MSR_FLAG,
'Model Specific Registers', cpuModelSpecificRegisters);
CheckFeature(PAE_FLAG,
'Physical Address Extensions',
cpuPhysicalAddressExtensions);
CheckFeature(MCE_FLAG,
'Machine Check Extensions', cpuMachineCheckExtensions);
CheckFeature(CX8_FLAG, 'CMPXCHG8B Instruction', cpuCMPXCHG8B);
CheckFeature(APIC_FLAG, 'On Chip APIC', cpuOnChipAPIC);
CheckFeature(SEP_FLAG, 'Fast System Call', cpuFastSystemCall);
CheckFeature(MTRR_FLAG,
'Memory Type Range Registers', cpuMemoryRangeRegisters);
CheckFeature(PGE_FLAG, 'Page Global Enable', cpuPageGlobalEnable);
CheckFeature(MCA_FLAG,
'Machine Check Architecture', cpuMachineCheckArchitecture);
CheckFeature(CMOV_FLAG,
'Conditional Move Instruction',
cpuConditionalMoveInstruction);
CheckFeature(PAT_FLAG, 'Page Attribute Table', cpuPageAttributeTable);
CheckFeature(PSE36_FLAG,
'32 Bit Page Size Extension', cpu32BitPageSzExtension);
CheckFeature(PSNUM_FLAG,
'Processor Serial Number', cpuProcessorSerialNum);
CheckFeature(MMX_FLAG, 'Intel MMX Technology', cpuMMXTechnology);
CheckFeature(FXSR_FLAG,
'Fast Floating Point Save and Restore',
cpuFastFloatingPoint);
CheckFeature(SIMD_FLAG, 'Streaming SIMD Extensions', cpuSIMDExtensions);
end
else
begin
if FeatureList <> nil then
FeatureList.Add('Non-Intel or >486 Chip - Features Unknown');
include(RetVar, cpuNonIntel);
end;
except
if FeatureList <> nil then
FeatureList.Add('No CPUID Support');
include(RetVar, cpuNoCPUID);
end;

Result := RetVar;
end;

end.

2004. augusztus 8., vasárnap

Print a Canvas

Problem/Question/Abstract:

How to print a canvas:

Answer:
This is simple way.I found in Tory delphi pages.Original belongs to Simon Grossenbacher
Homepage: http://www.swissdelphicenter.ch

this is code

uses
Printers;

procedure PrintText(Text: string);
begin
with Printer do
begin
BeginDoc;
Canvas.TextOut(5, 50, Text);
EndDoc;
end;
end;

2004. augusztus 7., szombat

Flash an icon on the Windows Taskbar

Problem/Question/Abstract:

I would like to know how use the FlashWindow API function. It never flashes the icon on the Taskbar.

Answer:

Try this - works on all versions of Windows from Windows 98 upwards. It will flash the window caption and task bar window until it comes to the foreground. You can amend the flags, count and timeout to change if you want it to work differently.

procedure TestFlash;
var
FW: TFlashWInfo;
begin
FW.cbSize := SizeOf(FW);
FW.hwnd := Application.Handle;
FW.dwFlags := FLASHW_ALL + FLASHW_TIMERNOFG;
FW.uCount := 1;
FW.dwTimeout := 0;
FlashWindowEx(FW);
end;


2004. augusztus 6., péntek

Visual C++ Versus Delphi

Problem/Question/Abstract:

Compare VC++ and Delphi

Answer:

You find this article also on http://home.xnet.com/~johnjac Last Revised: 3/12/00. If you have any comments or suggestions or corrections please send them to me at johnjac@xnet.com. Thank you.
INTRODUCTION
The purpose of this paper is to provide Visual C++ programmers with a comparison between Delphi 5 Professional Version with Visual C++ 6 Professional Version, with an eye toward what a programmer needs and wants from his or her development tools in actual use.

When one looks back at the history of Visual C++ and Delphi, one can get an appreciation for the radical differences between them. Visual C++ traces its roots back into Microsoft C/C++, back when MS was the underdog in the C++ market. Delphi traces its roots back into Borland&#8217;s Turbo Pascal, which is itself derived from the first Borland product ever, a Pascal compiler that revolutionized the world by virtue of it&#8217;s amazingly low price, and made the name Philippe Kahn a household name among programmers. Like Borland, MS originated out of a language product, Basic, but then, unlike Borland, soon moved on to operating systems, office productivity suites, and a host of other products. Borland achieved a good deal of market share in the desktop database market, and even owned the spreadsheet Quattro Pro back then, but a costly legal battle with Lotus over the spreadsheet interface, and the movement of client server databases into the desktop realm eventually eclipsed both of those mark ets. Both company&#8217;s development tools reflect these different histories. In each case, their top-selling language product is based on their very first language product. Visual Basic is descended from Basic, and Delphi is descended from Borland Pascal. Visual C++ competes against Visual Basic for attention and resources within Microsoft, and so has never been made RAD the way Visual Basic and Delphi are RAD. This is probably the biggest difference between Visual C++ and Delphi.

One interesting difference between Visual C++ and Delphi is that since Borland at one time practically owned the desktop database market, Delphi comes completely equipped to create and handle Paradox and DBase tables through direct drivers in the included Borland Database Engine, rather than relying on ODBC. Everything you need to create desktop database applications is included in Delphi 5 Pro. Microsoft owns two desktop databases too, FoxPro and Access. As far as I can tell, Visual C++ 6 Pro has no included support for FoxPro and Access beyond the classes for dealing with DAO and ADO, both of which are the next evolutionary steps after ODBC. DAO used to be the way to get to Access in Microsoft development tools, but it wouldn&#8217;t multithread. Now the preferred data model is ADO which can be threaded and has a number of enhancements that make it far better for database access than DAO. Code to use ADO looks pretty much the same in Visual C++ as it does in Visual Basic, as ADO is a h igh-level layer of abstraction that encapsulates the details of connecting, maintaining and caching connections and refreshing data. The biggest drawback of using Visual C++ with ADO, versus Delphi with the BDE, is that Visual C++ requires you to write all the interfacing code yourself, and offers you little in the way of database components that will alleviate the need to do this. Visual C++ allows you to use ActiveX controls that provide some of this functionality, but ActiveX has a number of problems of its own, such as the fact that they are binary controls and so must be included in their entirety even if only a small part of their functionality is required.

Delphi comes with a number of database components that integrate so well with the Borland Database Engine that it is possible in Delphi for even a mere beginner to create a fully functioning database application in minutes, without writing any code. Even a seasoned Visual C++ programmer will be hard-pressed to be able to achieve this kind of speed, as Visual C++ has nothing at all that is comparable to the database components in Delphi.
COMPILER AND LINKER
It should be pointed out that Delphi produces highly optimized machine code that is usually not noticeably different from VC++ code in speed of execution. What is amazing is that Delphi creates this code in a fraction of the time it takes VC++ to produce the equivalent. While the average compile in VC++ is several minutes, the average compile in Delphi is just a few seconds. I have never come across what I consider a totally adequate explanation for this difference in terms of the usefulness of the language features that cause this difference, but the common explanation is that C++ is much more difficult to parse, and can contain very deeply nested include files. Pascal is as fully Object Oriented as C++, in fact more so in my opinion, and offers roughly the same power as C++, and probably greater robustness. On the other hand, Delphi lacks a macro preprocessor, operator overloading and templates, which collectively are traditionally blamed for the slower C++ compiles. In Delphi the lack of templates i s dealt with by enforcing a single-inheritance object hierarchy. All objects in Delphi descend from Tobject, which gives them plenty of interesting properties and run-time information, and this means that you can use RTTI and polymorphism to accomplish in Delphi what templates accomplish in C++. Some people find the C++ template approach is cleaner, more concise, and easier to use, other find the single-inheritance approach better. Of all the features of C++ the macro preprocessor is the most abused, particularly by Microsoft. It is debateable whether all these features are worth the much greater extra compile time in C++.

In response to these slow compile times, C++ vendors have introduced incremental linkers and precompiled headers, which do speed up the average VC++ compile. Even with these enhancements turned on, a second (partial) compile is still longer than the equivalent Delphi full compile. Furthermore, these enhancements are not flawless or in my opinion even trustworthy. It is a common occurance in VC++ for the use of precompiled headers and/or incremental linking to result in several problems 1) a mismatch between breakpoints and their code, 2) incomplete integration of new code 3) incorrect logic and 4) inability to compile because it is misreading the header files (usually this manifests itself as an "Unexpected End of File").

One feature of both compilers and linkers is the ability to recognize and remove dead code. Like VC++, Delphi will only compile/link in the code that is actually called, but unlike VC++, it will give you a visual cue in the editor to show you what lines of code were live and which lines were dead, so that you could tidy up your code by taking out dead functions, etc. Delphi does this by displaying a small blue dot in the left hand gutter for lines that are live, after a compile and link. There is no way to know which code is live or dead in VC++. Having said this, it is important to realize that Delphi has a minimum code size of around 200K, because certain sections of the Visual Component and Run Time Libraries are called in every single application. (You can get smaller apps by not using the VCL and relying on Win API calls instead.) In VC++ these types of libraries are DLL&#8217;s that are assumed to be on the client&#8217;s machine, so a Visual C++ app using MFC can be incredibly small. (This is one of the advantages of being the one that supplies the operating system. You can "cheat" by making something needed by your development tools be a part of the operating system. When Borland asked MS if MS could include, along with the operating system, DLL's for use by apps made by the Borland tools, MS said no. Only MS tools get this advantage.) This introduces the very slight risk that a future installation over those DLL&#8217;s would render the VC++ app suddenly unuseable. This occurred with the second service patch for Visual Studio 6, for example (it was fixed with the next patch). On the other hand, the default database model in Delphi relies on DLL&#8217;s that make up the "Borland Database Engine", which together will add 3-5MB to a project. In addition, this also exposes the program to the possibility of the DLL&#8217;s being overwritten at some point with something incompatible, though Borland tries to prevent this by requiring only approved installers be used for the BDE.

Delphi does offer the ability to create and use DLL&#8217;s, both as the generic Windows variety of DLL callable from any language and as an easier to use Delphi-specific version Borland calls "packages".

An interesting aside is that Delphi can create and use C++ OBJ files created and used by C++ Builder. Though it is a difficult and time-consuming to use classes across that boundary, procedural code is not difficult to use that way. The only real difficulties arise in dealing with references to the C++ run-time libraries, which of course do not exist in Delphi and so would result in unresolved external references.

Finally, the error messages reported by VC++ are much more cryptic than the straight forward messages reported by Delphi's compiler and linker. This is especially true if one is doing template work, such as using the Standard Template Library. Such error messages can literally wrap across the full screen width three or four times.
COMPONENTS
Components are a very big difference between Visual C++ and Delphi. Visual C++ components are ActiveX controls. Delphi can use ActiveX controls, even more easily than Visual Basic, but can also use VCL components which link directly into the executable as mentioned above. Most Delphi users will only use an ActiveX control if there is no Delphi control that will do the job (which is unlikely). Delphi can be used to create Delphi components (which will also work in C++ Builder) as well as ActiveX controls that will work in just about any development environment. Delphi has a special wizard for creating ActiveX control out of VCL components, for code reuse. Visual C++ has libraries and wizards for creating ActiveX controls but it comes with no preinstalled controls.

It is in the area of components that Delphi really is leagues ahead of Visual C++. This superiority is twofold.

-Third-p

arty Delphi components are plentiful and most are free. There are plenty of high-quality components for all but the most esoteric of needed functionality. They are relatively easy to install. -Delphi comes with a large set of pre-installed components that greatly speed up development. There are several categories of these components:

Win API: windows GUI components like the Listview, etc as well as standard windows dialogs. There are many more components than there are generic Windows controls. Data Access: Data retrieval components for handling SQL, database management and batch transfers, as well as components that form the base for the Data Controls. Also includes data modules, which have a design screen allowing diagramming of data relationships.

Data Display: components for displaying database data, such as images, text, etc. in a variety of different appearances such as grids, individual windows components, etc. Interbase Express: statically linked components for access to the Interbase relational database, without the need for any DLL's.

Internet Socket and HTTP Controls: controls for socket communications and dynamically created HTML. Fastnet: Internet controls from Netmasters, that provide out-of-the-box functionality for HTTP, NNTP, SMTP, POP3, etc. In prior versions these had been ActiveX controls but are now statically-linkable VCL components, and are threadable, by the way.

QuickReport: controls for creating printed and previewable reports. COM Servers: controls for controlling Office applications Word, Excel, Project and Outlook as well as Internet Explorer via COM. TeeChart Charting and Graphing: controls for various charts and graphs.

These components are for the most part stable and tested, as well as being fully documented in the help system, except for the COM Servers (as of this writing). In addition to individual components, Delphi also allows the creation and use of component templates which are a bunch of components together with the code that ties them together. You can select several components and turn them into a single component that you can drop on any form like a regular component, with all the intraconnecting code intact. In addition, one can use Frames to create the equivalent of a container form that can be contained in other components and screens. Components can be easily tied to actions by using ActionLists, which handle the enabling and disabling of connected components and their captions and associated event handlers. This greatly speeds up the creation and control of complicated GUIs and enables a level of abstraction between GUI and code. In addition, Delphi comes with a convenient type library editor for the type libraries associated with COM objects.

The Visual Component Library in Delphi 5 is a more recent creation than MFC, and reflects a philosophy that prefers greater abstraction away from the Win32 API. While MFC lacks support for such basic things as setting component colors, these and a myriad of other properties can be set in VCL components with a single assignment. Programmers don&#8217;t have to look up the appropriate Win32 API calls needed to set a Window component&#8217;s specific property like color, they simply write one little assignment or set one single property in the Delphi property editor. This results in code that is much more readable and maintainable, as well as being easier to write rapidly.

The VCL also contains classes for non-visual aspects of Windows programming, such as wrapper classes for handling INI files, the registry, the clipboard, media files, timers, hotkeys, file wildcard masks, AVI files, services, streaming, drag and drop, COM, printing, string lists, etc. Many of these classes have no counterpart in MFC, or are much more lightly embodied than in Delphi. Take the TstringList class in Delphi as an example. In VC++ one would use a CStringList. CstringList has members for adding, retreiving and removing members of a list, but Delphi&#8217;s TstringList adds sorting, easy import and export of comma-delimited data, name=value parsing, and file and stream input and output. The Tclipboard, Tregistry, TregIniFile and Tinifile classes are other examples of VCL classes that have no equivalent in MFC, but encapsulate a lot of grunt-work Win32 API coding that otherwise has to be done by hand. Overall, the general rule seems to be that over 95% of the Win32 API programmin g a person needs to do is done for you in a VCL control or class.

One field in which the VCL is inferior is in its support and accommodation of multithreading. While the VCL supplies a class that is trivially easy to use for running worker threads, things get complicated when threads have to interact with the VCL, because most of the VCL is not thread safe. You have to resort to using a supplied Tthread class method called "Synchronize" that waits for the main thread to enter its message processing loop, and then updates the VCL within the main thread, or you have to use an alternate method like messages to communicate between the non-primary threads and the main thread. You can&#8217;t directly access any of the visual VCL components from within any thread other than the application&#8217;s main thread. MFC can handle multithreading without such ruses, in fact you can place message pumps within a thread and have a window act like its own thread.

The component model in Delphi has a very nice advantage over the Microsoft component model, ActiveX, and that is that Delphi components can be statically linked right into the executable. Not only is this more convenient (and much smaller) when it comes to deployment, but it also means the components share in the benefits of modern linker intelligence. If you use one small bit of functionality from a large extensive component, you only suffer the overhead of the functionality you actually use, and that functionality will be optimized with your code to boot. To get the equivalent in Visual C++ you would need to have the code itself, which works OK for freely distributed code but not so well for commercial components. In addition, these kind of C++ code libraries tend to be much more expensive than their equivalent Delphi components.

There are plenty of third-party Delphi components that don't come with Delphi, and most of these come with source code. While the components that come with Delphi or the supplementary CDROM are stable and tested, components from other sources may vary quite a bit in quality. Creating advanced Delphi components is an interesting intellectual challenge that may exceed the attention spans for some individuals, resulting in components that make Delphi unstable. Delphi doesn't run component code inside any kind of protective sandbox, and so an errant component can pull Delphi down into oblivion. The most common manifestation of an errant component is that Delphi will not close down gracefully, maybe even giving an endless cycle of access violations that requires a hard reboot. While this is rare, if you try enough components from unknown sources and/or beginners, you will eventually run into the problem. This will be the first question an experienced Delphi programmer asks you if you complain that Delphi is unstable: "What components did you install?" Ninety nine times out of a hundred it is some component that is causing the problem. All such third-party components should be thoroughly checked out before being used in any important Delphi program.
IDE
It is in the IDE that VC++ 6 comes the closest to Delphi 5, as both have heavily incorporated the intellisense features that first debutted in Visual Basic. In Visual C++, the method completion feature that pops up when you type a class or object name and ".", "::" or "->" has a nice additional feature that shows fly-by hints for each item in the drop down listbox, a feature Delphi lacks in its version of intellisense. Visual C++ also has automatic formatting as you type (though it is not as advanced as Visual Basic&#8217;s, which will even correct or respond to your changes in the capitalization of variable names). Delphi has no automatic formatting. In addition, Visual C++ seems less memory and resource intensive than Delphi 5, which has sometimes frozen on my Windows 98 machine with 96MB of RAM when I forgot to close other programs that were running at the time. Delphi 5 takes a seeming infinity to start, and after a few debugging sessions seems to take an infinity to close, often j ust dying out with access violations or illegal operations if Program Reset was used to cancel out of one or more debugging sessions. Delphi doesn&#8217;t use a virtual machine to isolate debugging sessions nor component code and the result in Windows 98 seems a tad precarious, at least on my machine. It seems that the two competing forces of high resource use and components and debugging sessions not being isolated are a bit too much for the jerry-rigged operating system known as Windows 98. Often an errant Delphi program can pull Delphi down with it. I have sometimes seen this happen with Visual C++.

Those caveats aside, The Delphi 5 IDE offers several very nice advantages over the Visual C++ 6 IDE. Once you step away from the VB-inspired intellisense, the Visual C++ IDE starts to show its age.

In contrast to Borland&#8217;s clean "two-way tools" the proprietary comments cluttering VC++ code from the Wizards are kludgy and incredibly dated. Where Delphi offers a nice clean Object Inspector to set properties and event handlers, Visual C++ is still relying on the ClassWizard from five years ago, back when cluttering code with all sorts of "do not touch!" markers was how all code generators worked. While it may have been innovative back then, it is just an anachronistic annoyance now. Code generated by Delphi has no markers at all and there is never a disconnect between the tools and the code no matter how many changes one makes. The mechanism in Delphi simply works better and the code is much, much cleaner and therefore easier to change and maintain. In Visual C++ the ClassWizard can become downright useless if any of the proprietary code markers are accidentally altered.

As an editor, the Delphi IDE offers several advantages as well. Unlike the class view in Visual C++, the code explorer in Delphi operates on any file that is opened, whether or not it has been added to the project yet. In addition, the code explorer is accessible during debugging. That is very convenient when porting code between projects or looking at code samples. In addition, it shows other useful information, such as what other files the current file is directly dependent on. A separate browser is also available in the IDE, which does require a compile, and is not visible during debugging, if one wishes to see references and the inheritance tree. This browser is different from the browser in Visual C++, with the predominant difference being that it doesn't disappear once you click on a reference. Another very useful Delphi IDE feature is class completion, which creates declarations for undeclared definitions, creates skeleton definitions for undefined declarations and automatically creates the access methods and procedures for properties. This is really convenient when writing a bunch of method definitions. A single key-stroke will toggle between a declaration and definition. Delphi also offers code browsing, where you can go forward and backward through code references the same way you can go back and forth through hyperlinks on the internet. You pause the mouse over an identifier and press the Ctrl key. The identifier becomes a hyperlink to its definition. Click and you are there, with a highlighted "back" button in the IDE to take you back. This is like a multilevel reference browser, where you can trace deep into a tree of function calls and then quickly retrace your steps back home. Visual C++ has nothing like this, though you could achieve the same result in Visual C++, with much more work, by using bookmarks and the browser (which you could also use in Delphi).

Together the collective result of all these Delphi IDE innovations is a much greater, much more holistic understanding of the code than you will usually get using the Visual C++ IDE.

In addition though there are other nice touches to the Delphi 5 IDE that go beyond this. A code template feature lets you store code snippets that can be retrieved with simple keystrokes, like live macros that expand during editing. A ToDo list is integrated into the IDE which makes it very convenient to use, offering a type of project/feature tracking. You can associate to-do items with specific locations in the code or with the project in general. Getting help on any specific item in code is also more convenient, as it takes only a right click and menu selection. If you select an identifier this help look-up is intelligent enough to know to look up the identifier&#8217;s type rather than engage in a futile search for the identifier string itself. In Visual C++ you have to copy the name of the item you want to look up, and then open up the help system (a localized MSDN) and paste the string into a combo-box. Unfortunately, you often must engage in an additional step of limiting the scope of the search or you will end up with all sorts of J++ and VB references that do no good. This marks the major difference between Borland and Microsoft help systems. The Microsoft help resources are massive, but this makes finding relevant information unduly time-consuming, whereas the Borland help systems are more specialized to the task on hand. I prefer the Borland approach, as I can always use a mere two mouse clicks (or one keystroke) to get the information I need. It is my personal opinion that MS should include more code samples for specific help items, instead of large project-scale samples. Another nice feature of the Delphi IDE is that the project manager shows the full absolute path for project files. In Visual C++ if you need to move files around you may be dismayed to discover that while the project files and IDE are using absolute path names, they do not update these nor show you these absolute path names. As a result, forking code can be catastrophic, as the new proje ct still relies on the files from the old project location even though the IDE gives no visual clue to this fact. In Delphi, you can freely move projects around without having to do any manual fix-ups, as Delphi uses relative paths and in any event shows you the full absolute paths it is using. In Visual C++ it usually just isn&#8217;t so easy. (BTW, the project window in Delphi now supports multiple executables and projects, even allowing convenient drag and drop of files) The Delphi IDE has another unique feature in that you can save multiple "desktops", which are layouts, whereas VC++ 6 will only save and use one layout. You can switch between desktops simply by selecting the new desktop in a combo-box on the Delphi toolbar. The IDE will automatically switch to the user-configured "Debug" desktop when debugging. The Delphi IDE also makes it much easier to work with the Version Information resource for your executable, as well as optionally auto-incrementing the build number in the version number. Boo kmarks in Delphi are a little easier to use than in Visual C++, as they are available in the right-click menu, and they do not accumulate across different sessions. In Visual C++ my bookmarks list is now several dozen long and deleting them only temporarily deletes them, with the deleted bookmarks returning in the next session.
DEBUGGING
When it comes to debugging there are some major differences between Delphi 5 and Visual C++ 6, which can be summed up by saying that Delphi's hunger for windows resources can cause problems for debugging applications under Windows 95/98. Some of the additional features in Delphi 5 make certain debugging tasks much, much easier. Delphi has all the debugging features found in Visual C++, but adds several more. In Delphi, you can set breakpoints on the loading of DLL&#8217;s so that you can see the exact line of code that precipitates the module load. Then you can look through the module view and see what the DLL exports, the absolute path of the DLL&#8217;s that are being used, as well as the code files used by your executable. You could even go right to the assembly language in the DLL&#8217;s that corresponds to the exported symbol of your choice. In addition to the "Module Load" breakpoints, there are also "Data Breakpoints" that will trigger when a specific variable is changed. All breakpoints in Delphi can be conditional, can execute an expression, log to the event log, and can enable/disable groups of breakpoints or all remaining breakpoints. In fact you could choose to have a breakpoint simply log an expression and go on without stopping. Delphi 5 has full provisions for remote and DLL debugging. In addition, the inspectors in Delphi allow you to change any variables you wish at any point, something that Visual C++ very frequently balks at, without explanation.

As mentioned previously, in Delphi you can access the code explorer that gives you a convenient point and click tree structure for browsing code, which is equivalent to the Class Explorer pane in Visual C++ 6.0, whether you are editing or debugging. In Visual C++ the equivalent pane is not visible during debugging sessions, though you will often have the greatest need for it at that point.

Delphi also has data breakpoints and a very useful thread view, both of which can be very handy when trying to track down elusive bugs. (The Visual C++ thread view is a modal dialog box, which reduces its usefulness) I have found the integrated to-do list functionality very useful during debugging, as I can make a note of the bug right then and there in the IDE.

No discussion of debugging would be complete without at least a passing comment on Visual C++&#8217;s "Edit and Continue" feature, which originally debutted in Visual Basic. The idea looks good on paper and in marketing blurbs, but in practice the feature is often virtually useless. The main problem with this feature is that it requires the incremental linker, which itself is not reliable in my humble opinion. I have seen too many instances of the incremental linker (and partial builds as well) simply not fully incorporating all changes to code. Even if you get past the need for accurate compiles you are faced with the fact that there is no way the compiler could recreate the full results of any code change if it occurs in the middle of a multiply iterated loop, for example. The third problem I had with the "Edit and Continue" feature was that I could never get it to work very well. The most common problem would be an inability to invoke the incremental linker for some reason. This marke ting feature is simply not good enough nor robust enough to offset the loss from the frequent inability to alter variables during debugging.

Another advantage of Delphi over Visual C++ is the difference in compile and link speeds, with Delphi&#8217;s blinding speed being a huge advantage here. The fastest C++ partial compiles/links are still several times longer than Delphi full compiles/links and this can make a big difference when one is trying to fix and test bugs quickly. The rapid turnaround possible with Delphi makes rapid-fire debugging possible, while debugging with Visual C++ is hobbled by the slow C++ compiles.
LANGUAGE
Both Object Pascal and C++ are full Object Oriented languages. Both languages also have roots in procedural languages that preceded the integration of Object Oriented principles. This means that both languages allow you to incorporate Object Oriented principles as little or as much as you want, subject to certain caveats I will cover very shortly. There is a common misperception among many Visual C++ programmers that Delphi is just like Visual Basic, but using Pascal instead of Basic. This is not true. In the spectrum of languages, in many ways Object Pascal is to Pascal what C++ is to C. Both languages are rich enough that an intelligent, inquisitive person will find themselves unlimited by the language. Object Pascal is in the same league as Java and C++. Some say it is easier to learn than C++, but this is hard for me to gauge, as I already had several years of experience in C++ by the time Delphi came out. One thing that can be gleaned from the newsgroups and discussions with novices is that it is indeed harder to learn Object Pascal than Visual Basic.

A person who knows only one of these two languages does themselves a great disservice by not learning the other. Object Pascal and C++ are full and rich languages that each have their interesting and challenging idiosyncrasies and hidden treasures. Each language has a different feature set, and so require of the good programmer that he approach the same problems in different ways. I personally find that knowing and using both is a good way to keep oneself interested and open in programming as an intellectual exercise, an art, and a career. If, like myself, you feel that "real programmers" know at least two different computer languages well, these two are extremely good candidates for one's toolbox. There is no significant loss in power in going from either one to the other.

Avoid falling for the common misperceptions that Object Pascal is a "toy language like VB" or that C++ will be forever luring the gullible programmer into wild pointers and memory allocation errors that are impossible to fix. I have been using Object Pascal since Delphi came out and I have not run into any of the types of limits that programmers associate with beginner's tools like VB. Likewise I have been using VC++ in my career and I haven't had a single wild pointer or memory allocation error in the past year or so, despite coding a complete commercial app and it's update in that time. Both languages are more than adequate for most of the programs you will need to write, and are complex enough to keep inquisitive minds entertained.

There are some important caveats to keep in mind however. Both Delphi and Visual C++ come with frameworks for creating Windows programs and both tools bend their language around their framework or for its benefit. Visual C++ does not by default create nor read ANSI C++ if you use MFC. You can tweak ANSI C++ code to get it to compile in both ANSI C++ compilers and the Visual C++ compiler with MFC, but you can not take MFC-based Visual C++ code and use it in anything other than the Visual C++ compiler. (There is one exception to this, in that C++ Builder 5 has an MFC compatibility switch for it's compiler.) Likewise, Object Pascal created in Delphi is also mostly proprietary. It can compile most generic and old Pascal code, but it doesn't create it. In both cases, there are third party paraphenalia that attempt to remedy these limitations, but there is inevitably the additional possibility for mischief in these schemes.

Object Pascal is a language created and used exclusively by and for Delphi and as such it represents are far greater departure from traditional Pascal than is represented by the departure of Visual C++ from ANSI C++. It also fits Delphi's VCL far more tightly than the C++ created by Visual C++ fits MFC. Because there is not even any pretense to an ANSI-like standard for Object Pascal, Borland has freely adapted Object Pascal to Delphi's needs. The result is a language that gracefully accomodates the Property Method Event model embodied in Delphi and makes it easy to create robust readable Windows code. On the other hand, Microsoft had to minimize the changes to C++ that would accomodate MFC, and the result of this, and the fact that MFC is much older than the VCL, is that a more procedural methodology results in the use of macroes, untouchable cryptic markers in comments and things like the "message map", which is an untouchable but visible block of code that matches messages with functions. The Delphi approach is far cleaner and easier to create, read and maintain, without sacrificing the power to get under the hood and fiddle with the works if one so fancies. This reflects an apparent design philosophy in Delphi that the language should be subordinated to the goals of the tool, rather than the tool being subordinated to the goals of the language.

Aside from this, there are important differences that may be benefits or costs, depending on your point of view. Object Pascal does not offer multiple inheritance, templates, operator overloading, inline functions, preprocessor/macroes, globally accessible static class variables, nor nested classes. C++ does not offer virtual constructors, embedded procedures, sets, built-in messages, built-in interfaces, built-in OLE automation, built-in strings, or the "finally" construct. None of these are real impediments in either language, as alternatives exist for all of them that are more than adequate in creating the same results. For example, in Delphi the default calling convention is the register convention which passes parameters via registers instead of the stack (thus usually avoiding the creation of a stack frame altogether), avoiding the same overhead avoided by inline functions.

C++ and Object Pascal have a great number of syntactical differences, but this should not lead one to conclude that they have great number of conceptual differences. A C++ programmer who already knows Object Oriented principles will find it rather easy to learn Object Pascal. Because the same concepts apply to both languages, the only real differences are in the implementation of those concepts. This conceptual overlap not only provides the seasoned C++ programmer an easy entrance into Delphi programming, but it also means that knowing and mastering both languages will thereby strengthen his knowledge and appreciation of those underlying Object Oriented principles.
COM
Delphi has a fair number of components, language features and classes for writing COM/OLE/ActiveX programs, components and servers, with particular strength on the creation and use of objects rather than servers, though Delphi 5 did add some significant improvements to the support for COM servers. Using COM objects in Delphi can be as trivially simple as in Visual Basic, or as complicated as you want. The Delphi approach to COM is thoroughly component oriented, in fact a Delphi wizard will create components on the pallette that wrap around ActiveX controls and servers, so that you can drop them on your forms and set their properties in the design-time Object Inspector as if they were normal Delphi components. This is an entirely different approach than one finds in Visual C++'s ATL, because it is much more RAD in it's philosophy than the ATL, and seasoned ATL users may unfortunately see the Delphi approach as indistinguishable from the Visual Basic approach. It would be incorrect though to assume that the Delphi system is a limited black box like the Visual Basic system. All the code for the COM related classes is available, one can use inheritance to alter most of the behaviour, and the Delphi COM classes are layered enough that you can choose from several different levels of abstraction. The highest levels of abstraction so encapsulate the details of COM that you can use COM without knowing COM, for better or for worse. Before the ATL, COM in Visual C++ was a lot of work that often were exercises in frustration for many, particularly those who don't have much knowledge and/or experience in COM. The ATL has greatly simplified and lessened this burden, and in the hands of experienced programmers seems to be more than up to the task of creating COM servers and controllers, but it is impossible to improve upon the simplicity offered by Delphi, which sacrifices nothing in therms of power.

It is important to realize that these two different libraries, the VCL and the ATL, seem to have been designed with different goals and philosophies in mind. The VCL seems to have been designed with more emphasis placed on the creation and use of COM/ActiveX controls and controllers, with the ATL being better suited to the creation of COM servers. However, in this most recent edition of Delphi, Borland has tried to position Delphi as a better creator of COM servers than before. Differences in architecture and philosophy remain though, and are significant enough to present a delightfully challenging intellectual exercise for anyone crossing over from one to the other. Interfaces are a built-in feature of Object Pascal, which leads to some very clean, easy to read code. COM Interfaces in Visual C++ are not built-in to the language but so are not as cleanly coded, but work quite well too. However, Delphi interfaces support multiple inheritance, though Delphi classes do not. This disparity means that one can not descend from both a class that provides the implementation for the interface methods and another class that provides the implementation for the methods in one of the other interfaces from which it inherits. Instead, one must use a VB-like approach where one integrates an object of this other class into the target class as a private member and assign it as the implementation for the interface using an "implements" keyword. This may put off seasoned C++ programmers who will be scratching their heads wondering why true multiple inheritance couldn't just be added to Object Pascal to more easily accomodate interface implementations instead of requiring them to use this VB-style workaround. More than anything else, this curious situation seems to be a good reason why, though Delphi is better for COM controllers, Visual C++ seems better for COM servers, at least those inheriting a significant amount of their functionality from a pre-existing code base.

COM code in Object Pascal is far cleaner and easier to read than in C++, due to the way interfaces are built right into the Object Pascal language.
PERFORMANCE
If you are like most Visual C++ programmers you would say that code created with Visual C++ will most likely be faster because "everyone knows that Visual C++ is what you use when you need speed". You would be wrong to make such a blanket assumption about Delphi's ability to produce optimal code. See Jake's Code Efficiency Challenge for real world examples of attempts at optimal code in both languages. The compiler and linker in Delphi is on the same par as the Visual C++ compiler and linker, producing fast, rather well-optimized code. Granted, one has more optimization choices in Visual C++, being able to optimize for size or speed, or to toggle individual optimizations, and there are certain speed oriented C++ code features like inline functions that have no equivalent in Delphi. However, given all this it remains the fact that there is no significant difference in the speed of the code created in Delphi versus Visual C++.

What is a fact is that there are idiosyncratic weaknesses in the code libraries that can impede code efficiency. In the case of Delphi, the compiler and linker are top notch, but some parts of the VCL are not so strong when it comes to manipulating strings, such as the StringReplace function which results in timings that are two to three times their Visual C++ CString::Replace equivalent. Likewise a stray container choice can decelerate string manipulations in Visual C++, such as using CStringArray instead of CStringList. Careful coding (and time-testing the code) can have a big payoff in both tools.
DATABASE SUPPORT
It is in database support that Visual C++ really lags behind. While Delphi offers drag and drop simplicity in creating database-centric user interfaces, Visual C++ requires the user to code almost everything themselves, from beginning to end. The difference here results in several orders of magnitude of difference in productivity. While Visual C++ programmers are busy looking up how to create a connection string for ADO, Delphi programmers will already be unit testing their creations.

Database support in Delphi 5 Pro, comes in several categories, the Borland Database Engine, the ADOExpress Components for native ADO, and the InterbaseExpress components for Borland's (soon to be Open Source) Interbase. All three support the drag and drop database-aware controls that come on the Delphi pallette, which include edit boxes, lookup comboboxes, grids and so on. The Borland Database Engine supports SQL for local desktop databases like Paradox, DBase, Access, ASCII, Local Interbase and anything for which you have an ODBC driver. The Borland Database Engine offers a plethora of features such as bidirectional scrolling, cached updates, connection control, transaction control, etc. even for databases that do not offer these features themselves. Support and unlimited licensing for Paradox and DBase tables is included straight out of the box for you to use in your applications without further fuss or expense. The only problems with the BDE is that it is based on a set of DLL's thart you must distribute with your app and install using a tool like InstallShield, and this set of DLL's has a rather large footprint of several megabytes. The new ADO components in Delphi are wrappers around ADO, which like ODBC, can access anything for which there is a driver. While ADO is a thinner database interface than the BDE, and not as easy to use, it offers the advantages of being familiar to Visual C++ programmers, coming with the new Microsoft operating systems, and offering a popular standard interface for Windows-based database access. In addition, the ADO components in Delphi are statically linked right into the executable, requiring no additional DLL's to be distributed to PC's that already have ADO. The ADO components in Delphi connect to the database aware controls on the Delphi component pallette just as easily as the BDE components, for drag and drop ease in setting up user interfaces for ADO-based applications. Finally, Delphi also comes with native Interbase controls for using Interbase as the SQL database to which database aware user interface controls can be connected. This has the potential upside that, because Borland has decided to make Interbase Open Source, these components will allow a programmer to quickly set up user interfaces for a free heavy-duty database they can freely distribute with their applications. Delphi 5 Pro also comes with Borland's Web Broker technology which makes it easy to use these database access methods and components to create database-centric web pages and HTML interfaces for access from any browser.

Visual C++ comes with code libraries that ease, but don't eliminate, the burden of writing your own ADO or OLE DB code. ActiveX controls that interface with these libraries allow programmers to put a visual user interface into their VC++ programs, but don't offer anywhere near the speed and ease that Delphi offers for database programming, and must be distributed along wth the app. Visual C++ has nothing for Paradox or DBase tables, and no desktop databases come in the box for you to use and distribute if you buy Visual C++ pro by itself, though it will interface with Access if you obtain it separately. While Access is rather lightweight compared to Paradox tables, it might be adequate for many needs. Visual C++ will interface well with MS SQL Server and any database for which a C/C++ API exists, but still requires non-trivial coding to connect database data with the user interface. Microsoft has not announced any plans to make their SQL Server database open source.
CONCLUSIONS AND RECOMMENDATIONS
The choice of a development tool is dependent on several different factors, some of which have nothing to do with the technical merits of the tool itself. No program is ever written in a vacuum, without being affected by outside forces. Programmers will often argue for their favorite tool as if it will be used in it's ideal setting, with perfectly experienced developers wielding it with grace and might. The reality is often starkly different from the utopia taught by such zealots. The factors to be considered in choosing between Delphi 5 and Visual C++ 6 include:

Existing Skill Set: The learning curves for both C++ and Object Pascal imply a start up cost that must be weighed in when programmers will be using them as novices. According to various sources there is as large as a 10 to 1 differential in the productivity of programmers. By the apparent consensus among many Delphi programmers, Delphi allows 2 to 4 times more productivity than Visual C++ overall, and the opinions among those Visual C++ programmers who have tried Delphi appear to be that the differential is much less than that. The moral of this story is that if you can find experienced, good programmers, you should probably consider using whatever tool for which they are experienced and skilled. Their skill differential may outweigh the tool differential, if they are highly skilled in one tool and know nothing about the other. Unfortunately, the reality of the marketplace is that the best programmers are very difficult to find and 9 chances out of 10 you'll end up with a rookie who has lied on his resume. Since Delphi is gentler on novices than Visual C++, this would seem to be a point in Delphi's favor, unless you already have a pool of experienced skillful programmers. Experienced, skilled Delphi programmers will demonstrate greater productivity than experienced, skilled C++ programmers, if you can find them. Existing Legacy Code: Rewriting code from scratch is an invariably expensive process, in the short run. Despite all the implied benefits of OOP for code reusability, reusing already existing code will cut more time off a project than anything else. Copy and paste in VC++, and drag and drop in Delphi, are still the predominant form of code reuse, and the more of a project's code can consist of already-written code the more of a headstart you'll have. Programmers are notorious for wanting to rewrite everything in their own image and seem to almost universally suffer from the "not invented here" syndrome. This personality trait is very expensive. The person who walks into a place that has an already functional product that meets their needs and says he will completely rewrite the product from scratch using the latest OO techniques is a would-be charlatan who will drain your pocketbook in search of the Holy Grail of programming purity. This point also applies to those who refuse to use components and component based systems. Components represent the ultimate standardization of code reuse, and Delphi does components much more effectively than Visual C++. The Nature of the Required Programs: Both tools have their particular strengths when it comes to specific types of programs. Delphi is vastly superior to Visual C++ for the creation of database front-ends, as well as the rapid creation of rich, effective user interfaces. In addition, in those areas in which pre-existing Delphi components exist that are robust and powerful, Delphi again has the strong advantage. This includes internet and intranet applications, charting and reporting, database access, and advanced user interfaces. Where Visual C++ has an advantage is the creation of device drivers, COM servers, scientific, mathematical, and statistical applications, console mode programs, WinCE applications and small utilities. These disparate advantages fly in the face of the trend among development houses to standardize on a single development tool for all development. Rather, maximum productivity would be attained by mastering both tools and using whichever one was best suited for a given application. There is absolutely no reason why any good programmer could not be proficient and productive in both languages. Future Portability and Extensibility: As of this writing, the only area in which there is anything approaching certainty is that Borland has announced that their C++ Builder and Delphi products will come out in a Linux version in the middle of the year 2000 and that there will be an attempt to minimize the effort needed to port applications from the Windows versions of these products. While Borland has been uncharacteristically vocal about their plans for the future, Microsoft has been uncharacteristically mum about the future. What is known is that whereas it used to be possible to write Visual C++ apps for the Intel and Alpha chips, NT for the Alpha chips is now being dropped. This leaves WinCE for multiple platform Visual C++ programming. Unfortunately for this cause, WinCE is doing poorly in its market. In addition, it seems rather adventurous and probably unwarranted to believe that Microsoft will port Visual C++ to non-MS operating systems any time soon, given their track record so far, leaving adventurous cross-platformers to the ravages of third-party hacks and intermediaries. Borland, on the other hand, seems positively giddy over Linux lately. Also, on the database front, Borland has recently announced that their database, Interbase, will be open source (read "free"), and that they will be improving the integration of Interbase with Borland tools.