Error with PrivateMessages in IdIRC - delphi

Hi I have a problem with the following code:
program test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, idIRC, idContext, idGlobal;
// procedure TForm1.IdIRC1PrivateMessage(ASender: TIdContext; const ANickname,
// AHost, ATarget, AMessage: string);
// procedure TForm1.IdIRC1Raw(ASender: TIdContext; AIn: Boolean;
// const AMessage: string);
procedure IrcPrivateMessage(ASelf: Pointer; const ANickname, AHost, ATarget,
AMessage: string);
begin
Writeln('[+] Message ' + ANickname + ' > ' + AMessage);
end;
procedure IrcRaw(ASelf: Pointer; ASender: TIdContext; AIn: Boolean;
const AMessage: String);
begin
Writeln(iif(AIn, '[Recv] ', '[Sent] ') + AMessage);
end;
var
irc: TIdIRC;
m1: TMethod;
m2: TMethod;
begin
try
irc := TIdIRC.Create(nil);
try
irc.Host := 'localhost';
irc.Port := 6667;
irc.Nickname := 'tester';
irc.Username := 'tester';
m1.Code := #IrcRaw;
m1.Data := irc;
irc.OnRaw := TIdIRCRawEvent(m1);
m2.Code := #IrcPrivateMessage;
m2.Data := irc;
irc.OnPrivateMessage := TIdIRCPrivMessageEvent(m2);
try
irc.Connect;
except
Writeln('Nay');
Exit;
end;
Writeln('Yeah');
irc.Join('#locos');
while ('1' = '1') do
begin
//
end;
finally
irc.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The problem is when I receive a private message, the ANickname entry gives me the following error:
Project test.exe raised exeption class #C00000005 with message 'access violation at 0x00404673:read of adress 0x03cf4e58
What am I doing wrong?

TIdIRCPrivMessageEvent is a "procedures of object", which implicitly references the object. You added a Self parameter for that, but you omitted the TIdContext parameter that is also part of the event:
procedure IrcPrivateMessage(ASelf: Pointer; Sender: TIdContext; const ANickname, AHost, ATarget, AMessage: string);
begin
....
end;

Related

Adding object methods to a stringlist so they can be invoked by name

