Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
One of the major new features of Delphi was the support for App Tethering, which basically means the option to interact between two applications that can exist on different devices (but must be connected on the same subnet, so not over the internet and as of Delphi XE7 also using Bluetooth).
With App Tethering we can discover other Delphi applications that support app tethering running on the same device or on other connected devices on the same subnet (or on the same machine or device).
Once we are connected to another device, we can share data between applications (standard data types and streams) and execute actions that are shared by an application using App Tethering. We can use passwords to ensure that not everyone can execute these actions using App Tethering (although it still only works on the same subnet, we should ensure that not everyone can access the data or execute actions without permission).
App Tethering Logging
One of the way in which I've used App Tethering myself is in order to allow my (mobile) apps to send log messages using CodeSite.
You can download the full source code here.
Start a new Delphi VCL application, save it as CodeSiteAppTetheringManager. Optionally, we can place a TMemo on the form, to hold the last message that was sent to the CodeSite App Tethering Manager.
Now, place a TTetheringManager componenton a form, with the following properties:
object TetheringManager1: TTetheringManager Password = 'geheim' Text = 'CodeSiteManager' AllowedAdapters = 'Network' endThis means that we'll require an AppTethering connecting app over the network, using password "geheim" to connect to our CodeSiteManager
Also place a TTetheringAppProfile component on the form, with the following properties set:
object TetheringAppProfile1: TTetheringAppProfile Manager = TetheringManager1 Text = 'CodeSiteProfile' Group = 'eBob42' OnResourceReceived = TetheringAppProfile1ResourceReceived endThis means that the CodeSiteProfile belongs to the group "eBob42", is connected to the TetheringManager, and response to the OnResourceReceived event.
In the event handler, we can handle the incoming message as follows:
implementation uses CodeSiteLogging; {$R *.dfm} procedure TForm1.TetheringAppProfile1ResourceReceived( const Sender: TObject; const AResource: TRemoteResource); begin Memo1.Text := DateTimeToStr(Now) + #13#10 + AResource.Value.AsString; CodeSite.Send(AResource.Value.AsString) end;This means that any resource message received will be sent to CodeSite (and also shown in the Memo field, including the current date and time of the message).
If we run this CodeSiteAppTetheringManager application, we can allow any application that runs on the network (including mobile devices that contain apps that we need to test and debug), to send log messages to this CodeSiteAppTetheringManager, so we can see these messages show up in the CodeSite LiveViewer (or Logfile viewer).
To create a client that can connect to the CodeSiteAppTetheringManager, we need a data module (called DataModErrorTethering) with three components: a TTetheringManager, a TTetheringAppProfile and (for VCL clients only) a TApplicationEvents component:
The individual components are configured as follows:
object DataModErrorTethering: TDataModErrorTethering object TetheringManager1: TTetheringManager Password = 'geheim' Text = 'TetheringManager1' AllowedAdapters = 'Network' end object TetheringAppProfile1: TTetheringAppProfile Manager = TetheringManager1 Text = 'TetheringAppProfile1' Group = 'eBob42' end object ApplicationEvents1: TApplicationEvents end endThe ApplicationEvents component is the one that can respond to unhandled exceptions (the ones that I want to send to CodeSite), by responding to the OnException event handler, implemented as follows:
procedure TDataModErrorTethering.ApplicationEvents1Exception( Sender: TObject; E: Exception); var i: Integer; begin for i:=0 to TetheringManager1.RemoteProfiles.Count-1 do TetheringAppProfile1.SendString(TetheringManager1.RemoteProfiles[i], E.Classname, E.Message); end;This basically means that it looks through all connected remote profiles, and sends the exception classname and messages to all connected CodeSiteAppTetheringManagers that it could connect to. Note that for Mobile Clients, you need to hook the OnException event handler in a slightly different way, which is demonstrated in my Delphi Mobile Development courseware manual.
In order to make all this work, however, we do need to implement the necessary task to connect the data module to any (or all) CodeSiteAppTetheringManagers.
A procedure RegisterTethering is added to the public section of the data module, so the application that includes this data module can call the RegisterTethering method (for example in the OnCreate event) in order to register it. Inside this method, we should make sure to Unpair any existing managers that may exist, and then start the discovery process of AppTethering Managers.
procedure TDataModErrorTethering.RegisterTethering; var i: Integer; begin for i:=TetheringManager1.PairedManagers.Count-1 downto 0 do TetheringManager1.UnPairManager(TetheringManager1.PairedManagers[i]); TetheringManager1.DiscoverManagers; end;We also need to implement three event handlers of the TetheringManager: OnEndManagerDiscovery, OnEndProfilesDiscovery, and OnRequestManagerPassword.
procedure TDataModErrorTethering.TetheringManager1EndManagersDiscovery( const Sender: TObject; const ARemoteManagers: TTetheringManagerInfoList); var i: Integer; begin for i:=0 to ARemoteManagers.Count-1 do if ARemoteManagers[i].ManagerText = 'CodeSiteManager' then TetheringManager1.PairManager(ARemoteManagers[I]) end;In the OnEndManagerDiscovery, we verify that the TetheringManager has the text CodeSiteManager (we only want to connect to CodeSite AppTethering Managers at this time), and if so, we'll pair the managers.
In the OnEndProfilesDiscovery, we'll connect our TetheringProfile to the remote profiles from the TetheringManagers, so we can send messages from our profile to the others.
procedure TDataModErrorTethering.TetheringManager1EndProfilesDiscovery( const Sender: TObject; const ARemoteProfiles: TTetheringProfileInfoList); var i: Integer; begin for i:=0 to TetheringManager1.RemoteProfiles.Count-1 do TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[i]) end;And finally, in the OnRequestManagerPassword, we need to pass the password that the remote CodeSiteAppTetheringManager expects from us: "geheim".
procedure TDataModErrorTethering.TetheringManager1RequestManagerPassword( const Sender: TObject; const ARemoteIdentifier: string; var Password: string); begin Password := 'geheim'; end;This will ensure that the calling demo application (with this data module included) can actually connect to the CodeSiteAppTatheringManager.
Now, when we run the CodeSiteAppTetheringClient application, any unhandled exception will be sent to any running CodeSiteAppTetheringManager applications that were found and connected when the client application was started. Although this is "nice" for a Windows client application, it is in fact very helpful when writing and testing / debugging mobile applications, since there is no CodeSite for Android or iOS yet, but this tethering technique will allow mobile devices to send CodeSite log messages to the manager which will forward them to the actual CodeSite Dispatcher and LiveViewer or LogViewer.
We can even extend the data module with a protected method called CodeSiteSend taking a Msg parameter of type String, to mimic the call to CodeSite.Send itself. The implementation would be similar to the exception handler, sending the CodeSite message to the CodeSite AppTethering Manager as follows:
procedure TDataModErrorTethering.CodeSiteSend(Msg: String); var i: Integer; begin for i:=0 to TetheringManager1.RemoteProfiles.Count-1 do TetheringAppProfile1.SendString(TetheringManager1.RemoteProfiles[i], 'CodeSite', Msg); end;Note that this should be a private method, because the easiest way to make it available to developers is to create a class called CodeSite with a class method called Send, as follows:
type CodeSite = class public class procedure Send(Msg: String); end;With the implementation of the CodeSite.Send method as follows:
class procedure CodeSite.Send(Msg: String); begin if Assigned(DataModErrorTethering) then DataModErrorTethering.CodeSiteSend(Msg); end;Using the CodeSiteAppTetheringDataModule unit, we can call CodeSite.Send from any type of application, including mobile applications, and if they are able to connect to the CodeSiteAppTetheringManager on the local WiFi, we can send CodeSite messages from the client, and see them in the LIve Viewer or Log Viewers.