How can I access HTTPRIO.Tag previously assigned from, for instance, the HTTPRIO.OnBeforeExecute event? Is it possible?
Let's say I have something like this:
procedure TForm1.HTTPRIO1BeforeExecute(const MethodName: string; SOAPRequest: TStream);
begin
if THHPRIO(Sender).Tag = 99 then
...some code...
end;
But I don't have a Sender on any of the THTTPRIO events.
Since you are assigning events to multiple THTTPRIO objects, but don't have access to a Sender parameter in the events (bad design on THTTPRIO's author), one workaround is to use the TMethod record to manipulate the event handler's Self pointer to point at the THTTPRIO object rather than the TForm1 object, eg:
procedure TForm1.FormCreate(Sender: TObject);
var
M: TBeforeExecuteEvent;
begin
M := HTTPRIOBeforeExecute;
TMethod(M).Data := HTTPRIO1;
HTTPRIO1.OnBeforeExecute := M;
M := HTTPRIOBeforeExecute;
TMethod(M).Data := HTTPRIO2;
HTTPRIO2.OnBeforeExecute := M;
// and so on ...
end;
And then you can type-cast Self inside the events, eg:
procedure TForm1.HTTPRIOBeforeExecute(const MethodName: string; SOAPRequest: TStream);
begin
if THTTPRIO(Self).Tag = 99 then
...some code...
end;
Alternatively, you can use a standalone procedure (or class static method) with an explicit parameter for the Self pointer, eg:
procedure HTTPRIOBeforeExecute(Sender: THTTPRIO; const MethodName: string; SOAPRequest: TStream);
begin
if Sender.Tag = 99 then
...some code...
end;
procedure TForm1.FormCreate(Sender: TObject);
var
M: TBeforeExecuteEvent;
begin
TMethod(M).Data := HTTPRIO1;
TMethod(M).Code := #HTTPRIOBeforeExecute;
HTTPRIO1.OnBeforeExecute := M;
TMethod(M).Data := HTTPRIO2;
TMethod(M).Code := #HTTPRIOBeforeExecute;
HTTPRIO2.OnBeforeExecute := M;
// and so on ...
end;
While Remy's solution works, leaving the code in TForm1 leaves you at risk of accidentally referencing fields from TForm1 in TForm1.HTTPRIOBeforeExecute, and since "Self" isn't a TForm1 anymore, that would crash/corrupt memory.
I would personally use the following approach :
THTTPRIOMethodHolder = class(THTTPRIO)
public
procedure HTTPRIOBeforeExecute(const MethodName: string; SOAPRequest: TStream);
end;
procedure THTTPRIOMethodHolder.HTTPRIOBeforeExecute(const MethodName: string; SOAPRequest: TStream);
begin
if {self.}Tag = 99 then
...some code...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
HTTPRIO1.OnBeforeExecute := THTTPRIOMethodHolder(HTTPRIO1).HTTPRIOBeforeExecute;
HTTPRIO2.OnBeforeExecute := THTTPRIOMethodHolder(HTTPRIO2).HTTPRIOBeforeExecute;
end;
This should work as long as you don't add fields in THTTPRIOMethodHolder and HTTPRIOBeforeExecute is not made virtual.
Well, I actualy not leaving the code in TForm1.
I'm doing something like this:
type
THTTPRIO_EventHandlers_v2 = Class
procedure HTTPRIOBeforeExecute(const MethodName: String; SOAPRequest: TStream);
procedure HTTPRIOAfterExecute(const MethodName: string; SOAPResponse: TStream);
end;
type
THTTPRIOBeforeExecute = procedure(const MethodName: String; SPRequest: TStream) of Object;
THTTPRIOAfterExecute = procedure(const MethodName: string; SOAPResponse: TStream) of Object;
var
HTTPRIO: THTTPRIO;
procedure TForm1.Button1Click(Sender: TObject)
var
RIO_Events: THTTPRIO_EventHandlers_v2;
EvtHTTPRIOBeforeExecute: THTTPRIOBeforeExecute;
EvtHTTPRIOAfterExecute: THTTPRIOAfterExecute;
begin
HTTPRIO := THTTPRIO.Create(Application);
HTTPRIO.Tag := 99;
HTTPRIO.Converter.Encoding := 'utf-8';
RIO_Events := THTTPRIO_EventHandlers_v2.Create;
EvtHTTPRIOBeforeExecute := RIO_Events.HTTPRIOBeforeExecute;
TMethod(EvtHTTPRIOBeforeExecute).Data := HTTPRIO;
HTTPRIO.OnBeforeExecute := EvtHTTPRIOBeforeExecute;
EvtHTTPRIOAfterExecute := RIO_Events.HTTPRIOAfterExecute;
TMethod(EvtHTTPRIOAfterExecute).Data := HTTPRIO;
HTTPRIO.OnAfterExecute := EvtHTTPRIOAfterExecute;
RIO_Events.Free;
end;
And on the, for instance, HTTPRIOBeforeExecute procedure, I'm doing this:
procedure THTTPRIO_EventHandlers_v2.HTTPRIOBeforeExecute(const MethodName: String; SOAPRequest: TStream);
var
HttpRioTag: Integer;
begin
HttpRioTag := THTTPRIO(Self).Tag;
if HttpRioTag = 99 then
...some code...
end;
This is working as expected.
Am I doing it the right way?
Thanks
Related
I have a class that has a few event properties, and another class that contains the event handlers. At compile-time I don't know the structure of either class, at run-time I only get to know the match between event property and event handler using their names. Using RTTI, I'd like to assign the event handlers to the respective event properties, how can I do so?
I currently have something like this:
type
TMyEvent = reference to procedure(const AInput: TArray<string>; out AOutput: TArray<string>);
TMyBeforeEvent = reference to procedure(const AInput: TArray<string>; out AOutput: TArray<string>; out ACanContinue: boolean);
TMyClass = class
private
FOnBeforeEvent: TMyBeforeEvent;
FOnEvent: TMyEvent;
public
property OnBeforeEvent: TMyBeforeEvent read FOnBeforeEvent write FOnBeforeEvent;
property OnEvent: TMyEvent read FOnEvent write FOnEvent;
end;
TMyEventHandler = class
public
procedure DoBeforeEvent(const AInput: TArray<string>; out AOutput: TArray<string>; out ACanContinue: boolean);
procedure DoEvent(const AInput: TArray<string>; out AOutput: TArray<string>);
end;
procedure AssignEvent;
implementation
uses
Vcl.Dialogs, System.RTTI;
{ TMyEventHandler }
procedure TMyEventHandler.DoBeforeEvent(const AInput: TArray<string>;
out AOutput: TArray<string>; out ACanContinue: boolean);
begin
// do something...
end;
procedure TMyEventHandler.DoEvent(const AInput: TArray<string>;
out AOutput: TArray<string>);
begin
// do something...
end;
procedure AssignEvent;
var
LObj: TMyClass;
LEventHandlerObj: TMyEventHandler;
LContextObj, LContextHandler: TRttiContext;
LTypeObj, LTypeHandler: TRttiType;
LEventProp: TRttiProperty;
LMethod: TRttiMethod;
LNewEvent: TValue;
begin
LObj := TMyClass.Create;
LEventHandlerObj := TMyEventHandler.Create;
LContextObj := TRttiContext.Create;
LTypeObj := LContextObj.GetType(LObj.ClassType);
LEventProp := LTypeObj.GetProperty('OnBeforeEvent');
LContextHandler := TRttiContext.Create;
LTypeHandler := LContextHandler.GetType(LEventHandlerObj.ClassType);
LMethod := LTypeHandler.GetMethod('DoBeforeEvent');
LEventProp.SetValue(LObj, LNewEvent {--> what value should LNewEvent have?});
end;
A reference to procedure(...) is an anonymous method type. Under the hood, it is implemented as an interfaced object (ie, a class that implements the IInterface interface) with an Invoke() method that matches the parameters of the procedure.
So, can't use TMyEventHandler.DoBeforeEvent() directly with TMyClass.OnBeforeEvent, for instance, since TMyEventHandler does not match that criteria. But, you can wrap the call to DoBeforeEvent() inside of an actual anonymous procedure, eg:
procedure AssignEvent;
var
LObj: TMyClass;
LEventHandlerObj: TMyEventHandler;
LEventHandler: TMyBeforeEvent;
LContextObj: TRttiContext;
LMethod: TRttiMethod;
LEventProp: TRttiProperty;
begin
LObj := TMyClass.Create;
LEventHandlerObj := TMyEventHandler.Create;
LContextObj := TRttiContext.Create;
LMethod := LContextObj.GetType(LEventHandlerObj.ClassType).GetMethod('DoBeforeEvent');
LEventHandler := procedure(const AInput: TArray<string>; out AOutput: TArray<string>; out ACanContinue: boolean);
begin
// Note: I don't know if/how TRttiMethod.Invoke() can handle
// 'out' parameters, so this MAY require further tweaking...
LMethod.Invoke(LEventHandlerObj, [AInput, AOutput, ACanContinue]);
end;
LEventProp := LContextObj.GetType(LObj.ClassType).GetProperty('OnBeforeEvent');
LEventProp.SetValue(LObj, TValue.From(LEventHandler));
end;
Same with using TMyEventHandler.DoEvent() with TMyClass.OnEvent.
Alternatively, if you change the reference to procedure(...) into procedure(...) of object instead, you can then use the TMethod record to assign TMyEventHandler.DoBeforeEvent() directly to TMyClass.OnBeforeEvent, eg:
procedure AssignEvent;
var
LObj: TMyClass;
LEventHandlerObj: TMyEventHandler;
LEventHandler: TMyBeforeEvent;
LContextObj: TRttiContext;
LEventProp: TRttiProperty;
LMethod: TRttiMethod;
begin
LObj := TMyClass.Create;
LEventHandlerObj := TMyEventHandler.Create;
LContextObj := TRttiContext.Create;
LEventProp := LContextObj.GetType(LObj.ClassType).GetProperty('OnBeforeEvent');
LMethod := LContextObj.GetType(LEventHandlerObj.ClassType).GetMethod('DoBeforeEvent');
with TMethod(LEventHandler) do
begin
Code := LMethod.CodeAddress;
Data := LEventHandlerObj;
end;
LEventProp.SetValue(LObj, TValue.From(LEventHandler));
end;
Same with using TMyEventHandler.DoEvent() with TMyClass.OnEvent.
Good question!
I believe I have found a sound approach.
To illustrate it, create a new VCL application with a form (Form1) and a single button (Button1) on it. Give the form an OnClick handler:
procedure TForm1.FormClick(Sender: TObject);
begin
ShowMessage(Self.Caption);
end;
We will attempt to assign this handler to the button's OnClick property using only RTTI and property and method names (as strings).
The key thing to remember is that a method pointer is a pair (code pointer, object pointer), specifically, a TMethod record:
procedure TForm1.FormCreate(Sender: TObject);
var
C: TRttiContext;
b, f: TRttiType;
m: TRttiMethod;
bp: TRttiProperty;
handler: TMethod;
begin
C := TRttiContext.Create;
f := C.GetType(TForm1);
m := f.GetMethod('FormClick');
b := C.GetType(TButton);
bp := b.GetProperty('OnClick');
handler.Code := m.CodeAddress;
handler.Data := Form1;
bp.SetValue(Button1, TValue.From<TNotifyEvent>(TNotifyEvent(handler)));
end;
I'm wondering if this is possible. I want to get the TypeInfo, passing the type's name as a string.
Something like this:
type
TSomeValues = record
ValueOne: Integer;
ValueTwo: string;
end;
function ReturnTypeInfo(aTypeName: string): TypeInfo;
begin
// that's is the issue
end;
procedure Button1Click(Sender: TObject);
var
_TypeInfo: TypeInfo;
begin
_TypeInfo := ReturnTypeInfo('TSomeValues');
end;
Use the TRttiContext.FindType() method and TRttiType.Handle property, eg:
uses
..., System.TypInfo, System.Rtti;
function ReturnTypeInfo(aTypeName: string): PTypeInfo;
var
Ctx: TRttiContext;
Typ: TRttiType;
begin
Typ := Ctx.FindType(aTypeName);
if Typ <> nil then
Result := Typ.Handle
else
Result := nil;
end;
...
type
TSomeValues = record
ValueOne: Integer;
ValueTwo: string;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
_TypeInfo: PTypeInfo;
begin
_TypeInfo := ReturnTypeInfo('Unit1.TSomeValues');
...
end;
I have 2 years old project which I am rebooting back to life. I have debug window which shows different information, and one part of them is that it serializes various google protobuf objects and shows them.
Here is how typical protobuf class looks like:
// Generated by the protocol buffer compiler. DO NOT EDIT!
// Source: message.proto
unit Poker.Protobufs.Objects.PingParams;
interface
uses
System.SysUtils,
{$IFNDEF FPC} System.Generics.Collections {$ELSE} Contnrs {$ENDIF},
pbOutput, Poker.Protobufs.Objects.Base, Poker.Protobufs.Reader, Poker.Types;
type
TPB_PingParams = class(TProtobufBaseObject)
private
const
kUptimeFieldNumber = 1;
var
FUptime: UInt32;
FHasBits: UINT32;
procedure set_has_Uptime;
procedure clear_has_Uptime;
procedure SetUptime(const AValue: UInt32);
public
constructor Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE); overload;
destructor Destroy; override;
procedure LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer); override;
procedure MergeFrom(const AFrom: TPB_PingParams);
procedure Clear;
function IsInitialized: Boolean; override;
// required uint32 Uptime = 1;
function has_Uptime: Boolean;
procedure clear_Uptime;
property Uptime: UInt32 read FUptime write SetUptime;
end;
TPB_PingParamsList = class(TObjectList<TPB_PingParams>)
procedure Assign(const APB_PingParamsList: TList<TPB_PingParams>);
end;
implementation
uses
pbPublic;
constructor TPB_PingParams.Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE);
begin
inherited Create(ALightweight);
MergeFrom(AFrom);
end;
destructor TPB_PingParams.Destroy;
begin
inherited;
end;
procedure TPB_PingParams.LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer);
var
tag, field_number, wire_type, endpos: Integer;
begin
endpos := AProtobufReader.getPos + ASize;
while (AProtobufReader.getPos < endpos) and
(AProtobufReader.GetNext(tag, wire_type, field_number)) do
case field_number of
kUptimeFieldNumber: begin
Assert(wire_type = WIRETYPE_VARINT);
FUptime := AProtobufReader.readUInt32;
set_has_Uptime;
end;
else
AProtobufReader.skipField(tag);
end;
end;
procedure TPB_PingParams.MergeFrom(const AFrom: TPB_PingParams);
begin
if AFrom.has_Uptime then
SetUptime(AFrom.Uptime);
end;
function TPB_PingParams.IsInitialized: Boolean;
begin
if (FHasBits and $1) <> $1 then
Exit(FALSE);
Exit(TRUE);
end;
procedure TPB_PingParams.clear_Uptime;
begin
FUptime := 0;
clear_has_Uptime;
end;
function TPB_PingParams.has_Uptime: Boolean;
begin
result := (FHasBits and 1) > 0;
end;
procedure TPB_PingParams.set_has_Uptime;
begin
FHasBits := FHasBits or 1;
end;
procedure TPB_PingParams.clear_has_Uptime;
begin
FHasBits := FHasBits and not 1;
end;
procedure TPB_PingParams.SetUptime(const AValue: UInt32);
begin
if not Lightweight then
Assert(not has_Uptime);
FUptime := AValue;
if not Lightweight then
ProtobufOutput.writeUInt32(kUptimeFieldNumber, AValue);
set_has_Uptime;
end;
procedure TPB_PingParams.Clear;
begin
if FHasBits = 0 then
Exit;
clear_Uptime;
end;
procedure TPB_PingParamsList.Assign(const APB_PingParamsList: TList<TPB_PingParams>);
var
pbobj: TPB_PingParams;
begin
Clear;
for pbobj in APB_PingParamsList do
Add(TPB_PingParams.Create(pbobj, TRUE));
end;
end.
And my serialization function:
function SerializeObject(const AObject: TObject): String;
var
t: TRttiType;
p: TRttiProperty;
properties: TArray<TRttiProperty>;
method: TRttiMethod;
begin
result := '';
if not Assigned(AObject) then
Exit;
t := TRttiContext.Create.GetType(AObject.ClassType);
properties := t.GetProperties;
for p in properties do
begin
method := t.GetMethod(Format('has_%s', [p.Name]));
if (Assigned(method)) and
(method.Invoke(AObject, []).AsBoolean) then
result := result + Format('%s: %s; ', [p.Name, ValueToStr(p, p.GetValue(AObject))]);
end;
end;
It is specifically designed to serialize fields that begin with has_ in protobuf objects. Now, I didn't change anything in the code over last 2 years, and this was working before. But now it doesn't. Line properties = t.GetProperties returns empty array for my protobuf classes.
My guess is that I had some globally defined compiler directive which allowed me to serialize public methods in the class. But I cannot figure out which one.
If I put {$M+} in front of my protobuf classes, and move methods to published, it works (kinda). But this worked before just like it is shown in the sources, without any {$M+} directives or similar. So I'm curious what I miss.
Compiler is same as before, XE2.
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.
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.