Blog

All Blog Posts  |  Next Post  |  Previous Post

Custom extensibility for your Miletus application

Bookmarks: 

Wednesday, March 2, 2022

TMS Software Delphi  Components
One of the new features of TMS WEB Core v1.9.7.0 was custom extensibility for Miletus applications. But what is that exactly? To put it simply, if you are after a native API we did not cover or you have custom routines you'd like to move separately you can take advantage of dynamic libraries and write your own extensions!

How it works?

As the intro suggests a custom dynamic library will be needed. We've built in support for 4 different method types that can be used in dynamic libraries to execute them directly from a Miletus application. These are the following:

procedure MyProcedureWithParameter(AData: PChar); cdecl;
procedure MyProcedureWithoutParameter; cdecl;

function MyFunctionWithParameter(AData: PChar): PChar; cdecl;
function MyFunctionWithoutParameter: PChar; cdecl;
And the corresponding promises to execute these methods are:
//Resolves with nil in case of await()
function ExecProc(ALibraryPath: string; AProc: string; AData: string): TJSPromise; 
function ExecProc(ALibraryPath: string; AProc: string): TJSPromise;

//Resolves with a string in case of await()
function ExecFunc(ALibraryPath: string; AFunc: string; AData: string): TJSPromise;
function ExecFunc(ALibraryPath: string; AFunc: string): TJSPromise;
It's also possible to assign a callback that can be used to send custom messages to the Miletus application at any time. The only requirement for this is to implement RegisterCallback:

type
  TCallback = procedure(AMessageID: Integer; AData: PChar); cdecl;

var
  MyCallback: TCallBack;

const 
  MYID = 123;

procedure RegisterCallback(AFunction: Pointer); cdecl;
begin
  @MyCallback := AFunction;
end;

procedure MyProcedure; cdecl;
begin
  //Do something and call MyCallback
  MyCallback(MYID, '{"Name": "My data", "Value": "This is my JSON formatted data"}');
end;
To capture the messages sent via the callback, use the MiletusCommunication.OnCustomMessage event.

And of course let's not forget that you can load a library by calling LoadLibrary(ALibraryPath: string) and unload it by UnloadLibrary(ALibraryPath). With all the puzzle pieces layed out, let's see how to arrange them into a nice picture.

Let's see an example

In our small example we'll take a look at how to expose the GetUserNameW Windows API to a Miletus application. Keep in mind this is for Windows only! If you want to extend your dynamic library to get the current username for macOS or Linux too, you'll need to use/implement the OS specific calls for those platforms as well.

After creating a new dynamic library project in the Delphi IDE we'll see an empty template. From here we only need to add one function that returns the result of GetUserName from the Winapi.Windows unit.

library DemoLib;

uses
  System.SysUtils,
  System.Classes,
  Winapi.Windows;

{$R *.res}

function GetCurrentUsername(AData: PChar): PChar; cdecl;
var
  i: Cardinal;
begin
  i := 256;
  GetUserName(AData, i);
  Result := AData;
end;

exports
  GetCurrentUsername;

begin
end.
The next step is to use this newly created library from our Miletus application. To keep the application simple, we'll add a TWebLabel and use that to greet the user when the application starts.

As mentioned above we can call the methods directly from the dynamic library via promises. This means we'll need to mark the TMiletusForm.OnCreate event implementation as [async], which allows us to await the results of our async calls.

[async]
procedure MiletusFormCreate(Sender: TObject);
Now we can use the TMiletusForm.OnCreate implementation to await the LoadLibrary and ExecFunc promises. LoadLibrary resolves with a Boolean value which indicates success of the library loading. ExecFunc resolves with a string that is the result from the DLL. And of course at the end we also free the library by calling UnloadLibrary. Putting this all together, we arrive at:

procedure TForm1.MiletusFormCreate(Sender: TObject);
var
  b: Boolean;
  user, s: string;
const
  LIBPATH = 'path\to\DemoLib.dll';
begin
  b := Await(Boolean, LoadLibrary(LIBPATH));
  if b then
  begin
    s := StringOfChar(' ', 256);
    user := Await(string, ExecFunc(LIBPATH, 'GetCurrentUsername', s));
    WebLabel1.Caption := 'Hello, ' + user + '!';
    UnloadLibrary(LIBPATH);
  end;
end;
There is no next step, it was simple as that. The result of this as a running application compared to our design-time view:
TMS Software Delphi  Components

We hope this gives you many ideas for your current and future Miletus applications. We are curious to see what use cases you come up with!

Get started

TMS Software Delphi  Components
Miletus technology is part of TMS WEB Core and is included. Download the latest version of the fully functional trial version of TMS WEB Core for Delphi or TMS WEB Core for Visual Studio Code and get started. 


Tunde Keller


Bookmarks: 

This blog post has received 4 comments.


1. Thursday, March 3, 2022 at 4:40:07 AM

it looks like GetCurrentUserName returns a pointer to a local ( stack based ) variable which is incorrect....

Smith Greg


2. Friday, March 4, 2022 at 9:51:42 AM

Thank you for your valuable remark! The blog post had been updated.

Tunde Keller


3. Thursday, April 14, 2022 at 10:24:56 AM

Hello. Thanky you for these very good infos. Where can I find the source code for the above example?

Ertan Baykal


4. Friday, April 15, 2022 at 9:18:24 AM

There is a demo included with full source code in the distribution under
Demo\Miletus\Library Demo

Bruno Fierens




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post