Non-Visual Plugins (Delphi)

In Delphi, we provide non-visual plugins for both VCL and FireMonkey frameworks.

In this article I will describe how to create a new non-visual plugin, and show you what features it provides and how they can be used.

Getting Started

Before we start to create our first plugin, let's look at a Hydra plugin project in its entirety.

A Hydra plugin project consists of three parts:

  • Plugin Module - A DLL project that acts as a container for a module controller and can hold a set of plugins.
  • Module Controller - Entry point of the module. The module controller supplies the host with information about stored plugins and provides methods that allow the host to instantiate and work with plugins.
  • Plugins - Special classes stored inside the plugin module.

Now that you know what a plugin project consists of, we can work on how to create and set up a Hydra plugin project and add a non-visual plugin to it.

Working with Wizards

Creating a Hydra plugin 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 Module and add a non-visual plugin to it.

Creating a new Plugin Module

In the IDE File -> New -> Other go to the RemObjects Hydra category and select the Plugin Module option:

The New Hydra Module Project wizard will start and you will be presented with a Welcome screen:

On the next page, the wizard allows you to select a folder where the project will be saved, the name of the project and its type:

As you can see, the wizard allows you to choose between FireMonkey and VCL:

The next step is to configure a module controller for the project:

You can choose a name for the controller and (by checking the Configure Advanced Options) set up its data.

The following screenshots show pages with additional options for the module controller:

These pages allow you to set module controller data like name or description. All the data is optional and can be changed later in the project.

Now the final screen that will summarize the project options:

If all settings are fine, press the Finish button and the wizard will create a new Hydra plugin module and add a new module manager to it.

Creating a new Plugin

After the new plugin module is created, it will automatically start the New Hydra Plugin wizard that will allow you to create a new plugin. You can launch this wizard at any time from the IDE menu File -> New -> Other by selecting the RemObjects Hydra category and the Hydra Plugin item.

On the next page you will be able to select the type of the plugin:

The wizard allows you to create three different types of plugin. For this article, we will use the Non-Visual Hydra Plugin option.

After selecting a plugin type, you need to specify a plugin name:

You can also set the plugin version and additional options by checking the Configure Advanced Options. The pages with the advanced options are exactly the same as in the New Hydra Module wizard. They are optional and can be edited at any time in the project.

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

That done, the wizard will add a new file that contains a non-visual plugin and show a dialog that allows you to choose the runtime package settings:

