FireMonkey Host (Delphi)

A Hydra host is an application that provides the ability to load (host) and use the exposed functionality of a plugin. Host applications are able to use all plugins supported by Hydra (with the exception of RemObjects SDK plugins).

Starting from version 4, Hydra receives support for FireMonkey plugins and hosts, which allows you to easily mix VCL, WinForms, WPF, Silverlight and FireMonkey in your applications.

FireMonkey is a cross-platform GPU powered GUI framework from Embarcadero. It is similar in concept to .NET’s WPF and builds on the former VGScene/DXScene components to let you build applications with both 2D and 3D elements, transforms and animations.

FireMonkey is not an evolution of the VCL, nor does it replace it. The VCL is still included in Delphi, with FireMonkey being an alternative option for UI development. But while the VCL remains for Windows only, FireMonkey will allow you to create GUI applications for both Windows (32 and 64 bit) and Mac OS X.

Since FireMonkey is a cross-platform framework, its not bound to Windows and it doesn't directly use things like the Windows message loop or handles. Being a GPU powered framework, FireMonkey uses GDI+, Direct2D/Direct3D or OpenGL to render GUI, meaning that by default, FireMonkey and the VCL will not get along well, and you won't be able to add a FireMonkey control to a VCL application or vice versa, or even share FireMonkey and VCL forms within the same application.

This article will describe how to create a new FireMonkey host application, and talk about what features it provides and how they can be used.

Getting Started

A Hydra host application is basically a common application. Our wizards can create this application for every supported platform, so you can have a WinForms, VCL or FireMonkey HD application.

By default, there aren't a lot of changes in these applications done by the wizard, the main one is that it adds a module manager component which allows you to interact with plugins. However, for some host projects, it adds additional items, which will be described in the "Review the resulting project" section.

All this allows you to easily convert your existing project into a Hydra host project.

Let's create and set up a Hydra host project.

Working with the Wizard

Creating a Hydra host is a very simple process that is entirely automated by our IDE wizards.

The following screenshots will show you all the steps required to create a new Hydra host application.

To start the wizard that will help you create a new host, go to the menu File -> New -> Other and select the RemObjects Hydra category and select the Host Application option:

The New Hydra Host Project wizard will start and you will be presented with a Welcome screen.

The next page allows you to set a destination folder, a project name and the platform for the host:

The wizard allows you to choose between FireMonkey and VCL frameworks; to create a FireMonkey host you will need to select the FMX option.

After everything is set, the wizard will show the summary page:

The wizard will then create a new project that contains the host application and show a dialog that allows you to choose the runtime package settings:

I will describe how to work with the runtime package later in this article.

After finishing its work, the wizard will create a new application that is ready to work with Hydra plugin modules.

Review the resulting project

Let's review the resulting project:

  • Project file - The wizard will create a *.dpr file and, if needed, additional project files (like *.dproj). The only line Hydra added in the project file is:
FMX.Types.GlobalUseDirect2D := false;

Please do not remove this line, it declares that the host application will use the GDI+ rendering engine, and currently only this engine is supported in Hydra applications.

  • Main file - This file contains the main form definition:
uses
  {FMX:} System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
  {Hydra:} Hydra.Core.Interfaces, Hydra.Core.PluginDescriptors, Hydra.Core.BaseModuleManager, 
  Hydra.Core.SimpleModuleManager, Hydra.FMX.Interfaces, Hydra.FMX.ModuleManager, Hydra.FMX.HostWrapper;

type
  TMainForm = class(TForm)
    ModuleManager: THYFMXModuleManager;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

As you can see, it pretty much looks like a regular FireMonkey HD application. The wizard added a unit into the uses section, which contains the definitions of most common items, like interfaces or enums.

It also added the definition for the module manager; I will describe how to work with the module manager later in this article.

Using the host

By now you will have a complete project that, with a little bit of additional work, can be used to work with plugins. The host application can load any plugin module, regardless of which platform it was developed with, allowing you to mix Delphi and .NET functionality in the same application (more on this later in this article).

As I've already said, the host application is a common FireMonkey application, the main form generated by the wizard is inherited from the standard and provides exactly the same design surface, so you can work with the host application like you do with any regular FireMonkey application.

