VCL 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.

VCL is a visual component-based framework, and one of the most used types of host applications. It also is is our oldest supported framework, so there are some features that are available for this framework only.

In this article, I will describe how to create a new VCL 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 all supported platforms, so you can have a WinForms, VCL, or FireMonkey HD application.

By default there aren't many changes done by the wizard, the main one being that it adds a module manager component that 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 the Hydra host project.

Now let's create and set up a Hydra host project.

Working with the Wizard

Creating a Hydra host is a very simple process which 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 a platform for the host:

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

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

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

I will describe how to work with runtime packages 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). This is a regular VCL project file, the wizard doesn't add anything specific for this one.
  • Main file - This file contains the main form definition:
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, uHYModuleManager, uHYIntf;

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

As you can see, this pretty much looks like a regular VCL application. The wizard added some units into the uses section, which contain definitions for the most common items, like interfaces or enums.

It also added a 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 what platform it was developed with, allowing you to mix Delphi and .NET functionality in the same application. I will describe how to do this later in this article.

The host application is a common VCL application, the main form generated by the wizard is inherited from the standard TForm and provides exactly the same design surface, so you can work with the host application like you do with any regular VCL application.

Below we will look at the most common way to use the host, but first, let's talk about custom interfaces. One of the major parts of the Hydra framework are custom interfaces. Custom interfaces are user-defined interfaces that can be used in the 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 host or plugin methods. When we talk about things like "accessing host methods" we refer to the custom interfaces, but since this is a large topic, we can't cover it in this article, so if you feel like you need to use them, please take a look at these two articles:

There are also two VCL-only topics that we won't cover in this article:

Please follow the links to learn more about these features.

Module Manager Members

For host applications, the THYModuleManager 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:

Methods:

  • GetModuleFileNames - Uses the specified search path to populate a list with module filenames and returns the number of modules that were found.
  • CreateInstance - Creates an instance of a plugin with a specified name. CreateInstance returns a reference to an IInterface, however, you can also use the CreateVisualPlugin or CreateNonVisualPlugi methods to create a specific plugin.
  • ReleaseInstance - Releases an instance that was created by the CreateInstance method. This is 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 a module with a specified file name, and can optionally create a new AppDomain for .NET plugins or use a global one. Returns an index of the new module. This method will automatically define the plugin module type and will call on the module manager methods to load the specific module. You can also 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 list of file names, etc.
  • UnloadModule - Unloads a plugin module by using a reference to the module, index or file name. The UnloadModules method allows you to unload all modules.
  • FindModule - Searches for a loaded module by a specified file name, returns nil if nothing is found. ModuleByFileName does the same search, but will throw an exception if the module wasn't found.
  • FindPluginDescriptor - Searches for a plugin descriptor by the specified name, returns nil if nothing is found. PluginDescriptorByName does 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 after the module manager is created.

Just a description, we will have a detailed look at these two later:

  • 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.

Events:

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

  • OnBeforeLoadModule
  • OnAfterLoadModule
  • OnBeforeUnloadModule
  • OnAfterUnloadModule
  • OnLoadModuleError

Events that react on instance creation:

  • OnBeforeCreateInstance
  • OnAfterCreateInstance

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

  • OnGetAutomationObject
  • OnNotifySilverlightError

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 what this means.

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 THYModule and allow you to get access to module data.

THYModule 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)
  • Handle - Handle of the module, can be zero if the module doesn't have a handle

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 stores custom data
  • RequiredPrivilege - String that defines privileges required to create this plugin
  • CheckPluginType - Method that allows you to check if 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

We won't dig deep into details of custom interface usage (please refer to this article: Passing interfaces between Host and Plugins), but we will look at how the host deals with them.

The THYModuleManager 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, this is for general interfaces like IHYCrossPlatformHost.
  2. If the plugin requests a different interface, and if 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: THYModuleManager;
[..]
private
  procedure MyInterfaceMethod; safecall;
[..]

In some cases (if, for example, you don't have a parent form), you may use a custom implementor which is a bit more complex. Module manager requires that CustomImplementor object implements IInterface, so we can call the 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 that can be used as a base class for an object that implements custom interfaces, for example:

uses
 uHYInterfaceHelpers;

[..]
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: IHYVCLVisualPlugin;
  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: IHYVisualPlugin; - 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 the plugin is no longer needed.
  • ModuleManager.LoadModule('ModuleName.dll'); - This method allows you to load a plugin module with a specified path. Module manager will automatically detect the type of the module and will use the appropriate method. Module manager also provides a couple of methods for module loading, such as loading from a list of file names or loading using a 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 that 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 only used 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 an instance of a plugin before the plugin module is unloaded.
  • ModuleManager.UnloadModules; - This method unloads all modules that were previously loaded by the module manager. 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 able to load, create and show your visual plugin.

Runtime Packages

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