Data Sharing
Method Registration
See the Delphi 10 and Delphi 7 examples on GitHub.
To expose an io.Connect method that can be invoked by other interop-enabled apps, you must register the method in io.Connect and provide its implementation. The app must also implement the IGlueRequestHandler
interface to dispatch and handle the method invocation requests.
This can be simplified by using the TGlueRequestHandler
class of the io.Connect Helper Unit which takes care of implementing IGlueRequestHandler
and dispatching the request to a specific Delphi procedure.
Registering Methods
To register an Interop method, use the RegisterMethod
method of the IGlue42
interface after io.Connect has been initialized. Provide a name and a method implementation:
TMainForm = class(TForm)
...
private
G42: IGlue42;
// The "DelphiAdd" Interop method handle.
methodAdd: GlueMethod;
protected
// Implementation of the "DelphiAdd" method.
procedure GlueMethodAdd(Sender: TGlueRequestHandler;
Method: GlueMethod;
Instance: GlueInstance;
Args: GlueContextValueArray;
callback: IGlueServerMethodResultCallback;
Cookie: TCallbackCookie;
argsSA: PSafeArray);
...
procedure TMainForm.InitializeGlue;
...
G42.Start(inst);
// Register the "DelphiAdd" method and create a handler linked to the `GlueMethodAdd` procedure.
methodAdd := G42.RegisterMethod('DelphiAdd',
TGlueRequestHandler.Create(nil,GlueMethodAdd),'','',nil);
...
Method Implementation
The following example demonstrates a sample implementation of the "DelphiAdd" method. The method accepts two integers as arguments and returns their sum. If the arguments are invalid or missing, returns an error message:
procedure TMainForm.GlueMethodAdd(Sender: TGlueRequestHandler;
Method: GlueMethod;
Instance: GlueInstance;
Args: GlueContextValueArray;
callback: IGlueServerMethodResultCallback;
Cookie: TCallbackCookie;
argsSA: PSafeArray);
var
I: Integer;
a: Int64;
b: Int64;
aValid: WordBool;
bValid: WordBool;
errorMessage: WideString;
gValue: GlueValue;
gResult: GlueResult;
begin
// Validate the arguments.
aValid := False;
bValid := False;
for I := Low(Args) to High(Args) do
begin
gValue := Args[I].Value;
if (Args[I].Name = 'a') and ((gValue.GlueType = GlueValueType_Int)
or (gValue.GlueType = GlueValueType_Long)) then begin;
a := gValue.LongValue;
aValid := True;
end;
if (Args[I].Name = 'b') and ((gValue.GlueType = GlueValueType_Int)
or (gValue.GlueType = GlueValueType_Long)) then begin;
b := gValue.LongValue;
bValid := True;
end;
end;
if not aValid then
errorMessage := 'Argument a is missing or invalid.';
if not bValid then
errorMessage := 'Argument b is missing or invalid.';
ZeroMemory(@gResult, sizeof(gResult));
// Send an error message and a failed method invocations status if the arguments are invalid.
if not (aValid and bValid) then begin
gResult.Status := GlueMethodInvocationStatus_Failed;
gResult.Message := errorMessage;
callback.SendResult(gResult);
Exit;
end;
// Prepare the result as a `PSafeArray` and send it via the callback.
gResult.Values := CreateContextValues_SA(AsGlueContextValueArray([
CreateContextValue('sum', CreateValue(a+b))
]));
gResult.Status := GlueMethodInvocationStatus_Succeeded;
callback.SendResult(gResult);
SafeArrayDestroy(gResult.Values);
end;
Method Invocation
To invoke Interop methods, use BuildAndInvoke
, InvokeMethod
or InvokeMethods
. BuildAndInvoke
accepts an instance of IGlueContextBuilderCallback
for building the arguments that will be passed to the invoked Interop method, while the other two methods accept the invocation arguments directly as a PSafeArray
. If multiple apps or app instances have registered the same Interop method, you may select the instances which will service the method invocation (see Targeting). After invoking an Interop method, you must handle the returned result, if any.
Invoking Methods
The following example demonstrates invoking the previously registered "DelphiAdd" Interop method by using InvokeMethods
. The arguments passed to the method must be in a PSafeArray
:
// Prepare the arguments in a `PSafeArray`.
psaArgs := CreateContextValues_SA(AsGlueContextValueArray([
CreateContextValue('a', CreateValue(5)),
CreateContextValue('b', CreateValue(6))
]));
// Invoke the Interop method.
G42.InvokeMethods('DelphiAdd', psaArgs, nil, False,
GlueInstanceIdentity_None, MethodAddHandler, 3000, '');
SafeArrayDestroy(psaArgs);
Targeting
Targeting allows to invoke a method on one or more specific apps or app instances that have registered the same Interop method.
The following example demonstrates how to use InvokeMethods
to target various app instances:
var
psaArgs: PSafeArray;
psaInstances: PSafeArray;
instances: TGlueInstanceArray;
instance: GlueInstance;
begin
...
// Invoke a method on any of the app instances.
G42.InvokeMethods('DelphiAdd', psaArgs, nil, False,
GlueInstanceIdentity_None, MethodAddHandler, 3000, '');
// Invoke a method on all app instances.
G42.InvokeMethods('DelphiAdd', psaArgs, nil, True,
GlueInstanceIdentity_None, MethodAddHandler, 3000, '');
// Invoke a method on any instance of an app named "specific-app".
ZeroMemory(@instance, sizeof(instance));
instance.ApplicationName := 'specific-app';
SetLength(instances, 1);
instances[0] := instance;
psaInstances := CreateInstanceArray_SA(instances);
G42.InvokeMethods('DelphiAdd', psaArgs, psaInstances, False,
GlueInstanceIdentity_ApplicationName, MethodAddHandler, 1000, '');
SafeArrayDestroy(psaInstances);
...
Handling Invocation Results
To handle the results from Interop method invocations, implement the IGlueInvocationResultHandler
. This can be simplified by using the TGlueResultHandler
class of the io.Connect Helper Unit.
The following example demonstrates how to set up the invocation result handler:
TMainForm = class(TForm, IGlueContextHandler)
...
protected
MethodAddHandler: TGlueResultHandler;
procedure HandleAddResult(Sender: TGlueResultHandler;
GlueInvResults: TGlueInvocationResultArray;
Cookie: TCallbackCookie;
const correlationId: WideString);
...
procedure TMainForm.InitializeGlue;
...
// Create a method handler linked to the `HandleAddResult` procedure.
MethodAddHandler := TGlueResultHandler.Create(nil, HandleAddResult);
...
When invoking multiple instances at once, the result handler will receive the result from each invocation in an element of the GlueInvResults
array. If there aren't any available instances that match the targeting criteria, a single error result will be received by the handler.
The following example demonstrates a sample implementation of the result handler:
procedure TMainForm.HandleAddResult(Sender: TGlueResultHandler;
GlueInvResults: TGlueInvocationResultArray;
Cookie: TCallbackCookie;
const correlationId: WideString);
var
gResult: GlueResult;
data: TGlueContextValueArray;
sum: Int64;
begin
gResult := GlueInvResults[0].result;
if gResult.Status <> GlueMethodInvocationStatus_Succeeded then begin
// Invocation failed, more information may be available in `gResult.Message`.
Exit;
end;
// Translate the result values.
data := SA_AsTranslatedContextValues(gResult.Values);
if (Length(data) <> 1) or (data[0].Name <> 'sum') then begin
// Unexpected result.
Exit;
end;
sum := data[0].Value.LongValue;
end;
Discovery
Your app can discover registered Interop methods and streams and other apps (Interop servers) which offer them.
Listing All Methods & Streams
To find all registered Interop methods/streams, use use GetAllMethods
method:
var
saMethods: PSafeArray;
methods: array of GlueContext;
begin
saMethods := G42.GetAllMethods();
methods := SA_AsGlueContextArray(saMethods);
...
SafeArrayDestroy(saMethods);
end;
Listing Methods & Streams for Target
To find all Interop methods/streams registered by a single or multiple apps, use use GetMethodNamesForTarget
. The method accepts a regular expression to match against registered app names as a required argument.
The following example shows how to list all Interop methods and streams registered by apps with names starting with "client":
var
saMethods: PSafeArray;
methods: array of GlueContext;
begin
saMethods := G42.GetMethodNamesForTarget('^client.*');
methods := SA_AsGlueContextArray(saMethods);
...
SafeArrayDestroy(saMethods);
end;
Listing All Interop Servers
To get a list of the names of all apps offering Interop methods/streams, use the GetTargets
method:
var
saTargetNames: PSafeArray;
targetNames: TStrArray;
begin
saTargetNames := G42.GetTargets();
targetNames := SA_AsStringArray(saTargetNames);
...
SafeArrayDestroy(saTargetNames);
end;
Streaming
Overview
Your app can publish events that can be observed by other apps, or it can provide real-time data (e.g., market data, news alerts, notifications, etc.) to other apps by publishing an Interop stream. Your app can also receive and react to these events and data by creating an Interop stream subscription.
Apps that create and publish to Interop Streams are called "publishers", and apps that subscribe to Interop Streams are called "subscribers". An app can be both.
Publishing Stream Data
To expose a data stream to which other apps can subscribe, you must register a stream and provide implementations for handling the server side streaming events (subscription requests, added/removed subscribers). Once a stream has been successfully registered, the publishing app can start pushing data to it.
Creating Streams
To register Interop streams, use the RegisterStream
method after io.Connect has been initialized. To handle subscription requests, implement the IGlueSubscriptionHandler
interface.
The following example demonstrates registering an Interop streaming method named "DelphiStream":
TMainForm = class(TForm, IGlueSubscriptionHandler)
...
private
G42: IGlue42;
// Handles for the resulting stream and the Interop streaming method.
delphiStream: IGlueStream;
deplhiStreamMethod: GlueMethod;
protected
// Implement `IGlueSubscriptionHandler`.
function HandleSubscriptionRequest(stream: GlueMethod;
caller: GlueInstance;
requestValues: PSafeArray;
const callback: IGlueServerSubscriptionCallback): HResult; stdcall;
function HandleSubscriber(subscriberInstance: GlueInstance;
const glueStreamSubscriber: IGlueStreamSubscriber;
requestValues: PSafeArray): HResult; stdcall;
function HandleSubscriberLost(streamSubscriber: GlueInstance;
const glueStreamSubscriber: IGlueStreamSubscriber): HResult; stdcall;
...
procedure TMainForm.InitializeGlue;
...
G42.Start(inst);
// Register the Interop stream.
delphiStreamMethod := G42.RegisterStream('DelphiStream', Self, '', '', nil, delphiStream);
...
Accepting or Rejecting Subscription Requests
The implementation of the HandleSubscriptionRequest
callback method allows you to handle subscription requests. Use the Accept
and Reject
methods of the IGlueServerSubscriptionCallback
instance passed to HandleSubscriptionRequest
:
function TMainForm.HandleSubscriptionRequest(stream: GlueMethod;
caller: GlueInstance;
requestValues: PSafeArray;
const callback: IGlueServerSubscriptionCallback): HResult; stdcall;
var
reqArgs: GlueContextValueArray;
gResult: GlueResult;
begin
// Get the request arguments passed by the caller.
reqArgs := SA_AsGlueContextValueArray(requestValues);
if (Length(reqArgs) > 0) and (reqArgs[0].Name = 'rejectme') then begin
ZeroMemory(@gResult, sizeof(gResult));
gResult.Message := 'Rejected as per request.';
// Reject the subscriber.
callback.Reject(gResult);
Result := S_OK;
Exit;
end;
// Accept the subscriber.
ZeroMemory(@gResult, sizeof(gResult));
callback.Accept('', gResult);
Result := S_OK;
end;
Added or Removed Subscriptions
Handling New Subscriptions
The HandleSubscriber
callback method is invoked when a new subscriber has been accepted. As new subscribers won't automatically get the data that has been previously published to the stream, this handler can be used to privately push some initial data to the new subscriber only. Use the Push
method of the IGlueStreamSubscriber
instance to send the data to the subscriber:
function TMainForm.HandleSubscriber(subscriberInstance: GlueInstance;
const glueStreamSubscriber: IGlueStreamSubscriber;
requestValues: PSafeArray): HResult; stdcall;
var
psaData: PSafeArray;
begin
// Put the inital data into a `PSafeArray`.
psaData := CreateContextValues_SA(AsGlueContextValueArray([
CreateContextValue('initialData', CreateValue('Welcome!'))
]));
// Push the data privately to the new subscriber.
glueStreamSubscriber.Push(psaData);
SafeArrayDestroy(psaData);
Result := S_OK;
end;
Handling Removed Subscriptions
The HandleSubscriberLost
callback method is invoked when a stream subscription has been terminated. The following example demonstrates a minimal implementation:
function TMainForm.HandleSubscriberLost(streamSubscriber: GlueInstance;
const glueStreamSubscriber: IGlueStreamSubscriber): HResult; stdcall;
begin
Result := S_OK;
end;
Pushing Data
You can push data to a stream by using the Push
method of an IGlueStream
instance. Data can be sent to all subscribers on the stream, or to a group of subscribers on a specific stream branch.
The following example demonstrates how to push data to all subscribers on a stream:
var
psaData: PSafeArray;
value01: TGlueContextValue;
value02: TGlueContextValue;
begin
value01 := CreateContextValue('instrument', CreateValue('GOOG'));
value02 := CreateContextValue('price', CreateValue(1764.70));
psaData := CreateContextValues_SA(AsGlueContextValueArray([value01,value02]));
// Pushing data to the Interop stream.
delphiStream.Push(psaData,'');
SafeArrayDestroy(psaData);
end;
You can also push data directly to a subscriber by using the Push
method of an IGlueStreamSubscriber
instance (see Handling New Subscriptions).
Using Stream Branches
Using stream branches allows you to group subscribers by any criterion and target stream data at specific groups of subscribers. Branches are distinguished by their name (key). Each io.Connect stream has a default (unnamed) branch on which it accepts subscribers and to which it pushes data if no branch is specified.
To accept a subscription on a branch, specify the branch name when accepting the subscription. If the branch doesn't exist, it will be automatically created:
function TMainForm.HandleSubscriptionRequest(stream: GlueMethod;
caller: GlueInstance;
requestValues: PSafeArray;
const callback: IGlueServerSubscriptionCallback): HResult; stdcall;
var
gResult: GlueResult;
begin
// Accept the subscriber on branch "branch01".
ZeroMemory(@gResult, sizeof(gResult));
callback.Accept('branch01',gResult);
Result := S_OK;
end;
To push data to subscribers on a specific branch, specify a branch name when pushing data to the stream:
delphiStream.Push(psaData, 'branch01');
⚠️ Note that the branch must have been previously created by accepting at least one subscriber on it.
To list the names of all branches of an Interop stream, use the GetBranchKeys
method:
var
psaBranchNames: PSafeArray;
branchNames: TStrArray;
begin
psaBranchNames := delphiStream.GetBranchKeys();
branchNames := SA_AsStringArray(psaBranchNames);
...
SafeArrayDestroy(psaBranchNames);
end;
Consuming Stream Data
To receive data published on an Interop stream, an app must subscribe to it using the SubscribeStream
or SubscribeStreams
methods, and implement the IGlueStreamHandler
interface to be able to handle stream-related events. This can be simplified by using the TGlueStreamHandler
class of the io.Connect Helper Unit.
Subscribing to Streams
To subscribe to an Interop stream offered by a specific app instance, use the SubscribeStream
method. To subscribe to an Interop stream offered by one or more instances, use the SubscribeStreams
method.
The following example demonstrates how to subscribe to the "DelphiStream" Interop stream by targeting the first instance that has registered it, and how to set up the related data handler:
TMainForm = class(TForm)
...
private
G42: IGlue42;
protected
// Stream data handling.
procedure HandleDelphiStreamData(Method: GlueMethod;
data: GlueContextValueArray;
dataAsSA: PSafeArray);
...
procedure TMainForm.InitializeGlue;
var
streamMethod: GlueMethod;
...
G42.Start(inst);
// Subscribe to the stream and create a stream data handler linked to the `HandleDelphiStreamData` procedure.
G42.SubscribeStreams('DelphiStream', nil, nil, false,
GlueInstanceIdentity_None,
TGlueStreamHandler.Create(HandleDelphiStreamData), 0);
...
Handling Subscriptions Client Side
The following example demonstrates a sample procedure for handling Interop stream data:
procedure TMainForm.HandleDelphiStreamData(Method: GlueMethod;
data: GlueContextValueArray;
dataAsSA: PSafeArray);
var
I: integer;
gValue: GlueValue;
initialData: WideString;
instrument: WideString;
price: Double;
begin
initialData := '';
instrument := '';
price := -1;
for I := Low(data) to High(data) do
begin
gValue := data[I].Value;
if data[I].Name = 'initialData' then begin
initialData := gValue.StringValue;
end;
if data[I].Name = 'instrument' then begin
instrument := gValue.StringValue;
end;
if data[I].Name = 'price' then begin
if (gValue.GlueType = GlueValueType_Long)
or (gValue.GlueType = GlueValueType_int) then begin
price := gValue.LongValue;
end;
if gValue.GlueType = GlueValueType_Double then begin
price := gValue.DoubleValue;
end;
end;
end;
// Use the data.
...
end;