Below I will describe the most common way to use the host, but first, I want to tell you about custom interfaces, one of the major parts of the Hydra framework. Custom interfaces are user-defined interfaces that can be used in a cross-platform environment (they can, for example, be shared between a FireMonkey host and a .NET plugin) to receive access to data or to call a host or plugin method. When I talk about things like "accessing host methods", I refer to the custom interfaces, but since this is a large topic, it cannot be covered in depth in this article, so if you feel like you need to use them, please take a look at these two articles:

For host applications, the THYFMXModuleManager component is a key component that allows you to perform the task of managing plugin modules. So, let's start with a short description of the module manager members.

Module Manager Members

As mentioned, the module manager is the key component of any Hydra host application, and it allows you to do the following:

Methods:

  • GetModuleFileNames - Uses the specified search path to populate a list with module filenames and returns the number of modules found.
  • CreateInstance - Creates an instance of the plugin with the specified name. CreateInstance returns a reference to an IInterface, however, you can optionally use the CreateVisualPlugin or CreateNonVisualPlugin methods to create a specific plugin.
  • ReleaseInstance - Releases the instance that was created by the CreateInstance method, this is an equivalent to setting the instance to nil. You need to release all the references to a plugin instance before you can unload a module or shut down your application.
  • LoadModule - Loads the module with the specified file name, and optionally either creates a new AppDomain for .NET plugins or uses a global one. This method will automatically define a plugin module type and will call the module manager methods to load the specific module, however, you can do this manually by using methods such as LoadUnmanagedModule, LoadFiremonkeyModule etc.
  • LoadModule - Loads the module with the specified file name, and optionally either creates a new AppDomain for .NET plugins or uses a global one. This method will automatically define a plugin module type and will call the module manager methods to load the specific module, however, you can do this manually by using methods such as LoadUnmanagedModule, LoadFiremonkeyModule etc.
  • LoadModules - Provides the ability to load a set of modules with different methods like by search path, from a list of file names, etc.
  • UnloadModule - Unloads a plugin module by using the reference to the module, index or file name. The UnloadModules method also allows you to unload all modules.
  • FindModule - Searches for a loaded module by the specified file name, returns nil if none is found. The ModuleByFileName method performs the same search, but will throw an exception if the module wasn't found.
  • FindPluginDescriptor - Searches for a plugin descriptor by the specified name and returns nil if none is found. The PluginDescriptorByName method performs the same search, but will throw an exception if the descriptor wasn't found.

Properties:

  • PluginDescriptors - Returns a reference to a plugin descriptor by specified index.
  • PluginDescriptorCount - Returns the number of plugin descriptors in all modules.
  • Modules - Returns a reference to a loaded module by specified index.
  • ModuleCount - Returns the number of loaded modules.
  • ModulesToLoad and AutoLoad - The first is a string list that allows you to define a list of modules that can be loaded, the second defines whether they will be loaded automatically.
  • CustomImplementer - Allows you to set a reference to an IInterface that will receive calls from plugins.
  • ResolveInterfacesToOwner - Defines whether the host will resolve interfaces to its owner.

This set of events reacts to various load/unload events:

Events that react on instance creation:

The following events are used only for Silverlight plugins (you can read more about them in this article:

Modules and Descriptors

As you can see, the module manager provides a couple of methods that allow you to access the modules and descriptors. So let's find out more about this.

Every plugin that was loaded by the module manager is represented by a special class that describes this module. All modules are descendants of the THYCrossPlatformModule class, which allows you to get access to module data.

THYCrossPlatformModule allows you to access to following data:

  • FileName - File name of the loaded module.
  • ModuleController - Holds a reference to an instance of the plugin's module controller. (IHYCrossPlatformModuleController)
  • Plugins - Allows you to access a plugin descriptor specified by index; unlike the module manager property, it allows you to get descriptors only for the specified module.
  • PluginsCount - Returns the number of plugin descriptors in this module.

Plugin descriptors are used to describe a plugin that is stored in the module. In most cases you will use the THYPluginDescriptor class to deal with the descriptors.

THYPluginDescriptor provides access to a set of data that describes the plugin:

  • PluginType - Defines the type of the module (visual, non visual, etc.).
  • Name - Holds the name of the plugin. You can use it to create an instance of the plugin.
  • MajorVersion and MinorVersion - Holds the user-specified version of the plugin.
  • Description - Holds a string that allows you to read the plugin description.
  • UserData - String that contains custom data.
  • RequiredPrivilege - String that defines privileges required to create this plugin.
  • CheckPluginType - Method that allows you to check whether the plugin is derived from a specified type.
  • CheckPluginInterface - Method that allows you to check if the plugin implements a specified interface.

Please note that MajorVersion, MinorVersion, Description and UserData are optional fields, they can be defined on the plugin side if you need such info, by default they are empty.

Custom Interfaces

I won't dig deep into details of custom interface usage (if you need more information, please refer to this article Passing interfaces between Host and Plugins), but I will describe how the host deals with them.

The THYFMXModuleManager itself implements a couple of base hosting interfaces to be able to deal with plugins, and provides a couple of ways for you to expose your own methods or properties to plugins.

First, let's review how the host reacts when the plugin tries to get access to it:

  1. First it checks if it already implements the requested interfaces, for general interfaces like IHYCrossPlatformHost.
  2. If the plugin requests a different interface and ResolveInterfacesToOwner is enabled, it will request its owner to resolve the interface.
  3. If the interface is still not resolved and CustomImplementer is set, it will forward the call to it.
  4. Otherwise it will return an error to the plugin.

By default, ResolveInterfacesToOwner is enabled, so the most common way to implement your custom interfaces is to use an owner object (in most cases this is TForm) and declare interfaces there, for example:

TMainForm = class(TForm, IMyCustomInterface)
  ModuleManager: THYFMXModuleManager;
[..]
private
  procedure MyInterfaceMethod; safecall;
[..]

In some cases (like when you don't have a parent form), you may use a custom implementor, which is a bit more complex. The module manager requires that the CustomImplementor object implements IInterface, so we can call a QueryInterface method on this object, but if you need this object to also implement an interface, then it must also implement IDispatch. For this purpose, you can use the THYFakeIDispatch class as a base class for an object that implements custom interfaces, for example:

uses
  Hydra.Core.FakeIDispatch;

[..]
TMyImplementor = class (THYFakeIDispatch, IMyCustomInterface)
private
  procedure MyInterfaceMethod; safecall;
end;
[..]

  ModuleManager.ResolveInterfacesToOwner := false;
  ModuleManager.CustomImplementer := TMyImplementor.Create;

Usage Example

While plugins do not require any special setup, you need to perform a couple of steps in the host application to be able to load your plugins, for example:

  TMainForm = class(TForm)
  [..]
  private
    { Private declarations }
    fVisualPlugin: IHYFMXVisualPlugin;
  end;

[..]
procedure TMainForm.FormCreate(Sender: TObject);
begin
  ModuleManager.LoadModule('ModuleName.dll');
  ModuleManager.CreateVisualPlugin('VisualPluginName', fVisualPlugin, Panel1);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  ModuleManager.ReleaseInstance(fVisualPlugin);
  ModuleManager.UnloadModules;
end;

Let's review this example step-by-step:

  • fVisualPlugin: IHYFMXVisualPlugin; - This is a reference to an instance of the plugin. You can use it to perform any specific actions with the plugin. You need to keep this reference alive until you use the plugin and release it when it is no longer needed.
  • ModuleManager.LoadModule('ModuleName.dll'); - This method allows you to load a plugin module with a specified path. The module manager will automatically detect the type of the module and will use the appropriate method. It also provides a couple of methods for module loading, such as loading from a list of file names or loading using search pattern.
  • ModuleManager.CreateVisualPlugin('VisualPluginName', fVisualPlugin, Panel1); - This method will create an instance of the plugin with the specified name and assign this instance to the fVisualPlugin variable we defined above. This method can also show plugin content in a container that, in this example, is represented by Panel1. Please note that this method is used only for creating an instance of a visual plugin, for non-visual plugins you can use ModuleManager.CreateNonVisualPlugin.
  • ModuleManager.ReleaseInstance(fVisualPlugin); - Releases an instance of a plugin. Please note that you must release the instance before the plugin module is unloaded.
  • ModuleManager.UnloadModules; - This method unloads all modules that were previously loaded by the module manager. The module manager also provides methods for unloading a specific plugin by its file name or index. Please note that there is no need to call UnloadModules on application end since module manager will do this operation on its own destruction.

This is it, with just a few lines of code the host is now able to load, create and show your visual plugin.

Runtime Packages

Hydra provides an easy way to enable runtime packages support for your project by using the Project Package Settings dialog, as described here.