Delphi TaskCompleted Event (using the Chilkat ActiveX)

Demonstrates the TaskCompleted event callback using the Chilkat Delphi ActiveX:

procedure TForm1.httpProgressInfo(ASender: TObject;  const name: WideString; const value: WideString);

begin
    // This event callback occurs in the background thread (because the asynchronous
    // version of the QuickGetStr method was called (i.e. QuickGetStrAsync)
    // UI elements must be updated from the main UI thread...
    TThread.Synchronize(nil, procedure begin
      Memo1.Lines.Add(name + ': ' + value);
      end);

end;

procedure TForm1.httpTaskCompleted(ASender: TObject;  const task: IChilkatTask);
begin
    // This event callback occurs in the background thread (because the asynchronous
    // version of the QuickGetStr method was called (i.e. QuickGetStrAsync)
    // UI elements must be updated from the main UI thread...
    TThread.Synchronize(nil, procedure begin
      Memo1.Lines.Add(task.ResultErrorText);
      end);

end;

procedure TForm1.Button2Click(Sender: TObject);
var
  http: TChilkatHttp;
  success: Integer;
  html: WideString;
  task: IChilkatTask;

begin
  http := TChilkatHttp.Create(Self);

  success := http.UnlockComponent('anything for 30-day trial');
  if (success <> 1) then
  begin
    Memo1.Lines.Add(http.LastErrorText);
    Exit;
  end;

  http.OnTaskCompleted := httpTaskCompleted;
  http.OnProgressInfo := httpProgressInfo;

  // Create a task to do the HTTP GET asynchronously.
  task := http.QuickGetStrAsync('http://www.chilkatsoft.com/');

  // Start the task in a background thread.
  task.Run();

  // Warning: If an event callback method calls TThread.Synchronize, then a call to task.Wait will hang
  // for the full duration of the timeout specified.  (If the wait-forever value of 0 is passed
  // to task.Wait, then it will hang forever.) This is because the call to TThread.Synchronize is waiting
  // for the main thread's event loop.  Control is not returned to the main event loop until this
  // TForm1.Button2Click returns -- thus there is a deadlock. There is no deadlock when a program
  // Waits on a task and there are no event callbacks that call TTHread.Synchronize.
  //task.Wait(5000);

end;

Chilkat 64-bit DLL for Delphi XE4 Now Available.

The 64-bit build of the Chilkat DLL for Delphi XE4 has been added to the v9.4.1 SP1 distribution. See http://www.chilkatsoft.com/delphiDll.asp

The 32-bit DLL is named ChilkatDelphiXE.dll and is valid for Delphi XE2, XE3, and XE4  (and will likely be valid for future Delphi releases).

The 64-bit DLL is named ChilkatDelphiXE4.dll and is valid for both Delphi XE3 and XE4.

The Chilkat *.pas sources (included in the download) contain the DLL function declarations that make it easy to use the exported DLL functions in a Delphi program.  Each of these .pas sources references the path to the DLL.  For example, this is a snippet from  the Mailman.pas source file showing the DLL reference:

...
implementation

const DLLName = 'ChilkatDelphiXE.dll';

function CkMailMan_Create; external DLLName;
procedure CkMailMan_Dispose; external DLLName;
...

Given that no path was specified, the “ChilkatDelphiXE.dll” is assumed to be in the same directory as the .pas source. It is possible to place the DLL in any directory, but the .pas files used by an application must be updated to reference the DLL:

...
implementation

const DLLName = 'c:/ChilkatDelphi/ChilkatDelphiXE.dll';

function CkMailMan_Create; external DLLName;
procedure CkMailMan_Dispose; external DLLName;
...

64-bit Builds
To build for 64-bit, the DLL referenced from within a .pas must be changed to reference the ChilkatDelphiXE64.dll, or as another solution, rename the 32-bit ChilkatDelphiXE.dll to ChilkatDelphiXE32.dll and then rename ChilkatDelphiXE64.dll to ChilkatDelphiXE.dll, which would then match what is referenced within the .pas files. Perhaps there are better solutions…

Delphi: how to call ActiveX Functions that return an OleVariant containing a Byte Array