We will learn how to work with the runtime package later in this article.

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 added by Hydra is {#HYDRAMODULE}. Please do not remove this line, it allows our IDE tools to recognize the Hydra module file.
  • Module Controller file - This file contains the definition of the module controller and the entry point.

  • HYInitializeModule - this method is called at initialization of plugin. Can be used for creation of default module controller. Optional.

  • HYFinalizeModule - this method is called at finalization of plugin. Can be used for destroying of created module controller. Optional.
  • HYGetCrossPlatformModule - This method returns an IHYCrossPlatformModule reference, which is used by .NET and Delphi host applications to get a reference to the module controller. Used in cross-platform mode.
  • HYGetModuleController - This method returns a THYModuleController instance. Used by Delphi VCL hosts when host and plugins are compiled with runtime packages.
procedure HYGetCrossPlatformModule(out result: IHYCrossPlatformModule); stdcall;
begin
  result := NewPluginLibraryController as IHYCrossPlatformModule;
end;

procedure HYInitializeModule; stdcall;
begin
  NewPluginLibraryController := TNewPluginLibraryController.Create('NewPluginLibrary.Library', 1, 0, sRequiredPrivilege, sDescription);
end;

procedure HYFinalizeModule; stdcall;
begin
  if Assigned(NewPluginLibraryController) then FreeAndNil(NewPluginLibraryController);
end;

exports
  HYInitializeModule,
  HYFinalizeModule,
  HYGetCrossPlatformModule;
procedure HYGetCrossPlatformModule(out result: IHYCrossPlatformModule); stdcall;
begin
  result := NewPluginLibraryController as IHYCrossPlatformModule;
end;

function HYGetModuleController : THYVCLModuleController;
begin
  result := NewPluginLibraryController;
end;

procedure HYInitializeModule; stdcall;
begin
  NewPluginLibraryController := TNewPluginLibraryController.Create('NewPluginLibrary.Library', 1, 0, sRequiredPrivilege, sDescription);
end;

procedure HYFinalizeModule; stdcall;
begin
  if Assigned(NewPluginLibraryController) then FreeAndNil(NewPluginLibraryController);
end;

exports
  HYInitializeModule,
  HYFinalizeModule,
  HYGetCrossPlatformModule,
  HYGetModuleController name name_HYGetModuleController; // backward compatibility
procedure HYGetCrossPlatformModule(out result: IHYCrossPlatformModule); stdcall;
begin  
  result := NewPluginLibraryController as IHYCrossPlatformModule;
end;

procedure HYInitializeModule; stdcall;
begin
  NewPluginLibraryController := TNewPluginLibraryController.Create('NewPluginLibrary.Library', 1, 0, sRequiredPrivilege, sDescription);
end;

procedure HYFinalizeModule; stdcall;
begin
  if Assigned(NewPluginLibraryController) then FreeAndNil(NewPluginLibraryController);
end;

exports
  HYInitializeModule,
  HYFinalizeModule,
  HYGetCrossPlatformModule,
  HYGetCrossPlatformModule name name_HYFMGetModuleController; // backward compatibility

Please note how we created an instance of the module controller. As you can see, it uses the data that we defined with the wizard, but you can change this data manually.

  • Non-Visual plugin file - Contains the wizard-generated definition of the non-visual plugin:
procedure Create_NewNonVisualPlugin(out anInstance: IInterface);
begin
  anInstance := TNewNonVisualPlugin.Create(NIL);
end;

resourcestring
  sDescription = '';

const
  sRequiredPrivilege = '';
  sUserData = '';

initialization
  RegisterPlugin('NewNonVisualPlugin', Create_NewNonVisualPlugin, TNewNonVisualPlugin,
                  1, 0, sRequiredPrivilege,
                  sDescription, sUserData);

First, the method that creates a new instance of the plugin is called. This method will be called by the module controller when the host application asks for a plugin instance. As you can see, by default it creates a new instance on every call.

The initialization section calls a RegisterPlugin method that registers our plugin in the module controller. Like the code in the controller, it uses data that was entered via the wizard to initialize the plugin descriptor.

Using the plugin

By now you will have a complete project that can be loaded by all supported host platforms without any additional work.

Both module controller and non-visual plugin provide a set of internal methods that are used by the Hydra framework to perform specific tasks, however, they also provide some useful methods/properties which can be used in your own application.

Below I will describe the most common way to use the module controller and plugin, but first, you should learn about custom interfaces. One major part of the Hydra framework are custom interfaces. Custom interfaces are user-defined interfaces that can be used in a cross-platform environment (they can, for example, be shared between a Delphi plugin and a .NET host application) to get access to data or to call host or plugin methods. When I talk about things like "accessing host methods", I refer to these 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:

Before I start to describe the visual plugin itself, let's look at how you can use the module controller.

How to use the Module Controller

While the main purpose of the module controller is to provide information about a plugin to a host, it can also be useful with some specific tasks.

As you can see in the code snippet above, the module controller is a global object, so it can be used to initialize some global data or to allow the host to gain access to global methods.

The module controller is derived from TDataModule and can be used exactly like data modules in regular Delphi applications, just keep in mind that the application mustn't contain more than one module controller and that the module controller will be initialized when the plugin is loaded by the host application and destroyed on module unload.

The module controller provides the following members:

  • OnSetHost event - This is an important event which enables you to access host methods or data. Because the controller is initialized on module load, it doesn't have access to the Host property in the OnCreate event. When the host assigns its reference to a module controller, the OnSetHost event is fired so you can safely get access to the host. One thing that you need to consider is that this event can be called with a nil host reference (when the host unloads a module), so you need to check this before accessing host members.
  • Host - This property holds the reference to the host object and can be used to access host methods or data.
  • Descriptor - Reference to the THYBaseDescriptor object, which stores module controller meta-data (like version or description) that was added by the wizard. This allows you to manipulate this information at runtime.
  • PluginDescriptorCount - Returns the number of registered plugins.
  • PluginDescriptors - Allows you to get access to a registered plugin descriptor by index and returns a reference to the THYPluginDescriptor or, in case of FMX, the THYCrossPlatformPluginDescriptor class that is used to store plugin meta-data.

How to use a Non-visual Plugin

Non-Visual plugins are descendants of TDataModuleand have an internal set of methods that allows them to be used from any supported host application.

You can use non-visual plugins like any regular data module, since they provide exactly the same design surface and functionality like regular modules do.

Like the module controller, visual plugins provide OnSetHost and Host members as described above. They also provide a set of events that can be called by the host application:

  • OnStart
  • OnStop
  • OnPause
  • OnResume

These events can be triggered from the host by calling the corresponding methods (Start, Stop, etc.).

VCL non-visual plugins also provide additional possibilities:

  • ControlsRepository - This property allows you to assign a reference to the THYPluginControlsRepository. The control repository is a storage that can hold definitions of menus and tool-bars exposed by the plugin.
  • PluginActions - This plugin allows you to assign a reference to the TActionList that the plugin exposes to the host application.

Please note that both ControlsRepository and PluginActions can only be exposed to VCL host applications.

Working with actions, menus and tool-bars is described in detail in the Hydra support for Menus and Toolbars article.

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.