I have server code than accepts commands from clients and executes object methods, as determined by the command received. I want to build a stringlist using AddObject to associate the command with the desired procedure. This works fine with standalone procedures but I get "variable required" errors when trying to add object methods to my stringlist. Here's example code:
type
TExample = class
public
var Commands: TStringList;
constructor Create;
destructor Destroy; override;
procedure ExecCommand(Cmd, Msg: string);
procedure Alpha(Msg: string);
procedure Beta(Msg: string);
procedure Gamma(Msg: string);
end;
constructor TExample.Create;
begin
inherited Create;
Commands := TStringList.Create;
Commands.AddObject('Alpha', #Alpha); // fails to compile: "variable required"
Commands.AddObject('Beta', #Beta);
Commands.AddObject('Gamma', #Gamma);
end;
destructor TExample.Destroy;
begin
Commands.Free;
inherited Destroy;
end;
procedure TExample.ExecCommand(Cmd, Msg: string);
type
TProcType = procedure(Msg: string);
var
i: integer;
P: TProcType;
begin
i := Commands.IndexOf(Cmd);
if i >= 0 then
begin
P := TProcType(Commands.Objects[i]);
P(Msg);
end;
end;
procedure TExample.Alpha(Msg: string);
begin
ShowMessage('Alpha: ' + Msg);
end;
procedure TExample.Beta(Msg: string);
begin
ShowMessage('Beta: ' + Msg);
end;
procedure TExample.Gamma(Msg: string);
begin
ShowMessage('Gamma: ' + Msg);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Example: TExample;
Cmd, Msg: string;
begin
Cmd := Edit1.Text;
Msg := Edit2.Text;
Example := TExample.Create;
Example.ExecCommand(Cmd, Msg);
Example.Free;
end;
You are trying to call non-static class methods on TExample objects, so you need to add of object to the declaration of TPropType to handle the Self parameter:
type
TProcType = procedure(Msg: string) of object;
However, non-static object method pointers are larger then plain vanilla pointers, as they carry two pieces of information - a pointer to the object, and a pointer to the method to call on the object - so you can't directly store a non-static method pointer in the TStringList.Objects[] list. However, you can store it indirectly.
One way is to dynamically allocating the method pointers, eg:
type
TExample = class
public
var Commands: TStringList;
constructor Create;
destructor Destroy; override;
procedure ExecCommand(Cmd, Msg: string);
procedure Alpha(Msg: string);
procedure Beta(Msg: string);
procedure Gamma(Msg: string);
end;
type
TProcType = procedure(Msg: string) of object;
PProcType = ^TProcType;
constructor TExample.Create;
var
P: PProcType;
begin
inherited Create;
Commands := TStringList.Create;
New(P);
P^ := #Alpha;
Commands.AddObject('Alpha', TObject(P));
New(P);
P^ := #Beta;
Commands.AddObject('Beta', TObject(P));
New(P);
P^ := #Gamma;
Commands.AddObject('Gamma', TObject(P));
end;
destructor TExample.Destroy;
var
I: Integer;
begin
for I := 0 to Commands.Count-1 do
Dispose(PProcType(Commands.Objects[I]));
Commands.Free;
inherited Destroy;
end;
procedure TExample.ExecCommand(Cmd, Msg: string);
var
i: integer;
P: PProcType;
begin
i := Commands.IndexOf(Cmd);
if i >= 0 then
begin
P := PProcType(Commands.Objects[i]);
P^(Msg);
end;
end;
procedure TExample.Alpha(Msg: string);
begin
ShowMessage('Alpha: ' + Msg);
end;
procedure TExample.Beta(Msg: string);
begin
ShowMessage('Beta: ' + Msg);
end;
procedure TExample.Gamma(Msg: string);
begin
ShowMessage('Gamma: ' + Msg);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Example: TExample;
Cmd, Msg: string;
begin
Cmd := Edit1.Text;
Msg := Edit2.Text;
Example := TExample.Create;
Example.ExecCommand(Cmd, Msg);
Example.Free;
end;
Another way is to store static pointers to the class methods, and then make use of the TMethod record to help you when you need to call the methods, like #OndrejKelle described in comments, eg:
type
TExample = class
public
var Commands: TStringList;
constructor Create;
destructor Destroy; override;
procedure ExecCommand(Cmd, Msg: string);
procedure Alpha(Msg: string);
procedure Beta(Msg: string);
procedure Gamma(Msg: string);
end;
type
TProcType = procedure(Msg: string) of object;
constructor TExample.Create;
begin
inherited Create;
Commands := TStringList.Create;
Commands.AddObject('Alpha', TObject(#TExample.Alpha));
Commands.AddObject('Beta', TObject(#TExample.Beta));
Commands.AddObject('Gamma', TObject(#TExample.Gamma));
end;
destructor TExample.Destroy;
begin
Commands.Free;
inherited Destroy;
end;
procedure TExample.ExecCommand(Cmd, Msg: string);
var
i: integer;
P: TProcType;
begin
i := Commands.IndexOf(Cmd);
if i >= 0 then
begin
TMethod(P).Data := Self;
TMethod(P).Code := Pointer(Commands.Objects[i]);
P(Msg);
end;
end;
procedure TExample.Alpha(Msg: string);
begin
ShowMessage('Alpha: ' + Msg);
end;
procedure TExample.Beta(Msg: string);
begin
ShowMessage('Beta: ' + Msg);
end;
procedure TExample.Gamma(Msg: string);
begin
ShowMessage('Gamma: ' + Msg);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Example: TExample;
Cmd, Msg: string;
begin
Cmd := Edit1.Text;
Msg := Edit2.Text;
Example := TExample.Create;
Example.ExecCommand(Cmd, Msg);
Example.Free;
end;
But either way, a TStringList is really not the best tool for this job. You really should use a TDictionary instead, then you are not having to jump through unnecessary hoops, eg:
uses
..., System.Generics.Collections;
type
TProcType = procedure(Msg: string) of object;
TExample = class
public
var Commands: TDictionary<String, TProcType>;
constructor Create;
destructor Destroy; override;
procedure ExecCommand(Cmd, Msg: string);
procedure Alpha(Msg: string);
procedure Beta(Msg: string);
procedure Gamma(Msg: string);
end;
constructor TExample.Create;
begin
inherited Create;
Commands := TDictionary<String, TProcType>.Create;
Commands.Add('Alpha', #Alpha);
Commands.Add('Beta', #Beta);
Commands.Add('Gamma', #Gamma);
end;
destructor TExample.Destroy;
begin
Commands.Free;
inherited Destroy;
end;
procedure TExample.ExecCommand(Cmd, Msg: string);
var
P: TProcType;
begin
if Commands.TryGetValue(Cmd, P) then
P(Msg);
end;
procedure TExample.Alpha(Msg: string);
begin
ShowMessage('Alpha: ' + Msg);
end;
procedure TExample.Beta(Msg: string);
begin
ShowMessage('Beta: ' + Msg);
end;
procedure TExample.Gamma(Msg: string);
begin
ShowMessage('Gamma: ' + Msg);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Example: TExample;
Cmd, Msg: string;
begin
Cmd := Edit1.Text;
Msg := Edit2.Text;
Example := TExample.Create;
Example.ExecCommand(Cmd, Msg);
Example.Free;
end;
Thanks for the posted solutions. I decided to use the TMethod option and simplify it. Since I can make the passed in commands match my object method names exactly, I can skip the hash list and make the calls directly as such:
type
TExample = class
published
procedure Alpha(Msg: string);
procedure Beta(Msg: string);
procedure Gamma(Msg: string);
public
procedure ExecCommand(Cmd, Msg: string);
end;
procedure TExample.ExecCommand(Cmd, Msg: string);
type
TProcType = procedure(Msg: string) of object;
var
M: TMethod;
P: TProcType;
begin
M.Code := Self.MethodAddress(Cmd);
if M.Code = Nil then ShowMessage('Unknown command: ' + Cmd) else
begin
M.Data := Pointer(Self);
P := TProcType(M);
P(Msg);
end;
end;

Delphi (6 & XE8), MSMQ, EOleException, Not enough storage is available to complete this operation

My Windows service throws an EOleException exception when the code tries to access the 'Body' property of a MSMQ message.
The error is intermittent and the service can run for a month without an issue.
The message payload is an XML string.
The service processes a small number of messages a day (~10) and the message payload is small <900 bytes.
The messages are sent to the MSMQ Server by a BizTalk application and event sinked in the service.
I've ported the code from D6 to XE8 (I imported the mqoa30.tlb Type Library into the XE8 RAD) and the problem occurs in both the D6 and XE8 build.
The error occurs in the 'arrived' procedure, when the error occurs I haven't been able to access the 'Body' in any way.
Any suggestions on how to resolve this problem (or debug it further)?
An old post that had a similar issue (I haven't been able to pin the problem on an issue with the XML payload).
Error Log
error, TMyThread.Arrived (VarIsStr Test): ClassName: EOleException, Error: Not enough storage is available to complete this operation
error, TMyThread.Arrived (QueueMessage Assignment): ClassName: EOleException, Error: Not enough storage is available to complete this operation,Label: adf7cea1-7be8-4382-8687-f4ea0f8a5e50, Body Length: 871, Msg Class: 0, Cursor: 0, Encrypt Algorithm: 26625, Journal: 1, Sender Version: 16
Code
procedure TMyThread.Arrived(ASender: TObject; const Queue: IDispatch; Cursor: Integer);
var
strMSMQMessage, strMsg, strMessageFile, strMessageProperties: string;
blnMsgAssigned: boolean;
intBodyLength, intMessageClass: integer;
wstrLabel: WideString;
intEncryptAlgorithm, intJournal, intSenderVersion: longint;
QueueMessage: IMSMQMessage3;
varTransaction, varWantDestinationQueue, varWantBody, varReceiveTimeOut, varWantConnectorType: OLEVariant;
begin
QueueMessage:= nil;
strMSMQMessage:= '';
intBodyLength:= 0;
wstrLabel:= '';
strMessageProperties:= '';
blnMsgAssigned:= False;
try
varTransaction:= False;
varWantDestinationQueue:= False;
varWantBody:= True;
varReceiveTimeOut:= 30000;
varWantConnectorType:= False;
QueueMessage:= IUnknown(Variant(FQueue).Receive(varTransaction, varWantDestinationQueue, varWantBody, varReceiveTimeOut, varWantConnectorType)) as IMSMQMessage3;
if Assigned(QueueMessage) then
begin
intBodyLength:= QueueMessage.BodyLength;
intMessageClass:= QueueMessage.MsgClass;
wstrLabel:= QueueMessage.Label_;
intEncryptAlgorithm:= QueueMessage.EncryptAlgorithm;
intJournal:= QueueMessage.Journal;
intSenderVersion:= QueueMessage.SenderVersion;
strMessageProperties:= 'Label: '+wstrLabel+', Body Length: '+IntToStr(intBodyLength)+', Msg Class: '+IntToStr(intMessageClass)+', Cursor: '+IntToStr(Cursor);
strMessageProperties:= strMessageProperties+', Encrypt Algorithm: '+IntToStr(intEncryptAlgorithm)+', Journal: '+IntToStr(intJournal);
strMessageProperties:= strMessageProperties+', Sender Version: '+IntToStr(intSenderVersion);
// Can trigger a 'Not enough storage...' error.
// Body: OLEVariant
try
if not VarIsStr(QueueMessage.Body) then
if Assigned(FEventLog) then
FEventLog.AppendToFile('debug', 'TMyThread.Arrived: VarIsStr = False', TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
except
on E: Exception do
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.Arrived (VarIsStr Test): ClassName: '+E.ClassName+', Error: '+E.Message+strMsg;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
strMSMQMessage:= VarToStrDef(QueueMessage.Body, '');
blnMsgAssigned:= True;
end;
QueueMessage:= nil;
except
on E: Exception do
begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.Arrived (QueueMessage Assignment): ClassName: '+E.ClassName+', Error: '+E.Message+','+strMessageProperties;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
end;
if blnMsgAssigned then
begin
strMessageFile:= TTMSMQService.FArchiveFilePath+'PA.'+FormatDateTime('yyyymmdd.hhmmsszzz', Now)+'.'+IntToStr(intBodyLength)+'.xml';
if Assigned(FMessageLog) then
FMessageLog.WriteToFile(strMessageFile, strMSMQMessage);
// Debug.
if Assigned(FEventLog) then
FEventLog.AppendToFile('debug', 'TMyThread.Arrived: Message Properties: '+strMessageProperties, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
if blnMsgAssigned and (Length(strMSMQMessage) > 0) then
ParseIncomingMessage(strMSMQMessage);
end else begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.Arrived: Error: QueueMessage not assigned';
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
// Debug.
if Assigned(FEventLog) then
FEventLog.AppendToFile('debug', 'TMyThread.Arrived: Try EnableNotification', TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
try
FQueue.EnableNotification(FEvent.DefaultInterface, EmptyParam, FTimeOut);
except
on E: Exception do
begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.Arrived (EnableNotification): ClassName: '+E.ClassName+', Error: '+E.Message;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
end;
end;
procedure TMyThread.Execute;
var
strMsg: string;
begin
inherited;
FreeOnTerminate:= False;
Randomize;
try
try
FQueueInfo:= CreateCOMObject(CLASS_MSMQQueueInfo) as IMSMQQueueInfo3;
FQueueInfo.FormatName:= FFormatNameOut;
FTimeOut:= -1;
FEvent:= TMSMQEvent.Create(nil);
FEvent.OnArrived:= Arrived;
FEvent.OnArrivedError:= ArrivedError;
FConnected:= OpenListeningQueue;
while not Terminated do
begin
if not FConnected then
begin
CloseListeningQueue;
FConnected:= OpenListeningQueue;
end;
Sleep(Random(500) + 1000);
end; // while not Terminated
CloseListeningQueue;
except
on E: Exception do
begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.Execute: ClassName: '+E.ClassName+', Error: '+E.Message;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
end;
finally
if Assigned(FEvent) then
FEvent.Free;
FQueueInfo:= nil;
end;
end;
TMyThread = class(TThread)
private
FFormatNameOut: string;
FEventLog: TEventLog;
FQueueInfo: IMSMQQueueInfo3;
FQueue: IMSMQQueue3;
FEvent: TMSMQEvent;
FTimeOut: OLEVariant;
FConnected: boolean;
FMessageLog: TMessageLog;
protected
procedure Execute; override;
procedure ParseIncomingMessage(const strMessage: string);
function OpenListeningQueue: boolean;
procedure CloseListeningQueue;
public
procedure Arrived(ASender: TObject; const Queue: IDispatch; Cursor: Integer);
procedure ArrivedError(ASender: TObject; const Queue: IDispatch; ErrorCode: Integer; Cursor: Integer);
constructor Create(const FormatNameOut: string);
destructor Destroy; override;
end;
constructor TMyThread.Create(const FormatNameOut: string);
const
PA_EVENTFILE = 'msmqpatevents.txt';
begin
CoInitialize(nil);
inherited Create(False);
FConnected:= False;
FFormatNameOut:= FormatNameOut;
FEventLog:= TEventLog.Create(TTMSMQService.FLogFilePath+PA_EVENTFILE);
FMessageLog:= TMessageLog.Create;
end;
destructor TMyThread.Destroy;
begin
FreeAndNil(FEventLog);
FreeAndNil(FMessageLog);
inherited Destroy;
CoUninitialize;
end;
function TMyThread.OpenListeningQueue: boolean;
var
strMsg: string;
begin
// Debug.
if Assigned(FEventLog) then
FEventLog.AppendToFile('debug', 'TMyThread.OpenListeningQueue', TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
Result:= False;
try
FQueue:= FQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
FQueue.EnableNotification(FEvent.DefaultInterface, EmptyParam, FTimeOut);
Result:= (FQueue.IsOpen = 1);
except
on E: Exception do
begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.OpenListeningQueue: ClassName: '+E.ClassName+', Error: '+E.Message;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
end;
end;
procedure TMyThread.CloseListeningQueue;
var
strMsg: string;
begin
// Debug.
if Assigned(FEventLog) then
FEventLog.AppendToFile('debug', 'TMyThread.CloseListeningQueue', TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
try
if Assigned(FQueue) then
if FQueue.IsOpen = 1 then
FQueue.Close;
except
on E: Exception do
begin
if Assigned(FEventLog) then
begin
strMsg:= 'TMyThread.CloseListeningQueue: ClassName: '+E.ClassName+', Error: '+E.Message;
FEventLog.AppendToFile('error', strMsg, TTMSMQService.FServiceVersion, TTMSMQService.FDatabaseVersion);
end;
end;
end;
end;

delphi E2009 Incompatible types: 'Parameter lists differ'

I was trying to compile this delphi code:
delphi UDP server
unit UDP.Server;
interface
uses
IdUDPServer,
IdSocketHandle,
System.SysUtils;
type
TResponseToMessage = reference to function(const AMsg : string):string;
TUDPServer = class
strict private const
DEFAULT_UDP_PORT = 49152;
strict private
FidUDPServer : TIdUDPServer;
FResponseToMessage: TResponseToMessage;
FEndOfProtocol: string;
class var FInstance : TUDPServer;
constructor Create();
destructor Free();
procedure UDPRead (AThread: TIdUDPListenerThread; AData: TArray<System.Byte>; ABinding: TIdSocketHandle);
private
class procedure ReleaseInstance();
public
procedure SetReponseToMessage(AFunction : TResponseToMessage);
procedure SetUDPPort(const APort : word);
procedure StartListen();
property EndOfProtocol : string read FEndOfProtocol write FEndOfProtocol;
class function GetInstance() : TUDPServer;
end;
implementation
{ TUdpServer }
constructor TUDPServer.Create;
begin
Self.FidUDPServer := TIdUDPServer.Create(nil);
Self.FidUDPServer.OnUDPRead := Self.UDPRead;
Self.FidUDPServer.DefaultPort := Self.DEFAULT_UDP_PORT;
end;
destructor TUDPServer.Free;
begin
Self.FidUDPServer.Free;
end;
class function TUDPServer.GetInstance: TUDPServer;
begin
if not Assigned(Self.FInstance) then
Self.FInstance := TUDPServer.Create();
Result := Self.FInstance;
end;
class procedure TUDPServer.ReleaseInstance;
begin
if Assigned(Self.FInstance) then
Self.FInstance.Free;
end;
procedure TUDPServer.SetReponseToMessage(AFunction: TResponseToMessage);
begin
Self.FResponseToMessage := AFunction;
end;
procedure TUDPServer.SetUDPPort(const APort: word);
begin
Self.FidUDPServer.Active := false;
Self.FidUDPServer.DefaultPort := APort;
Self.FidUDPServer.Active := true;
end;
procedure TUDPServer.StartListen;
begin
if not Self.FidUDPServer.Active then
Self.FidUDPServer.Active := true;
end;
procedure TUDPServer.UDPRead(AThread: TIdUDPListenerThread;
AData: TArray<System.Byte>; ABinding: TIdSocketHandle);
var
sResponse: string;
begin
sResponse := EmptyStr;
if Assigned(Self.FResponseToMessage) then
sResponse := Self.FResponseToMessage(StringOf(AData));
if sResponse <> EmptyStr then
ABinding.SendTo(ABinding.PeerIP,ABinding.PeerPort,sResponse);
ABinding.SendTo(ABinding.PeerIP,ABinding.PeerPort,Self.FEndOfProtocol);
end;
initialization
finalization
TUDPServer.ReleaseInstance();
end.
I get the error on Self.FidUDPServer.OnUDPRead := Self.UDPRead; line
E2009 Incompatible types: 'Parameter lists differ'
I can't figure out what is causing this error.
Replace TArray<System.Byte> with TIdBytes as that is what Indy is expecting you to use here. The code you have is likely from an older version of Indy.

Delphi Chromium - Iterate DOM

I'm trying to iterate the DOM using TChromium and because i use Delphi 2007 i can't use anonymous methods, so i created a class inherited of TCEFDomVisitorOwn. My code is as below, but for some reason the 'visit' procedure is never called, so nothings happens.
unit udomprinc;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ceflib, cefvcl;
type
TForm1 = class(TForm)
Chromium1: TChromium;
procedure FormCreate(Sender: TObject);
procedure Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame;
httpStatusCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TElementVisitor = class(TCefDomVisitorOwn)
private
FTagName, FHtml: string;
protected
procedure visit(const document: ICefDomDocument); override;
public
constructor Create(const par1, par2: string); reintroduce;
end;
var
Form1: TForm1;
implementation
constructor TElementVisitor.Create(const par1, par2: string);
begin
inherited create;
FTagName := par1;
FHtml := par2;
end;
procedure TElementVisitor.visit(const document: ICefDomDocument);
procedure ProcessNode(ANode: ICefDomNode);
var
Node: ICefDomNode;
tagname, name, html, value : string;
begin
if Assigned(ANode) then
begin
Node := ANode.FirstChild;
while Assigned(Node) do
begin
name := Node.GetElementAttribute('name');
tagname := Node.GetElementAttribute('tagname');
html := Node.GetElementAttribute('outerhtml');
value := Node.GetElementAttribute('value');
ProcessNode(Node);
Node := Node.NextSibling;
end;
end;
end;
begin
// this never happens
ProcessNode(document.Body);
end;
{$R *.dfm}
procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame;
httpStatusCode: Integer);
var visitor : TElementVisitor;
begin
visitor := TElementVisitor.Create('input','test');
chromium1.Browser.MainFrame.VisitDom(visitor);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
chromium1.load('www.google.com');
end;
end.
It's all about sending messages back and forth. Your code is missing a RenderProcessHandler, this allows the Renderer to receive messages.
In your DPR you should have code like this
if not CefLoadLibDefault then
Exit;
in your pas file
type
TNotifyVisitor = procedure(aNode: ICefDomNode; var aLevel: integer);// of object;
TAttributeType = (atNodeName, atName, atId, atClass, atLevel);
TElementNameVisitor = class(TCefDomVisitorOwn)
private
FName: string;
FAttributeName: string;
FOnFound: TNotifyVisitor;
FOnVisited: TNotifyVisitor;
function getAttributeName: string;
protected
procedure visit(const document: ICefDomDocument); override;
public
constructor Create(const AName: string); reintroduce;
property OnFound: TNotifyVisitor read FOnFound write FOnFound;
property OnVisited: TNotifyVisitor read FOnVisited write FOnVisited;
property AttributeName: string read getAttributeName write FAttributeName;
end;
TCustomRenderProcessHandler = class(TCefRenderProcessHandlerOwn)
protected
function OnProcessMessageReceived(const browser: ICefBrowser;
sourceProcess: TCefProcessId; const message: ICefProcessMessage): Boolean; override;
end;
implementation
var
_Browser: ICefBrowser;
{ TElementNameVisitor }
constructor TElementNameVisitor.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
function TElementNameVisitor.getAttributeName: string;
begin
if FAttributeName = '' then
Result := 'name'
else
Result := FAttributeName;
end;
procedure TElementNameVisitor.visit(const document: ICefDomDocument);
var
a_Level: integer;
a_message: iCefProcessMessage;
procedure ProcessNode(aNode: ICefDomNode; var aLevel: integer);
var
a_Node: ICefDomNode;
a_Name: string;
begin
if Assigned(aNode) then
begin
inc(aLevel);
a_Node := aNode.FirstChild;
while Assigned(a_Node) do
begin
if Assigned(FOnVisited) then
FOnVisited(a_Node, aLevel);
if Assigned(FOnFound) then
begin
a_Name := a_Node.GetElementAttribute(AttributeName);
if SameText(a_Name, FName) then
begin
// do what you need with the Node here
if Assigned(FOnFound) then
FOnFound(a_Node, aLevel);
end;
end;
ProcessNode(a_Node, aLevel);
a_Node := a_Node.NextSibling;
end;
end;
end;
begin
a_Level := 0;
ProcessNode(document.Body, a_Level);
a_message := TCefProcessMessageRef.New(cdomdataFin);
_Browser.SendProcessMessage(PID_BROWSER, a_message);
end;
You'll need to create a RenderProcessHandler:
initialization
CefRenderProcessHandler := TCustomRenderProcessHandler.Create;
To use it...You send a message to Renderer like this
function TformBrowser.HasBrowser: boolean;
begin
Result := Assigned(Chromium1.browser);
end;
procedure TformBrowser.Button1Click(Sender: TObject);
var
a_message: ICefProcessMessage;
a_list: ICefListValue;
a_How: string;
begin
if HasBrowser and FLoaded then
begin
FLoaded := False;
Case rgFindDomNodeBy.ItemIndex of
0: a_How := 'ByName';
1: a_How := 'ById';
2: a_How := 'ByClass';
3: a_How := 'ByAll';
end;
lbFrames.Items.Clear;
a_message := TCefProcessMessageRef.New(a_How);
a_list := a_message.ArgumentList;
a_list.SetString(0, edtAttribute.Text);
Chromium1.browser.SendProcessMessage(PID_RENDERER,a_message);
end;
end;
The RenderProcessHandler will get the message:
{ TCustomRenderProcessHandler }
procedure _ElementCB(aNode: ICefDomNode; var aLevel: integer);
var
a_message: ICefProcessMessage;
begin
a_message := TCefProcessMessageRef.New('domdata');
a_message.ArgumentList.SetString(Ord(atNodeName), aNode.Name);
a_message.ArgumentList.SetString(Ord(atName), aNode.GetElementAttribute('name'));
a_message.ArgumentList.SetString(Ord(atId), aNode.GetElementAttribute('id'));
a_message.ArgumentList.SetString(Ord(atClass), aNode.GetElementAttribute('class'));
a_message.ArgumentList.SetInt(Ord(atLevel), aLevel);
_Browser.SendProcessMessage(PID_BROWSER, a_message);
end;
function TCustomRenderProcessHandler.OnProcessMessageReceived(
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage): Boolean;
var
a_list: ICefListValue;
begin
_Browser := browser;
Result := False;
if SameText(message.Name, 'ByAll') then
begin
_ProcessElements(browser.MainFrame, _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByName') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0),'name', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ById') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0), 'id', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByClass') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser.MainFrame, a_list.GetString(0), 'class', _ElementCB);
Result := True;
end;
end;
The RenderProcessHandler creates the Visitor(TElementNameVisitor)
procedure _ProcessElementsByAttribute(const aFrame: ICefFrame; aName, aAttributeName: string; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aFrame) then
begin
a_Visitor := TElementNameVisitor.Create(aName);
a_Visitor.AttributeName := aAttributeName;
a_Visitor.OnFound := aVisitor;
aFrame.VisitDom(a_Visitor);
end;
end;
procedure _ProcessElements(const aFrame: ICefFrame; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aFrame) then
begin
a_Visitor := TElementNameVisitor.Create('');
a_Visitor.OnVisited := aVisitor;
aFrame.VisitDom(a_Visitor);
end;
end;
The Visitor (TElementNameVisitor)then sends a message back to TChromium and you can tie into it like:
procedure TformBrowser.Chromium1ProcessMessageReceived(Sender: TObject;
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage; out Result: Boolean);
var
a_List: ICefListValue;
begin
if SameText(message.Name, 'domdata') then
begin
a_List := message.ArgumentList;
lbFrames.Items.Add(a_List.GetString(Ord(atNodeName)));
lbFrames.Items.Add('Name: ' + a_List.GetString(Ord(atName)));
lbFrames.Items.Add('Id: ' + a_List.GetString(Ord(atId)));
lbFrames.Items.Add('Class: ' + a_List.GetString(Ord(atClass)));
lbFrames.Items.Add('Level: ' + IntToStr(a_List.GetInt(Ord(atLevel))));
lbFrames.Items.Add('------------------');
Result := True;
end else
if SameText(message.Name, cdomdataFin) then
begin
FLoaded := True;
end else
begin
lbFrames.Items.Add('Unhandled message: ' + message.Name);
inherited;
end;
end;
-----------edit-------------
After looking at this code...it can be improved...to be more thread friendly
Delete this
var
_Browser: ICefBrowser;
change this
TNotifyVisitor = procedure(aBrowser: ICefBrowser; aNode: ICefDomNode; var aLevel: integer);// of object;
add this to TElementNameVisitor
property Browser: ICefBrowser read getBrowser write FBrowser;
Change references in TElementNameVisitor to Browser also add this
function TElementNameVisitor.getBrowser: ICefBrowser;
begin
if not Assigned(FBrowser) then
Raise Exception.Create('Need to set the Browser property when creating TElementNameVisitor.');
Result := FBrowser;
end;
Change these
procedure _ProcessElementsByAttribute(const aBrowser: ICefBrowser; aName, aAttributeName: string; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aBrowser) and Assigned(aBrowser.MainFrame) then
begin
a_Visitor := TElementNameVisitor.Create(aName);
a_Visitor.Browser := aBrowser;
a_Visitor.AttributeName := aAttributeName;
a_Visitor.OnFound := aVisitor;
aBrowser.MainFrame.VisitDom(a_Visitor);
end;
end;
procedure _ProcessElements(const aBrowser: ICefBrowser; aVisitor: TNotifyVisitor);
var
a_Visitor: TElementNameVisitor;
begin
if Assigned(aBrowser) and Assigned(aBrowser.MainFrame) then
begin
a_Visitor := TElementNameVisitor.Create('');
a_Visitor.Browser := aBrowser;
a_Visitor.OnVisited := aVisitor;
aBrowser.MainFrame.VisitDom(a_Visitor);
end;
end;
Also change these
procedure _ElementCB(aBrowser: ICefBrowser; aNode: ICefDomNode; var aLevel: integer);
var
a_message: ICefProcessMessage;
begin
a_message := TCefProcessMessageRef.New(cdomdata);
a_message.ArgumentList.SetString(Ord(atNodeName), aNode.Name);
a_message.ArgumentList.SetString(Ord(atName), aNode.GetElementAttribute('name'));
a_message.ArgumentList.SetString(Ord(atId), aNode.GetElementAttribute('id'));
a_message.ArgumentList.SetString(Ord(atClass), aNode.GetElementAttribute('class'));
a_message.ArgumentList.SetInt(Ord(atLevel), aLevel);
aBrowser.SendProcessMessage(PID_BROWSER, a_message);
end;
function TCustomRenderProcessHandler.OnProcessMessageReceived(
const browser: ICefBrowser; sourceProcess: TCefProcessId;
const message: ICefProcessMessage): Boolean;
var
a_list: ICefListValue;
begin
Result := False;
if SameText(message.Name, 'ByAll') then
begin
_ProcessElements(browser, _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByName') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0),'name', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ById') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0), 'id', _ElementCB);
Result := True;
end else
if SameText(message.Name, 'ByClass') then
begin
a_list := message.ArgumentList;
_ProcessElementsByAttribute(browser, a_list.GetString(0), 'class', _ElementCB);
Result := True;
end;
end;

Problems getting the JSON data from DLL using SuperObject and OmniThreadLibrary

I'm using Delphi XE, I have the following code for my program and DLL:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, superobject,
OtlCommon, OtlCollections, OtlParallel;
type
TForm1 = class(TForm)
btnStart: TButton;
btnStop: TButton;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
FLogger : IOmniBackgroundWorker;
FPipeline: IOmniPipeline;
FLogFile: TextFile;
strict protected
procedure Async_Log(const workItem: IOmniWorkItem);
procedure Async_Files(const input, output: IOmniBlockingCollection);
procedure Async_Parse(const input: TOmniValue; var output: TOmniValue);
procedure Async_JSON(const input, output: IOmniBlockingCollection);
end;
var
Form1: TForm1;
function GetJSON(AData: PChar): ISuperObject; stdcall; external 'my.dll';
implementation
uses OtlTask, IOUtils;
{$R *.dfm}
function GetJSON_local(AData: PChar): ISuperObject;
var
a: ISuperObject;
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Text := StrPas(AData);
Result := SO();
Result.O['array'] := SA([]);
a := SO;
a.S['item1'] := sl[14];
Result.A['array'].Add(a);
a := nil;
a := SO;
a.S['item2'] := sl[15];
Result.A['array'].Add(a);
finally
sl.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
begin
// log
s := ExtractFilePath(Application.ExeName) + 'Logs';
if not TDirectory.Exists(s) then TDirectory.CreateDirectory(s);
s := Format(s+'\%s.txt', [FormatDateTime('yyyy-mm-dd_hh-nn-ss', Now)]);
AssignFile(FLogFile, s);
Rewrite(FLogFile);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseFile(FLogFile);
end;
procedure TForm1.Async_Log(const workItem: IOmniWorkItem);
begin
WriteLn(FLogFile, workItem.Data.AsString);
end;
procedure TForm1.Async_Files(const input, output: IOmniBlockingCollection);
var
f: string;
begin
while not input.IsCompleted do begin
for f in TDirectory.GetFiles(ExtractFilePath(Application.ExeName), '*.txt') do
output.TryAdd(f); // output as FileName
Sleep(1000);
end;
end;
procedure TForm1.Async_Parse(const input: TOmniValue; var output: TOmniValue);
var
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(input.AsString);
// output := GetJSON_local(PChar(sl.Text)); // output as ISuperObject --- local function
output := GetJSON(PChar(sl.Text)); // output as ISuperObject --- DLL function
finally
sl.Free;
end;
FLogger.Schedule(FLogger.CreateWorkItem(Format('%s - File processed: %s', [DateTimeToStr(Now), input.AsString])));
end;
procedure TForm1.Async_JSON(const input, output: IOmniBlockingCollection);
var
value: TOmniValue;
JSON: ISuperObject;
begin
for value in input do begin
if value.IsException then begin
FLogger.Schedule(FLogger.CreateWorkItem(value.AsException.Message));
value.AsException.Free;
end
else begin
JSON := value.AsInterface as ISuperObject;
FLogger.Schedule(FLogger.CreateWorkItem(JSON.AsString));
end;
end;
end;
//
procedure TForm1.btnStartClick(Sender: TObject);
begin
btnStart.Enabled := False;
FLogger := Parallel.BackgroundWorker.NumTasks(1).Execute(Async_Log);
FPipeline := Parallel.Pipeline
.Stage(Async_Files)
.Stage(Async_Parse)
.Stage(Async_JSON)
.Run;
end;
procedure TForm1.btnStopClick(Sender: TObject);
begin
if Assigned(FPipeline) and Assigned(FLogger) then begin
FPipeline.Input.CompleteAdding;
FPipeline := nil;
FLogger.Terminate(INFINITE);
FLogger := nil;
end;
btnStart.Enabled := True;
end;
end.
// DLL code
library my;
uses
SysUtils,
Classes, superobject;
function GetJSON(AData: PChar): ISuperObject; stdcall;
var
a: ISuperObject;
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Text := StrPas(AData);
Result := SO();
Result.O['array'] := SA([]);
a := SO;
a.S['item1'] := sl[14];
Result.A['array'].Add(a);
a := nil;
a := SO;
a.S['item2'] := sl[15];
Result.A['array'].Add(a);
finally
sl.Free;
end;
end;
exports
GetJSON;
begin
end.
When I try to run with debugging my code, after a few calls of the dll GetJSON function i get the following error:
"Project test_OTL_SO.exe raised exception class EAccessViolation with message 'Access violation at address 005A2F8A in module 'my.dll'. Write of address 00610754'."
However, this issue does not occur when I use the same local function GetJSON_local.
Could anyone suggest what am I doing wrong here?
EDIT: (solution)
I write this code for my DLL:
procedure GetJSON_(const AData: PChar; out Output: WideString); stdcall;
var
json, a: ISuperObject;
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Text := AData;
json := SO();
json.O['array'] := SA([]);
a := SO;
a.S['item1'] := sl[14];
json.A['array'].Add(a);
a := nil;
a := SO;
a.S['item2'] := sl[15];
json.A['array'].Add(a);
Output := json.AsString;
finally
sl.Free;
end;
end;
and changed the code of Async_Parse procedure:
procedure TForm1.Async_Parse(const input: TOmniValue; var output: TOmniValue);
var
sl: TStringList;
ws: WideString;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(input.AsString);
GetJSON_(PChar(sl.Text), ws); // DLL procedure
output := SO(ws); // output as ISuperObject
finally
sl.Free;
end;
FLogger.Schedule(FLogger.CreateWorkItem(Format('%s - File processed: %s', [DateTimeToStr(Now), input.AsString])));
end;
The problem is your passing of ISuperObject interfaces across a module boundary. Although interfaces can be safely used that way, the methods of the interface are not safe. Some of the methods of the interface accept, or return, strings, objects, etc. That is, types that are not safe for interop.
Some examples of methods that are not safe:
function GetEnumerator: TSuperEnumerator; // TSuperEnumerator is a class
function GetS(const path: SOString): SOString; // returns a Delphi string
function SaveTo(stream: TStream; indent: boolean = false;
escape: boolean = true): integer; overload; // TStream is a class
function AsArray: TSuperArray; // TSuperArray is a class
// etc.
You should serialize the JSON to text, and pass that text between your modules.

Resources