TwinCAT I/0 Ring 3 DLL: Delphi Application
For this example, the functions of the TcatIoDrv DLL are ported to Pascal and assembled in a unit named TCatIoDrv.pas.
The source for this example can be unpacked from here: TwinCAT I/O Ring 3 DLL Delphi Application.

System requirements:
- TwinCAT 2.5 or higher
- TCatIoDrv.Dll
- Delphi 5.0
Description
The mapping of the process image of the inputs and outputs of an additional task in the TwinCAT System Manager is to be triggered from the Delphi application. The cycle time is 100 ms, and is generated with the aid of a multi-media timer. The online values of the process images are displayed in two list boxes. The TwinCAT I/O devices can be reset by means of the I/O Reset button. Mapping the inputs and outputs can be started or stopped using the Start I/O cycle and Stop I/O cycle buttons. If there are any error messages they are output in another list box.
Task Configuration in the TwinCAT System Manager
Port number 301 is configured for the additional task in the TwinCAT System Manager.

The process image has the following size:
Input image: 2 bytes;
Output image: 8 bytes;

The port number and the byte size of the process image are defined as constants in the TCatIoDrv.pas unit, and must be appropriately changed if they have any other value.
Linking the TCatIoDrv unit
The declarations of the TCatIoDrv.DLL functions used are found in the TCatIoDrv.pas Pascal unit. This is linked into the project by means of a "uses" clause.
unit TCatIoDrvDelphiUnit;
interface
uses
TCatIoDrv,
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
StdCtrls, ExtCtrls, Grids;
...
Unit TCatIoDrv.pas
unit TCatIoDrv;
interface
uses sysutils,windows;
{$A-}
const
// TwinCAT® System Manager->Additional Tasks->Task 1 tab:
TASK_1_PORTNUMBER = 301;
// TwinCAT® System Manager->Additional Tasks->Task 1-Image->Size/Offset tab:
MAX_INPUT_IMAGE_BYTESIZE = 2;
MAX_OUTPUT_IMAGE_BYTESIZE = 8;
type
TInputImage = ARRAY[ 0 .. MAX_INPUT_IMAGE_BYTESIZE - 1 ] OF Byte;
TOutputImage = ARRAY[ 0 .. MAX_OUTPUT_IMAGE_BYTESIZE - 1 ] OF Byte;
PInputImage = ^TInputImage;
POutputImage = ^TOutputImage;
function TCatIoOpen:longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoOpen@0';
function TCatIoClose:longint;stdcall; external 'TCatIoDrv.dll' name '_TCatIoClose@0';
function TCatIoInputUpdate( nPort : WORD ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoInputUpdate@4';
function TCatIoOutputUpdate( nPort : WORD ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoOutputUpdate@4';
function TCatIoGetInputPtr( nPort : WORD; var pInput :Pointer; nSize :longint ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoGetInputPtr@12';
function TCatIoGetOutputPtr( nPort : WORD; var pOutput :Pointer; nSize :longint ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoGetOutputPtr@12';
function TCatIoReset():longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoReset@0';
function TCatIoGetCpuTime( var pCpuTime : TFileTime ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoGetCpuTime@4';
function TCatIoGetCpuCounter( var pCpuCount : int64 ):longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoGetCpuCounter@4';
implementation
initialization
finalization
end.
The Application
The event function FormCreate calls the DLL function TcatIoOpen. If successful, the function returns a null, and a connection to the TwinCAT I/O sub-system is established. The user is informed of any errors in the list box. When the application is closed, the connection to the TwinCAT I/O sub-system must be removed. This is done in the FormDestroy event function, which calls the DLL function TCatIotClose. Once the connection has been successfully established, two other functions are called: TCatIoGetInputPtr and TCatIoGetoutputPtr. These functions return pointers to the process images for the inputs and outputs. These pointers can be used to read the input process data or to write to the outputs.
unit TCatIoDrvDelphiUnit;
interface
usesTCatIoDrv,
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Grids;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Timer1: TTimer;
StartButton: TButton;
StopButton: TButton;
Label1: TLabel;
Button1: TButton;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
ListBox2: TListBox;
ListBox3: TListBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure StartButtonClick(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure InitControls();
procedure CalculateNewOutputs(pData : Pointer; cbSize :integer);
procedure ViewData( var ListBox : TListBox; pData : Pointer; cbSize :integer);
public
{ Public declarations }
end;
var
Form1: TForm1;
InputImagePtr :PInputImage;
OutputImagePtr :POutputImage;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var Result : integer;
InPtr, OutPtr :Pointer;
begin
InitControls();
Result := TCatIoOpen();
if ( Result <> 0 ) then
ListBox1.Items.Insert(0, Format('TCatIoOpen() error:%d', [ Result ] ) )
else
begin
{get/initialize pointer to the input image}
InPtr := NIL;
Result := TCatIoGetInputPtr( TASK_1_PORTNUMBER, InPtr, MAX_INPUT_IMAGE_BYTESIZE );
if ( Result = 0 ) And ( InPtr <> NIL ) then
InputImagePtr := InPtr
else
ListBox1.Items.Insert(0, Format('TCatIoGetInputPtr() error:%d', [ Result ] ) );
{get/initialize pointer to the output image}
OutPtr := NIL;
Result := TCatIoGetOutputPtr( TASK_1_PORTNUMBER, OutPtr, MAX_OUTPUT_IMAGE_BYTESIZE );
if ( Result = 0 ) And ( OutPtr <> NIL ) Then
OutputImagePtr := OutPtr
else
ListBox1.Items.Insert(0, Format('TCatIoGetOutputPtr() error:%d', [ Result ] ) );
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var Result : integer;
begin
Result := TCatIoClose();
if ( Result <> 0 ) then
MessageBox(0, 'TCatIoClose() error!', 'Error', MB_OK);
end;
The Timer1Timer event routine is called cyclically when the multimedia timer is active. The following functions are called every time by this routine:
- TCatIoInputUpdate (DLL function: triggers an update of the process image for the inputs (inputs are read));
- ViewData (auxiliary procedure: displays the current values of the inputs in a list box);
- CalculateNewOutputs (auxiliary procedure: generates/alters the values of the outputs (e.g. the running light));
- TCatIoOutputUpdate (DLL function: triggers an update of the process image for the outputs (outputs are written));
- ViewData (auxiliary procedure: displays the current values of the outputs in a list box);
- TCatIoGetCpuCounter (DLL function: reads the current value of the CPU counter);
- TCatIoGetCpuTime (DLL function: reads the counter state of the Pentium CPU);
procedure TForm1.Timer1Timer(Sender: TObject);
var Result : integer;
CpuCounter :int64;
FileTime :TFileTime;
SystemTime :TSystemTime;
begin
Result := 0;
try
{Update input image}
Result := TCatIoInputUpdate( TASK_1_PORTNUMBER );
if ( Result <> 0 ) then
ListBox1.Items.Insert(0, Format('TCatIoInputUpdate() error:%d', [ Result ] ) );
{View inputs}
ViewData( ListBox2, InputImagePtr, sizeof(TInputImage) );
{Calculate new output values (running light)}
CalculateNewOutputs( OutputImagePtr, sizeof(TOutputImage));
{Update output image}
Result := TCatIoOutputUpdate( TASK_1_PORTNUMBER );
if ( Result <> 0 ) then
ListBox1.Items.Insert(0, Format('TCatIoOutputUpdate() error:%d', [ Result ] ) );
{View outputs}
ViewData( ListBox3, OutputImagePtr, sizeof(TOutputImage) );
except
Timer1.Enabled := false;
ListBox1.Items.Insert(0, Format('TCatIoDrv exception error:%d', [ Result ] ) );
end;
Result := TCatIoGetCpuCounter(CpuCounter);
Label4.Caption := Format('TCatIoGetCpuCounter() result:%d, CPU counter:0x%x', [Result,CpuCounter] );
Result := TCatIoGetCpuTime(FileTime);
FileTimeToSystemTime( FileTime, SystemTime );
Label5.Caption := Format('TCatIoGetCpuTime() result:%d, CPU time: %d:%d:%d:%d',
[Result,SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds]);
end;
The routines for activating/deactivating the timer:
procedure TForm1.StartButtonClick(Sender: TObject);
begin
StartButton.Enabled := false;
StopButton.Enabled := true;
Timer1.Enabled := true;
end;
procedure TForm1.StopButtonClick(Sender: TObject);
begin
StartButton.Enabled := true;
StopButton.Enabled := false;
Timer1.Enabled := false;
end;
The routine for the I/O reset:
procedure TForm1.Button1Click(Sender: TObject);
var Result : integer;
begin
Result := TCatIoReset();
if ( Result <> 0 ) Then
ListBox1.Items.Insert( 0, Format('TCatIoReset() error:%d', [ Result ] ) );
end;
procedure TForm1.InitControls();
var Row :integer;
begin
StartButton.Enabled := true;
StopButton.Enabled := false;
Timer1.Enabled := false;
Timer1.Interval := 100; {100 ms}
for Row:= 0 To MAX_INPUT_IMAGE_BYTESIZE - 1 do
ListBox2.Items.Add( Format( 'Byte[%d]', [Row] ) );
for Row:= 0 To MAX_OUTPUT_IMAGE_BYTESIZE - 1 do
ListBox3.Items.Add( Format( 'Byte[%d]', [Row] ) );
end;
procedure TForm1.CalculateNewOutputs(pData : Pointer; cbSize :integer);
var i:integer;
pByte : ^Byte;
begin
if ( pData <> NIL ) And (cbSize > 0) then
begin
for i:= 0 to cbSize - 1 do
begin
pByte := Pointer(Integer(pData) + i);
if ( pByte^ = 0 ) then pByte^ := 1
else pByte^ := pByte^ shl 1;
end;
end;
end;
procedure TForm1.ViewData( var ListBox : TListBox; pData : Pointer; cbSize :integer );
var
ByteOff : Integer;
pByte : ^Byte;
begin
if ( pData <> NIL ) And (cbSize > 0) then
begin
for ByteOff := 0 to cbSize - 1 do
begin
pByte:= Pointer( integer(pData) + ByteOff );
ListBox.Items.Strings[ByteOff] := Format('Byte[%d] Value:0x%x',[ ByteOff, pByte^ ] );
end;
end;
end;
end.
The source for this example can be unpacked from here: TwinCAT I/O Ring 3 DLL Delphi Application.