Some Chilkat ActiveX functions return binary data as a Variant containing a byte array.  If the Chilkat function fails, it returns an OleVariant containing an empty byte array. The following code snippet is incorrect and will cause Delphi to raise an EVariantBadIndexError exception if an empty OleVariant is returned.

// This is incorrect:
mybuffer : array of byte;
// ...
// Do not directly assign a variable declared as "array of byte" to the OleVariant returned 
// by the Chilkat function.
mybuffer := socket.ReceiveBytes();

The correct technique is to do the following:

myVariant: OleVariant;
myByteArray : array of byte;
uBound : Integer;
numBytes : Integer;

// ....

    myVariant := socket.ReceiveBytes();

    // Check the upper bound of the array contained within the Variant.
    // If it equals -1, then it is empty which means the Chilkat function call failed.
    uBound := VarArrayHighBound( myVariant, 1 );
    if (uBound < 0) then
    begin
        Memo1.Lines.Add(socket.LastErrorText);
        Exit;
    end;
    // The number of bytes in the byte array equals uBound+1
    numBytes := uBound+1;
    // Now that we know the variant is non-empty, it is safe to access it as an "array of byte"
    myByteArray := myVariant;

Delphi ActiveX Event Handling

ActiveX event callbacks can be used to get progress information for lengthy operations, especially for API calls involving communications with servers, such as FTP, SFTP (SSH), IMAP, POP3, SMTP, HTTP, etc.

To use an ActiveX in Delphi, the component must be “imported”. This generates wrapper files (TLB.pas) in the “imports” directory, and may also install icons on the component palettte.

The Chilkat components are non-visual. There are two ways of creating an instance of a Chilkat object: (1) drag and drop the icon from the palette onto the form. The non-visual object will be seen as a small box on the form. The component may be selected, and the events can be obtained from the object inspector. (2) The recommended technique for instantiating a Chilkat object is to do so dynamically (do not drag and drop onto the form). In this case, you can go to the generated TLB.pas file and locate the event signatures (i.e. functions). Write your own method that has the same function signature, and point the relevant event property to your new method.

For example, here are the event signatures in the TLB.pas for Chilkat FTP2:

  TChilkatFtp2PutProgress = procedure(ASender: TObject; pctDone: Integer) of object;
  TChilkatFtp2GetProgress = procedure(ASender: TObject; pctDone: Integer) of object;
  TChilkatFtp2AbortCheck = procedure(ASender: TObject; out abort: Integer) of object;
  TChilkatFtp2BeginDownloadFile = procedure(ASender: TObject; const path: WideString; 
                                                              out skip: Integer) of object;
  TChilkatFtp2EndDownloadFile = procedure(ASender: TObject; const path: WideString; 
                                                            numBytes: Integer) of object;
  TChilkatFtp2VerifyDownloadDir = procedure(ASender: TObject; const path: WideString; 
                                                              out skip: Integer) of object;
  TChilkatFtp2BeginUploadFile = procedure(ASender: TObject; const path: WideString; 
                                                            out skip: Integer) of object;
  TChilkatFtp2EndUploadFile = procedure(ASender: TObject; const path: WideString; numBytes: Integer) of object;
  TChilkatFtp2VerifyUploadDir = procedure(ASender: TObject; const path: WideString; 
                                                            out skip: Integer) of object;
  TChilkatFtp2VerifyDeleteDir = procedure(ASender: TObject; const path: WideString; 
                                                            out skip: Integer) of object;
  TChilkatFtp2VerifyDeleteFile = procedure(ASender: TObject; const path: WideString; 
                                                             out skip: Integer) of object;

Cleaning up ActiveX Objects in Delphi — calling Free

When a Chilkat object is declared using “TChilkat*” such as TChilkatSFtp, and it is instantiated dynamically, then it must be explicitly freed (destroyed). The following code fragment demonstrates. If the Free method is not called, then object instances will accumulate in memory.

procedure TForm1.Button1Click(Sender: TObject);
var
sftp: TChilkatSFtp;
i: Integer;

begin

for i := 1 to 1000 do
  begin
    sftp := TChilkatSFtp.Create(Self);

    // ....

    sftp.Free();
  end;

end;

end.