I'm just starting to learn how to use the Indy 10 components in Delphi XE2. I started with a project that will use the command sockets (TIdCmdTCPServer and TIdCmdTCPClient). I've got everything set up and the client connects to the server, but after the client connects, any command the server sends to the client just freezes the server app, until it eventually crashes and closes (after a deep freeze).
Project Setup
The setup is very simple; there's a small server app and a small client app, each with its corresponding Indy command tcp socket component. There's only one command handler on the client.
Server App
On the server, I have a very simple wrapper for the context type TCli = class(TIdServerContext) which only contains one public property (the inheritance is practically a requirement of Indy).
Client App
The client on the other hand works just fine. It receives the command from the server and does its thing. The client has a timer which auto-connects if it's not already connected. It's currently set to try to connect after 1 second of the app starting, and keep attempting every 10 seconds if not connected already.
Problem Details
I am able to send one or two commands from the server to the client successfully (client responds properly), but the server freezes a few seconds after sending the command. I have event handlers for OnConnect, OnDisconnect, OnContextCreated, and OnException on the server, which all they do really is either post a log or handle connect/disconnect objects in a list view.
Screen Shot
Finally when the client app is gracefully closed, the server also gracefully snaps out of its frozen state. However if the client is forcefully closed, then the server is also forcefully closed. That's the pattern I'm seeing. It posts to a log on events with PostLog(const S: String) which simply appends short messages to a TMemo.
I've done two projects and had the problem on both. I've prepared a sample project...
Server Code (uServer.pas and uServer.dfm)
unit uServer;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdBaseComponent, IdComponent,
IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, Vcl.StdCtrls, Vcl.Buttons,
Vcl.ComCtrls;
type
TCli = class(TIdServerContext)
private
function GetIP: String;
public
property IP: String read GetIP;
procedure DoTest;
end;
TForm3 = class(TForm)
Svr: TIdCmdTCPServer;
Lst: TListView;
Log: TMemo;
cmdDoCmdTest: TBitBtn;
procedure cmdDoCmdTestClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure SvrConnect(AContext: TIdContext);
procedure SvrContextCreated(AContext: TIdContext);
procedure SvrDisconnect(AContext: TIdContext);
procedure SvrException(AContext: TIdContext; AException: Exception);
private
public
procedure PostLog(const S: String);
function NewContext(AContext: TIdContext): TCli;
procedure DelContext(AContext: TIdContext);
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
{ TCli }
procedure TCli.DoTest;
begin
Connection.SendCmd('DoCmdTest');
end;
function TCli.GetIP: String;
begin
Result:= Binding.PeerIP;
end;
{ TForm3 }
procedure TForm3.PostLog(const S: String);
begin
Log.Lines.Append(S);
end;
procedure TForm3.SvrConnect(AContext: TIdContext);
var
C: TCli;
begin
C:= TCli(AContext);
PostLog(C.IP+': Connected');
end;
procedure TForm3.SvrContextCreated(AContext: TIdContext);
var
C: TCli;
begin
C:= NewContext(AContext);
PostLog(C.IP+': Context Created');
end;
procedure TForm3.SvrDisconnect(AContext: TIdContext);
var
C: TCli;
begin
C:= TCli(AContext);
PostLog(C.IP+': Disconnected');
DelContext(AContext);
end;
procedure TForm3.SvrException(AContext: TIdContext; AException: Exception);
var
C: TCli;
begin
C:= TCli(AContext);
PostLog(C.IP+': Exception: '+AException.Message);
end;
procedure TForm3.cmdDoCmdTestClick(Sender: TObject);
var
X: Integer;
C: TCli;
I: TListItem;
begin
for X := 0 to Lst.Items.Count - 1 do begin
I:= Lst.Items[X];
C:= TCli(I.Data);
C.DoTest;
end;
end;
procedure TForm3.DelContext(AContext: TIdContext);
var
I: TListItem;
X: Integer;
begin
for X := 0 to Lst.Items.Count - 1 do begin
I:= Lst.Items[X];
if I.Data = TCli(AContext) then begin
Lst.Items.Delete(X);
Break;
end;
end;
end;
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Svr.Active:= False;
end;
procedure TForm3.FormCreate(Sender: TObject);
begin
Svr.Active:= True;
end;
function TForm3.NewContext(AContext: TIdContext): TCli;
var
I: TListItem;
begin
Result:= TCli(AContext);
I:= Lst.Items.Add;
I.Caption:= Result.IP;
I.Data:= Result;
end;
end.
//////// DFM ////////
object Form3: TForm3
Left = 315
Top = 113
Caption = 'Indy 10 Command TCP Server'
ClientHeight = 308
ClientWidth = 529
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
DesignSize = (
529
308)
PixelsPerInch = 96
TextHeight = 13
object Lst: TListView
Left = 336
Top = 8
Width = 185
Height = 292
Anchors = [akTop, akRight, akBottom]
Columns = <
item
AutoSize = True
end>
TabOrder = 0
ViewStyle = vsReport
ExplicitLeft = 333
ExplicitHeight = 288
end
object Log: TMemo
Left = 8
Top = 56
Width = 316
Height = 244
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
ScrollBars = ssVertical
TabOrder = 1
end
object cmdDoCmdTest: TBitBtn
Left = 8
Top = 8
Width = 217
Height = 42
Caption = 'Send Test Command'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 2
OnClick = cmdDoCmdTestClick
end
object Svr: TIdCmdTCPServer
Bindings = <>
DefaultPort = 8664
MaxConnections = 100
OnContextCreated = SvrContextCreated
OnConnect = SvrConnect
OnDisconnect = SvrDisconnect
OnException = SvrException
CommandHandlers = <>
ExceptionReply.Code = '500'
ExceptionReply.Text.Strings = (
'Unknown Internal Error')
Greeting.Code = '200'
Greeting.Text.Strings = (
'Welcome')
HelpReply.Code = '100'
HelpReply.Text.Strings = (
'Help follows')
MaxConnectionReply.Code = '300'
MaxConnectionReply.Text.Strings = (
'Too many connections. Try again later.')
ReplyTexts = <>
ReplyUnknownCommand.Code = '400'
ReplyUnknownCommand.Text.Strings = (
'Unknown Command')
Left = 288
Top = 8
end
end
Client Code (uClient.pas and uClient.dfm)
unit uClient;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.ExtCtrls,
IdContext, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
IdCmdTCPClient, IdCommandHandlers, Vcl.StdCtrls;
const // --- Change accordingly ---
TMR_INT = 10000; //how often to check for connection
SVR_IP = '192.168.4.100'; //Server IP Address
SVR_PORT = 8664; //Server Port
type
TForm4 = class(TForm)
Tmr: TTimer;
Cli: TIdCmdTCPClient;
Log: TMemo;
procedure CliCommandHandlers0Command(ASender: TIdCommand);
procedure TmrTimer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure CliConnected(Sender: TObject);
procedure CliDisconnected(Sender: TObject);
private
procedure PostLog(const S: String);
public
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.PostLog(const S: String);
begin
Log.Lines.Append(S);
end;
procedure TForm4.CliCommandHandlers0Command(ASender: TIdCommand);
begin
PostLog('Received command successfully');
end;
procedure TForm4.CliConnected(Sender: TObject);
begin
PostLog('Connected to Server');
end;
procedure TForm4.CliDisconnected(Sender: TObject);
begin
PostLog('Disconnected from Server');
end;
procedure TForm4.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Cli.Disconnect;
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
Tmr.Enabled:= True;
end;
procedure TForm4.TmrTimer(Sender: TObject);
begin
if Tmr.Interval <> TMR_INT then
Tmr.Interval:= TMR_INT;
if not Cli.Connected then begin
try
Cli.Host:= SVR_IP;
Cli.Port:= SVR_PORT;
Cli.Connect;
except
on e: exception do begin
Cli.Disconnect;
end;
end;
end;
end;
end.
//////// DFM ////////
object Form4: TForm4
Left = 331
Top = 570
Caption = 'Indy 10 Command TCP Client'
ClientHeight = 317
ClientWidth = 305
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
DesignSize = (
305
317)
PixelsPerInch = 96
TextHeight = 13
object Log: TMemo
Left = 8
Top = 56
Width = 289
Height = 253
Anchors = [akLeft, akTop, akRight, akBottom]
ScrollBars = ssVertical
TabOrder = 0
ExplicitWidth = 221
ExplicitHeight = 245
end
object Tmr: TTimer
Enabled = False
OnTimer = TmrTimer
Left = 56
Top = 8
end
object Cli: TIdCmdTCPClient
OnDisconnected = CliDisconnected
OnConnected = CliConnected
ConnectTimeout = 0
Host = '192.168.4.100'
IPVersion = Id_IPv4
Port = 8664
ReadTimeout = -1
CommandHandlers = <
item
CmdDelimiter = ' '
Command = 'DoCmdTest'
Disconnect = False
Name = 'cmdDoCmdTest'
NormalReply.Code = '200'
ParamDelimiter = ' '
ParseParams = True
Tag = 0
OnCommand = CliCommandHandlers0Command
end>
ExceptionReply.Code = '500'
ExceptionReply.Text.Strings = (
'Unknown Internal Error')
Left = 16
Top = 8
end
end
The reason your server is freezing up is because you are deadlocking your server code.
For each client that connects to TIdCmdTCPServer, a worker thread is created that continuously reads inbound commands from that connection so it can trigger TIdCommandHandler.OnCommand events in the TIdCmdTCPServer.CommandHandlers collection. TCli.DoTest() calls TIdTCPConnection.SendCmd() to send a command to a client and read its response. You are calling TCli.DoTest() (and thus SendCmd()) in the context of the main thread, so you have two separate thread contexts trying to read from the same connection at the same time, causing a race condition. The worker thread running inside of TIdCmdTCPServer is likely reading portions of (if not all of) the data that SendCmd() is expecting and will never see, so SendCmd() does not exit properly, blocking the main message loop from being able to process new messages ever again, hense the freeze.
Placing a TIdAntiFreeze in the server app can help avoid the freezing, by allowing the main thread context to continue processing messages while SendCmd() is deadlocked. But that is not a true solution. To really fix this, you need to redesign your server app. For starters, do not use TIdCmdTCPServer with TIdCmdTCPClient, as they are not designed to be used together. If your server is going to send commands to the client, and the client is never sending commands to the server, then use a plain TIdTCPServer instead of TIdCmdTCPServer. But even if you do not make that change, you still have other problems with your current server code. Your server event handlers are not performing thread-safe operations, and you need to move the call to TCli.DoTest() out of the main thread context.
Try this code:
uServer.pas:
unit uServer;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdBaseComponent, IdComponent,
IdTCPConnection, IdCustomTCPServer, IdTCPServer, IdThreadSafe, IdYarn, Vcl.StdCtrls, Vcl.Buttons,
Vcl.ComCtrls;
type
TCli = class(TIdServerContext)
private
fCmdQueue: TIdThreadSafeStringList;
fCmdEvent: TEvent;
function GetIP: String;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
procedure PostCmd(const S: String);
property CmdQueue: TIdThreadSafeStringList read fCmdQueue;
property CmdEvent: TEvent read fCmdEvent;
property IP: String read GetIP;
end;
TForm3 = class(TForm)
Svr: TIdTCPServer;
Lst: TListView;
Log: TMemo;
cmdDoCmdTest: TBitBtn;
procedure cmdDoCmdTestClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure SvrConnect(AContext: TIdContext);
procedure SvrDisconnect(AContext: TIdContext);
procedure SvrExecute(AContext: TIdContext);
procedure SvrException(AContext: TIdContext; AException: Exception);
public
procedure NewContext(AContext: TCli);
procedure DelContext(AContext: TCli);
end;
var
Form3: TForm3;
implementation
uses
IdSync;
{$R *.dfm}
{ TLog }
type
TLog = class(TIdNotify)
protected
fMsg: String;
procedure DoNotify; override;
public
class procedure PostLog(const S: String);
end;
procedure TLog.DoNotify;
begin
Form3.Log.Lines.Append(fMsg);
end;
class procedure TLog.PostLog(const S: String);
begin
with Create do begin
fMsg := S;
Notify;
end;
end;
{ TCliList }
type
TCliList = class(TIdSync)
protected
fCtx: TCli;
fAdding: Boolean;
procedure DoSynchronize; override;
public
class procedure AddContext(AContext: TCli);
class procedure DeleteContext(AContext: TCli);
end;
procedure TCliList.DoSynchronize;
begin
if fAdding then
Form3.NewContext(fCtx)
else
Form3.DelContext(fCtx);
end;
class procedure TCliList.AddContext(AContext: TCli);
begin
with Create do try
fCtx := AContext;
fAdding := True;
Synchronize;
finally
Free;
end;
end;
class procedure TCliList.DeleteContext(AContext: TCli);
begin
with Create do try
fCtx := AContext;
fAdding := False;
Synchronize;
finally
Free;
end;
end;
{ TCli }
constructor TCli.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
fCmdQueue := TIdThreadSafeStringList.Create;
fCmdEvent := TEvent.Create(nil, True, False, '');
end;
destructor TCli.Destroy;
begin
fCmdQueue.Free;
fCmdEvent.Free;
inherited Destroy;
end;
procedure TCli.PostCmd;
var
L: TStringList;
begin
L := fCmdQueue.Lock;
try
L.Add('DoCmdTest');
fCmdEvent.SetEvent;
finally
fCmdQueue.Unlock;
end;
end;
function TCli.GetIP: String;
begin
Result := Binding.PeerIP;
end;
{ TForm3 }
procedure TForm3.SvrConnect(AContext: TIdContext);
var
C: TCli;
begin
C := TCli(AContext);
TCliList.AddContext(C);
TLog.PostLog(C.IP + ': Connected');
end;
procedure TForm3.SvrDisconnect(AContext: TIdContext);
var
C: TCli;
begin
C := TCli(AContext);
TCliList.DeleteContext(C);
TLog.PostLog(C.IP + ': Disconnected');
end;
procedure TForm3.SvrExecute(AContext: TIdContext);
var
C: TCli;
L, Q: TStringList;
X: Integer;
begin
C := TCli(AContext);
if C.CmdEvent.WaitFor(500) <> wrSignaled then Exit;
Q := TStringList.Create;
try
L := C.CmdQueue.Lock;
try
Q.Assign(L);
L.Clear;
C.CmdEvent.ResetEvent;
finally
C.CmdQueue.Unlock;
end;
for X := 0 to Q.Count - 1 do begin
AContext.Connection.SendCmd(Q.Strings[X]);
end;
finally
Q.Free;
end;
end;
procedure TForm3.SvrException(AContext: TIdContext; AException: Exception);
var
C: TCli;
begin
C := TCli(AContext);
TLog.PostLog(C.IP + ': Exception: ' + AException.Message);
end;
procedure TForm3.cmdDoCmdTestClick(Sender: TObject);
var
X: Integer;
L: TList;
begin
L := Svr.Contexts.LockList;
try
for X := 0 to L.Count - 1 do begin
TCli(L.Items[X]).PostCmd;
end;
finally
Svr.Contexts.UnlockList;
end;
end;
procedure TForm3.DelContext(AContext: TCli);
var
I: TListItem;
begin
I := Lst.FindData(0, AContext, true, false);
if I <> nil then I.Delete;
end;
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Svr.Active := False;
end;
procedure TForm3.FormCreate(Sender: TObject);
begin
Svr.ContextClass := TCli;
Svr.Active := True;
end;
procedure TForm3.NewContext(AContext: TCli);
var
I: TListItem;
begin
I := Lst.Items.Add;
I.Caption := AContext.IP;
I.Data := AContext;
end;
end.
uServer.dfm:
object Form3: TForm3
Left = 315
Top = 113
Caption = 'Indy 10 Command TCP Server'
ClientHeight = 308
ClientWidth = 529
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
DesignSize = (
529
308)
PixelsPerInch = 96
TextHeight = 13
object Lst: TListView
Left = 336
Top = 8
Width = 185
Height = 292
Anchors = [akTop, akRight, akBottom]
Columns = <
item
AutoSize = True
end>
TabOrder = 0
ViewStyle = vsReport
ExplicitLeft = 333
ExplicitHeight = 288
end
object Log: TMemo
Left = 8
Top = 56
Width = 316
Height = 244
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
ScrollBars = ssVertical
TabOrder = 1
end
object cmdDoCmdTest: TBitBtn
Left = 8
Top = 8
Width = 217
Height = 42
Caption = 'Send Test Command'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 2
OnClick = cmdDoCmdTestClick
end
object Svr: TIdTCPServer
Bindings = <>
DefaultPort = 8664
MaxConnections = 100
OnConnect = SvrConnect
OnDisconnect = SvrDisconnect
OnExecute = SvrExecute
OnException = SvrException
Left = 288
Top = 8
end
end
uClient.pas:
unit uClient;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.ExtCtrls,
IdContext, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
IdCmdTCPClient, IdCommandHandlers, Vcl.StdCtrls;
const // --- Change accordingly ---
TMR_INT = 10000; //how often to check for connection
SVR_IP = '192.168.4.100'; //Server IP Address
SVR_PORT = 8664; //Server Port
type
TForm4 = class(TForm)
Tmr: TTimer;
Cli: TIdCmdTCPClient;
Log: TMemo;
procedure CliCommandHandlers0Command(ASender: TIdCommand);
procedure TmrTimer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure CliConnected(Sender: TObject);
procedure CliDisconnected(Sender: TObject);
private
procedure AppMessage(var Msg: TMsg; var Handled: Boolean);
procedure PostLog(const S: String);
procedure PostReconnect;
public
end;
var
Form4: TForm4;
implementation
uses
IdSync;
{$R *.dfm}
{ TLog }
type
TLog = class(TIdNotify)
protected
fMsg: String;
procedure DoNotify; override;
public
class procedure PostLog(const S: String);
end;
procedure TLog.DoNotify;
begin
Form4.Log.Lines.Append(fMsg);
end;
class procedure TLog.PostLog(const S: String);
begin
with Create do begin
fMsg := S;
Notify;
end;
end;
{ TForm4 }
const
WM_START_RECONNECT_TIMER = WM_USER + 100;
procedure TForm4.CliCommandHandlers0Command(ASender: TIdCommand);
begin
TLog.PostLog('Received command successfully');
end;
procedure TForm4.CliConnected(Sender: TObject);
begin
TLog.PostLog('Connected to Server');
end;
procedure TForm4.CliDisconnected(Sender: TObject);
begin
TLog.PostLog('Disconnected from Server');
PostReconnect;
end;
procedure TForm4.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Tmr.Enabled := False;
Application.OnMessage := nil;
Cli.Disconnect;
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
Tmr.Enabled := True;
end;
procedure TForm4.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
if Msg.message = WM_START_RECONNECT_TIMER then begin
Handled := True;
Tmr.Interval := TMR_INT;
Tmr.Enabled := True;
end;
end;
procedure TForm4.TmrTimer(Sender: TObject);
begin
Tmr.Enabled := False;
Cli.Disconnect;
try
Cli.Host := SVR_IP;
Cli.Port := SVR_PORT;
Cli.Connect;
except
PostReconnect;
end;
end;
procedure TForm4.PostReconnect;
begin
PostMessage(Application.Handle, WM_START_RECONNECT_TIMER, 0, 0);
end;
end.
uClient.dfm:
object Form4: TForm4
Left = 331
Top = 570
Caption = 'Indy 10 Command TCP Client'
ClientHeight = 317
ClientWidth = 305
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
DesignSize = (
305
317)
PixelsPerInch = 96
TextHeight = 13
object Log: TMemo
Left = 8
Top = 56
Width = 289
Height = 253
Anchors = [akLeft, akTop, akRight, akBottom]
ScrollBars = ssVertical
TabOrder = 0
ExplicitWidth = 221
ExplicitHeight = 245
end
object Tmr: TTimer
Enabled = False
OnTimer = TmrTimer
Left = 56
Top = 8
end
object Cli: TIdCmdTCPClient
OnDisconnected = CliDisconnected
OnConnected = CliConnected
ConnectTimeout = 0
Host = '192.168.4.100'
IPVersion = Id_IPv4
Port = 8664
ReadTimeout = -1
CommandHandlers = <
item
CmdDelimiter = ' '
Command = 'DoCmdTest'
Disconnect = False
Name = 'cmdDoCmdTest'
NormalReply.Code = '200'
ParamDelimiter = ' '
ParseParams = True
Tag = 0
OnCommand = CliCommandHandlers0Command
end>
ExceptionReply.Code = '500'
ExceptionReply.Text.Strings = (
'Unknown Internal Error')
Left = 16
Top = 8
end
end
Have you tried debugging the server?
The line
Result:= TCli(AContext);
(hard cast of TIdContext) looks like a potential reason for the freeze.
Have you read this, how to make the TIdCustomTCPServer aware of your own TIdServerContext class?
https://stackoverflow.com/a/5514932/80901
The relevant code in the answer:
constructor TOurServer.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
...
ContextClass := TOurContext;
...
end;
Related
I have never needed to do much COM, so have almost no experience with it. Nearly all of the documentation (certainly from MS) does not include Delphi examples.
In my code example, I can't see where I'm going wrong. The code was borrowed from snippets found in several locations on the web. Some were VB. I only found one thread for Free Pascal, and it was incomplete. This runs, but shows displays the same string for both name and value. I hope someone can see what I'm missing. I think the problem is with the line that reads:
PropValue := OleFolder.GetDetailsOf(OleFolderItem, i);
I don't know if I need to do something to initialize "OleFolderItem".
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ActiveX, ComObj, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
pnl1: TPanel;
btn1: TButton;
mmo1: TMemo;
OpenDialog1: TOpenDialog;
procedure btn1Click(Sender: TObject);
procedure getExtdProps(AFileName: string);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
Procedure TForm1.getExtdProps(AFileName: string);
var
Shell : Variant;
OleFolder : OleVariant;
OleFolderItem: OleVariant;
PropName, PropValue: string;
i: integer;
begin
Shell := CreateOleObject('Shell.Application');
OleFolder := Shell.Namespace(ExtractFilePath(AFileName));
i := 0;
PropName := 'Not an EmptyStr'; //So the next loop will work.
while PropName <> EmptyStr do
begin
PropName := OleFolder.GetDetailsOf(null, i); {null gets the name}
PropValue := OleFolder.GetDetailsOf(OleFolderItem, i); { OleFolderItem should get the value }
if PropName <> '' then
mmo1.Lines.Add(PropName + ': ' + PropValue);
inc(i);
end;
end;
procedure TForm1.btn1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
GetExtdProps(OpenDialog1.FileName);
end;
end;
end.
Try this complete example :
unit GetDetailsOfDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Win.ComObj, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
procedure GetExtdProps(AFileName: string);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.GetExtdProps(AFileName: string);
var
Shell : Variant;
OleFolder : OleVariant;
OleFolderItem : OleVariant;
ForlderName : String;
PropName : String;
PropValue : string;
I : integer;
begin
Shell := CreateOleObject('Shell.Application');
OleFolder := Shell.Namespace(ExtractFilePath(AFileName));
OleFolderItem := OleFolder.ParseName(ExtractFileName(AFileName));
for I := 0 to 999 do begin
PropName := OleFolder.GetDetailsOf(null, i);
PropValue := OleFolder.GetDetailsOf(OleFolderItem , I);
if (PropName <> '') and (PropValue <> '') then
Memo1.Lines.Add(Format('%3d) %-30s: %s',
[I, PropName, PropValue]));
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
GetExtdProps(OpenDialog1.FileName);
end;
end.
DFM file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 441
ClientWidth = 624
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
PixelsPerInch = 96
DesignSize = (
624
441)
TextHeight = 15
object Button1: TButton
Left = 40
Top = 32
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object Memo1: TMemo
Left = 8
Top = 72
Width = 609
Height = 361
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Consolas'
Font.Style = []
ParentFont = False
ScrollBars = ssBoth
TabOrder = 1
end
object OpenDialog1: TOpenDialog
Left = 48
Top = 88
end
end
I have a form with KeyPreview=true and want to capture the arrow keys, unless we are in a control that should handle those.
The issue is: focus is always on one of those controls.
How can I adapt/design this to work?
.PAS file
unit uKeyDownTests;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TFrmKeyDownTests = class(TForm)
PnlBottom: TPanel;
PnlClient: TPanel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
public
end;
var
FrmKeyDownTests: TFrmKeyDownTests;
implementation
{$R *.dfm}
type
THackWinControl = class(TWinControl);
procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var lActiveControl: TControl;
begin
// Earlier code, but that did not work either:
// if Edit1.Focused or Edit2.Focused or Edit3.Focused then Exit;
lActiveControl := ActiveControl;
if Assigned(lActiveControl) then
begin
if lActiveControl = Edit1 then
begin
THackWinControl(Edit1).KeyDown(Key,Shift);
Exit;
end;
if lActiveControl = Edit2 then
begin
THackWinControl(Edit2).KeyDown(Key,Shift);
Exit;
end;
if lActiveControl = Edit3 then
begin
THackWinControl(Edit3).KeyDown(Key,Shift);
Exit;
end;
end;
if (Key = VK_RIGHT) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'R';
Key := 0;
Exit;
end;
if (Key = VK_LEFT) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'L';
Key := 0;
Exit;
end;
if (Key = VK_UP) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'U';
Key := 0;
Exit;
end;
if (Key = VK_DOWN) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'D';
Key := 0;
Exit;
end;
end;
end.
.DFM file
object FrmKeyDownTests: TFrmKeyDownTests
Left = 0
Top = 0
Caption = 'Keydown tests'
ClientHeight = 336
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
KeyPreview = True
OldCreateOrder = False
OnKeyDown = FormKeyDown
PixelsPerInch = 96
TextHeight = 13
object PnlBottom: TPanel
Left = 0
Top = 295
Width = 635
Height = 41
Align = alBottom
TabOrder = 0
end
object PnlClient: TPanel
Left = 0
Top = 0
Width = 635
Height = 295
Align = alClient
TabOrder = 1
object Edit1: TEdit
Left = 40
Top = 32
Width = 121
Height = 21
TabOrder = 0
Text = 'Edit1'
end
object Edit2: TEdit
Left = 40
Top = 72
Width = 121
Height = 21
TabOrder = 1
Text = 'Edit1'
end
object Edit3: TEdit
Left = 40
Top = 112
Width = 121
Height = 21
TabOrder = 2
Text = 'Edit1'
end
end
end
(Answering my own question for my specific situation, slightly different from the one in the 'Possible dupe', but based on the answers there)
In my case, the easiest solution was:
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY; which only calls inherited
KeyPreview=true for the form
A FormKeydown that handles what I want to do with arrow keys
Result:
The controls that have focus as well as the form handle the arrow keys
It does not matter if the controls have an OnKeyDown handler (the Edit2 control) or not (the others)
Modified code:
unit uKeyDownTests;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TFrmKeyDownTests = class(TForm)
PnlBottom: TPanel;
PnlClient: TPanel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY; public
end;
var
FrmKeyDownTests: TFrmKeyDownTests;
implementation
{$R *.dfm}
procedure TFrmKeyDownTests.DialogKey(var Msg: TWMKey);
begin
inherited;
end;
procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
VK_RIGHT: PnlBottom.Caption := PnlBottom.Caption + 'R';
VK_LEFT : PnlBottom.Caption := PnlBottom.Caption + 'L';
VK_UP : PnlBottom.Caption := PnlBottom.Caption + 'U';
VK_DOWN : PnlBottom.Caption := PnlBottom.Caption + 'D';
end;
end;
{ TFrmKeyDownTests }
procedure TFrmKeyDownTests.Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
PnlBottom.Caption := PnlBottom.Caption + '-kd-';
end;
end.
I use Thread in my code to send SMS.
for Send SMS I use the MCoreComponent Class;
first, override Create function AND create a objSMS1 object,
then call objSMS1.connect() in the Execute Function
constructor ReceiveThread.create;
begin
Inherited Create(True);
objSMS1 := TSMS.Create(nil);
end;
procedure ReceiveThread.Execute();
begin
if Not objSMS1.IsError(true, strMyAppName) then
begin
objSMS1.Connect();
if Not objSMS1.IsError(true, strMyAppName) then
ShowMessage('Connection successful');
end;
while not Terminated do
begin
CoInitialize(nil);
DoShowData;//Recieved Message
end;
end;
these two functions work correctly, Connecting to Module Successfully Done, and check inbox every time.
But I need to send a message. My Send Message Function Is:
procedure ReceiveThread.SendSMS(phoneno, txt: String);
var strSendResult :String;
begin
objSMS1.Validity := Trim('24') + LeftStr('Hour', 1);//Access Violation Error
strSendResult := objSMS1.SendSMS(phoneno, txt, False);
if Not objSMS1.IsError(true, strMyAppName) then
MessageDlg('Message sent!', mtInformation, [mbOK], 0);
end;
When I call the SendSMS Function In Button Click On Main Form, App encounter Access Violation Error. How can I Call Send Message In Thread?
other Setting
var
RTh : ReceiveThread;//Global Var
//Run Tread
RTh := ReceiveThread.Create();
RTh.FreeOnTerminate := True;
//Send Message From Button Click
RTh.SendSMS(Phoneno,Msg);//Access Violation Error
As per the question, the main visible problem is that MessageDlg is called from inside a method of the thread without a synchronized block but the code itself has many other issues and the comments to your question have already pointed you out in the right direction.
The call to DoShowData could be another trouble but the question doesn't give more details about it.
Another strange thing is the recurrent call to CoInitialize. Even though this doesn't represent a big issue since subsequent calls return False, the call has to be balanced by CoUninitialize.
Quoting a comment: "Is SendSMS thread-safe?" you know.
I've tried to put some order in your code - I hope...
The thread uses a list of type TThreadList<TSMSInfo> and treats it like a queue to store and get the SMS to be sent: the list is accessed through its Locklist method in order to avoid concurrent access.
The SMS sent notify is implemented as a custom notify event of type TSMSSentEvent: if assigned the event is triggered in between a synchronized block in order to be executed in the main thread (the VCL thread in a GUI application).
Sleep(1) reduces the CPU charge* when the queue is empty - from 50% to 2% on my PC.
Beware of the objSMS1 object creation and its disposal because where I've put it might be not the right place; also probably you have to call objSMS1.Connect every time the queue is sent and objSMS1.Disconnect - this method should be available - right after that but you should know about it.
The {$DEFINE FAKESMS} compiler directive allowed me to test the app since I don't own any of the MCoreComponent libraries: I've left it as is for testing purposes.
SMSSender.pas unit: the thread class and friends
unit SMSSender;
{.$DEFINE FAKESMS}
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
Winapi.ActiveX;
const
StrMyAppName = '';
type
{$IFDEF FAKESMS}
TSMS = class
public
Validity: string;
function IsError(a: Boolean; b: string): Boolean;
procedure Connect;
function SendSMS(phoneNo, text: string; bBool: Boolean): string;
constructor Create(AObj: TObject);
end;
{$ENDIF}
TSMSInfo = record
id: Integer;
phoneNo: string;
text: string;
end;
TSMSSentEvent = procedure (Sender: TObject; AId: Integer; AIsError: Boolean; AResult: string) of object;
TSMSSender = class(TThread)
private
FSMSList: TThreadList<TSMSInfo>;
FSentCount: Integer;
function GetQueueCount: Integer;
protected
procedure Execute; override;
public
OnSMSSent: TSMSSentEvent;
procedure AddSMS(const ASMSInfo: TSMSInfo);
constructor Create(CreateSuspended: Boolean = False);
destructor Destroy; override;
property QueueCount: Integer read GetQueueCount;
property SentCount: Integer read FSentCount;
end;
implementation
{$IFDEF FAKESMS}
{ TSMS }
procedure TSMS.Connect;
begin
end;
constructor TSMS.Create(AObj: TObject);
begin
end;
function TSMS.IsError(a: Boolean; b: string): Boolean;
begin
Result := False;
end;
function TSMS.SendSMS(phoneNo, text: string; bBool: Boolean): string;
begin
Result := 'message sent';
Sleep(300);//simulates the SMS sent
end;
{$ENDIF}
{ TReceiveThread }
constructor TSMSSender.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FSentCount := 0;
FSMSList := TThreadList<TSMSInfo>.Create;
end;
destructor TSMSSender.Destroy;
begin
FSMSList.Free;
inherited;
end;
function TSMSSender.GetQueueCount: Integer;
begin
Result := FSMSList.LockList.Count;
FSMSList.UnlockList;
end;
procedure TSMSSender.AddSMS(const ASMSInfo: TSMSInfo);
begin
FSMSList.Add(ASMSInfo);
end;
procedure TSMSSender.Execute;
var
objSMS1: TSMS;
SMSInfo: TSMSInfo;
strSendResult: string;
lst: TList<TSMSInfo>;
begin
CoInitialize(nil);
try
objSMS1 := TSMS.Create(nil);
try
if objSMS1.IsError(True, StrMyAppName) then
raise Exception.Create('Error Message 1');
objSMS1.Connect;
if objSMS1.IsError(True, StrMyAppName) then
raise Exception.Create('Error Message 2');
objSMS1.Validity := '24H';
while not Terminated do begin
while GetQueueCount > 0 do begin
lst := FSMSList.LockList;
try
SMSInfo := lst.First;
lst.Delete(0);
finally
FSMSList.UnlockList;
end;
//maybe the following has to be synchronized in order to work properly?
//Synchronize(procedure
// begin
strSendResult := objSMS1.SendSMS(SMSInfo.phoneNo, SMSInfo.text, False);
// end);
Inc(FSentCount);
if Assigned(OnSMSSent) then
Synchronize(procedure
begin
OnSMSSent(Self, SMSInfo.id, objSMS1.IsError(true, StrMyAppName), strSendResult);
end);
if Terminated then
Break;
end;
Sleep(1);
end;
finally
objSMS1.Free;
end;
finally
CoUninitialize;
end;
end;
end.
Unit1.pas unit: the form unit
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.UITypes,
SMSSender;
type
TForm1 = class(TForm)
btnAddSMS: TButton;
Memo1: TMemo;
btnTerminate: TButton;
btnStart: TButton;
procedure btnAddSMSClick(Sender: TObject);
procedure btnTerminateClick(Sender: TObject);
procedure btnStartClick(Sender: TObject);
private
{ Private declarations }
FReceiver: TSMSSender;
procedure ReceiverSMSSent(Sender: TObject; AId: Integer; AIsError: Boolean; AResult: string);
procedure ReceiverTerminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
System.Math;
{$R *.dfm}
procedure TForm1.btnAddSMSClick(Sender: TObject);
var
sms: TSMSInfo;
begin
with sms do begin
id := Random(65535);
phoneNo := '+39' + IntToStr(RandomRange(111111111, 999999999));
text := 'You won nothing at all, as usual';
end;
FReceiver.AddSMS(sms);
end;
procedure TForm1.btnStartClick(Sender: TObject);
begin
Memo1.Lines.Clear;
FReceiver := TSMSSender.Create(True);
FReceiver.FreeOnTerminate := True;
FReceiver.OnSMSSent := ReceiverSMSSent;
FReceiver.OnTerminate := ReceiverTerminate;
FReceiver.Start;
btnStart.Enabled := False;
btnAddSMS.Enabled := True;
btnTerminate.Enabled := True;
end;
procedure TForm1.btnTerminateClick(Sender: TObject);
begin
FReceiver.Terminate;
end;
procedure TForm1.ReceiverSMSSent(Sender: TObject; AId: Integer; AIsError: Boolean;
AResult: string);
begin
Memo1.Lines.Add(Format('id = %d'#9'isError = %s'#9'result = %s', [AId, BoolToStr(AIsError), AResult]));
end;
procedure TForm1.ReceiverTerminate(Sender: TObject);
var
receiver: TSMSSender;
ex: Exception;
begin
btnStart.Enabled := True;
btnAddSMS.Enabled := False;
btnTerminate.Enabled := False;
receiver := TSMSSender(Sender);
ex := Exception(receiver.FatalException);
if Assigned(ex) then begin
MessageDlg(ex.Message, mtError, [mbOK], 0);
Exit;
end;
MessageDlg(Format('Thread %d has finished, %d SMS sent, queue count is %d.', [receiver.ThreadID, receiver.SentCount, receiver.QueueCount]), mtInformation, [mbOK], 0);
end;
end.
Unit1.dfm unit
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 277
ClientWidth = 527
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
527
277)
PixelsPerInch = 96
TextHeight = 13
object btnAddSMS: TButton
Left = 440
Top = 209
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'Add SMS'
Enabled = False
TabOrder = 0
OnClick = btnAddSMSClick
end
object Memo1: TMemo
Left = 8
Top = 8
Width = 417
Height = 257
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Lucida Console'
Font.Style = []
Lines.Strings = (
'Memo1')
ParentFont = False
ScrollBars = ssVertical
TabOrder = 1
end
object btnTerminate: TButton
Left = 440
Top = 240
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'Terminate'
Enabled = False
TabOrder = 2
OnClick = btnTerminateClick
end
object btnStart: TButton
Left = 440
Top = 178
Width = 75
Height = 25
Anchors = [akRight, akBottom]
Caption = 'Start'
TabOrder = 3
OnClick = btnStartClick
end
end
* Why Sleep(1) is better than Sleep(0)
My application can download one picture from every url in memo1.
It uses idhttp.get and has a skipbutton. After skip it downloads the next picture.
Q1: Do you have code to put into the destructor and what is the code for " terminate" and "waitfor"?
I found this on another website:
destructor thread.destroy;
begin
try
Terminate;
If HTTP.Connected then HTTP.Disconnect;
finally
WaitFor;
FreeAndNil(HTTP);
end;
inherited;
end;
Q2: How do I call the destructor and make it work?
Q3: Do you have hints (especially security concerns) and additional lines of code?
the code of my application:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdHTTP;
type
thread = class
public
Constructor Create; overload;
Destructor Destroy; override;
end;
TForm1 = class(TForm)
IdHTTP1: TIdHTTP;
Memo1: TMemo;
Memo2: TMemo;
Memo3: TMemo;
startbutton: TButton;
skipbutton: TButton;
procedure startbuttonClick(Sender: TObject);
procedure skipbuttonClick(Sender: TObject);
procedure IdHTTP1Work(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCount: Integer);
end;
var
Form1: TForm1;
http: tidhttp;
s: boolean;
implementation
{$R *.dfm}
constructor thread.Create;
begin
HTTP := TIdHTTP.Create(nil);
inherited ;
end;
destructor thread.destroy;
begin
try
If HTTP.Connected then HTTP.Disconnect;
finally
FreeAndNil(HTTP);
end;
inherited;
end;
procedure TForm1.startbuttonClick(Sender: TObject);
var
i: integer;
fs : TFileStream ;
begin
for i:= 0 to memo1.lines.count-1 do begin
s:= false;
fs := TFileStream.Create(inttostr(i)+'abc.jpg', fmCreate);
http:= idhttp1;
try
try
HTTP.Get(memo1.lines[i],fs);
memo2.Lines.add(memo1.Lines[i]);
except
on E: Exception do
begin
memo3.lines.add(' ha ha ha not working '+syserrormessage(getlasterror));
end;
end;
finally
fs.free;
end;
end;
end;
procedure TForm1.skipbuttonClick(Sender: TObject);
begin
s:=true;
end;
procedure TForm1.IdHTTP1Work(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCount: Integer);
begin
application.ProcessMessages;
if s = true then
http.Disconnect;
end;
end.
Since your are using IdHttp from the GUI (= main thread) and Indy is blocking, you have two options: a) use IdAntifreeze in combination with messages (just drop the component on the form), b) use threads.
Do NOT use Application.Processmessages as it will lead to strange side effects.
now to answer your questions:
Q1: the code you found on the internet implemented solution b) so this is not applicable for your current code
Q2: same as Q1
Q3 : here is a version that correctly implements solution a)
This code is still not 100% perfect as it does not implement logic for disabling/enabling the starttransfer and skiptransfer buttons (I leave that as an exercise for you :) ).
unit Unit16;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdAntiFreezeBase, IdAntiFreeze, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
const
WM_TRANSFER = WM_USER + 1;
type
TForm1 = class(TForm)
IdHTTP1: TIdHTTP;
IdAntiFreeze1: TIdAntiFreeze;
Memo1: TMemo;
Btn_start: TButton;
Btn_skip: TButton;
Memo2: TMemo;
procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure Btn_startClick(Sender: TObject);
procedure Btn_skipClick(Sender: TObject);
private
{ Private declarations }
Transferring : Boolean;
UrlIndex : Integer;
procedure NextTransfer(var msg : TMessage); message WM_TRANSFER;
procedure StartTransfer;
procedure DoTransfer;
procedure SkipTransfer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.NextTransfer(var msg: TMessage);
begin
DoTransfer;
end;
procedure TForm1.SkipTransfer;
begin
Transferring := false;
end;
procedure TForm1.StartTransfer;
begin
UrlIndex := 0;
DoTransfer;
end;
procedure TForm1.DoTransfer;
var
Url : String;
Stream : TStringStream;
begin
if UrlIndex < Memo1.Lines.Count then
begin
Url := Memo1.Lines[UrlIndex];
Memo2.Lines.Add(Format('getting data from URL: %s', [Url]));
Inc(UrlIndex);
Transferring := True;
try
Stream := TStringStream.Create;
try
IdHttp1.Get(Url, Stream);
Memo2.Lines.Add(Format('Data: "%s"',[Stream.DataString]));
finally
Stream.Free;
end;
except
on E: Exception do
begin
Memo2.Lines.Add(Format('error during transfer: %s', [E.Message]));
end;
end;
Transferring := False;
PostMessage(Handle, WM_TRANSFER, 0, 0);
end;
end;
procedure TForm1.Btn_startClick(Sender: TObject);
begin
Memo2.Lines.Add('starting transfer');
StartTransfer;
end;
procedure TForm1.Btn_skipClick(Sender: TObject);
begin
Memo2.Lines.Add('skipping current transfer');
SkipTransfer;
end;
procedure TForm1.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
Memo2.Lines.Add('work event');
if not Transferring and (AWorkMode = wmRead) then
try
Memo2.Lines.Add('disconnecting peer');
IdHttp1.Disconnect;
except
end;
end;
end.
DFM file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 290
ClientWidth = 707
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Memo1: TMemo
Left = 92
Top = 12
Width = 213
Height = 257
Lines.Strings = (
'http://stackoverflow.com'
'http://www.google.com'
'http://www.hardocp.com'
'')
TabOrder = 0
WordWrap = False
end
object Btn_start: TButton
Left = 8
Top = 128
Width = 75
Height = 25
Caption = 'Btn_start'
TabOrder = 1
OnClick = Btn_startClick
end
object Btn_skip: TButton
Left = 8
Top = 159
Width = 75
Height = 25
Caption = 'Btn_skip'
TabOrder = 2
OnClick = Btn_skipClick
end
object Memo2: TMemo
Left = 320
Top = 12
Width = 373
Height = 257
TabOrder = 3
WordWrap = False
end
object IdHTTP1: TIdHTTP
OnWork = IdHTTP1Work
AllowCookies = True
ProxyParams.BasicAuthentication = False
ProxyParams.ProxyPort = 0
Request.ContentLength = -1
Request.ContentRangeEnd = -1
Request.ContentRangeStart = -1
Request.ContentRangeInstanceLength = -1
Request.Accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
Request.BasicAuthentication = False
Request.UserAgent = 'Mozilla/3.0 (compatible; Indy Library)'
Request.Ranges.Units = 'bytes'
Request.Ranges = <>
HTTPOptions = [hoForceEncodeParams]
Left = 24
Top = 16
end
object IdAntiFreeze1: TIdAntiFreeze
Left = 16
Top = 72
end
end
I am using the Jedi usb hid component to connect to, read and write from a HID device. I have been unable to write to the device. I have been using this code.
type
TReport = Packed record
ReportID: byte;
Data: array [0..64] of byte;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I:integer;
HidData:TReport;
written:DWORD;
begin
hiddata.ReportID:=0;
hiddata.Data[0]:=0;
hiddata.Data[1]:=$80;
for I := 2 to 64 do
hiddata.Data[I]:=$FF;
currentdevice.WriteFile(hiddata,currentdevice.Caps.OutputReportByteLength,written);
end;
I made a test platform which you can use :
unit BasicMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, StdCtrls, Forms, Dialogs,
JvHidControllerClass, JvComponentBase;
type
TReport = packed record
ReportID: byte;
Data: array [0..64] of byte;
end;
TMainForm = class(TForm)
HidCtl: TJvHidDeviceController;
DeviceList: TListBox;
Label1: TLabel;
Label2: TLabel;
Button1: TButton;
procedure HidCtlDeviceChange(Sender: TObject);
function HidCtlEnumerate(HidDev: TJvHidDevice;const Idx: Integer): Boolean;
procedure Button1Click(Sender: TObject);
procedure FormCreate( Sender : TObject);
procedure DeviceRemoval(HidDev: TJvHidDevice);
procedure DeviceArrival(HidDev: TJvHidDevice);
public
end;
var
MainForm: TMainForm;
MyDevice: TJvHidDevice;
implementation
{$R *.dfm}
{ ***************************************************************************** }
Const
MyVendorID = $04D8; // Put in your matching VendorID
MyProductID = $003F; // Put in your matching ProductID
procedure TMainForm.FormCreate( Sender : TObject);
begin
HidCtl.OnArrival:= DeviceArrival;
HidCtl.OnRemoval:= DeviceRemoval;
end;
procedure TMainForm.DeviceRemoval(HidDev: TJvHidDevice);
begin
if ((Assigned(MyDevice)) and (NOT MyDevice.IsPluggedIn)) then
begin
HidCtl.CheckIn(MyDevice);
end;
end;
procedure TMainForm.DeviceArrival(HidDev: TJvHidDevice);
begin
if ((HidDev.Attributes.VendorID = MyVendorID) AND
(HidDev.Attributes.ProductID = MyProductID) AND
(HidDev.Caps.OutputReportByteLength = SizeOf(TReport)) ) then
begin
if HidDev.CheckOut then
begin
MyDevice := HidDev;
end;
end;
end;
procedure TMainForm.HidCtlDeviceChange(Sender: TObject);
begin
Label1.Caption := '-';
Label2.Caption := '-';
MyDevice := nil;
DeviceList.Clear;
HidCtl.Enumerate;
end;
function TMainForm.HidCtlEnumerate(HidDev: TJvHidDevice;const Idx: Integer): Boolean;
begin
DeviceList.Items.Add(
Format('%.4x/%.4x', [HidDev.Attributes.VendorID,HidDev.Attributes.ProductID]));
if (HidDev.Attributes.VendorID = MyVendorID) and (HidDev.Attributes.ProductID = MyProductID) then
begin
HidCtl.CheckOut(HidDev);
MyDevice := HidDev;
Label1.Caption := Format('%.4x/%.4x', [MyDevice.Attributes.VendorID , MyDevice.Attributes.ProductID]);
Label2.Caption := 'Length = '+ IntToStr(MyDevice.Caps.OutputReportByteLength) + ' ' + IntToStr(MyDevice.Caps.InputReportByteLength);
end;
Result := True;
end;
procedure TMainForm.Button1Click(Sender: TObject);
var
HidData : TReport;
written : DWORD;
begin
HidData.ReportID:=0;
HidData.Data[0]:=$80;
// Fill with more data
MyDevice.WriteFile(HidData, MyDevice.Caps.OutputReportByteLength, Written);
MyDevice.ReadFile(HidData, MyDevice.Caps.InputReportByteLength, Written);
end;
end.
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'MainForm'
ClientHeight = 341
ClientWidth = 535
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 48
Top = 8
Width = 31
Height = 13
Caption = 'Label1'
end
object Label2: TLabel
Left = 48
Top = 27
Width = 31
Height = 13
Caption = 'Label2'
end
object Button1: TButton
Left = 48
Top = 46
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object ListBox1: TListBox
Left = 48
Top = 96
Width = 465
Height = 97
ItemHeight = 13
TabOrder = 1
end
end
Fill in your VendorID and ProductID and the output data.
Effictivly one line do the trick that the writefile method is accepted or not :
ToWrite := TheDev.Caps.OutputReportByteLength;
TheDev.WriteFile(buffer,towrite,written);
The device accept only write of the correct buffer length. It doesn't work if you write only a part of the buffer length.