WinForms Hosts

__NOEDITSECTION__

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

Windows Forms (WinForms) is Microsoft's visual component-based framework, and Hydra WinForms hosts are the second most used host applications for host applications.

In this article we will learn how to create a new WinForms 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 host application is a simple thing, to do this select File -> New -> Project in the Visual Studio menu, choose the language you need (we provide templates for C#, VB.NET and Oxygene for .NET), then, in the RemObjects Hydra category, choose Host Application:

In this dialog you also need to enter the project name and destination folder.

And basically this is it, after finishing its work, the wizard will create a new application that is ready to work with Hydra plugin modules.

Review the resulting project

As mentioned earlier, the host application is a common Windows Forms Application, so when the wizard is done, you will receive the same project as you would with a regular template.

  • Program.cs - The entry point of the application; contains code that runs your application and main form.
  • Main.cs - Main form of the application, a regular WinForm.

The wizard also added a new component to the main form, the module manager, a key component that allows you to manage your plugins, which will be described later in this article.

One more thing: in the AssemblyInfo.cs we need to set the following assembly attribute:

[assembly: ComVisible(true)]

Please do not change this attribute, it will allow plugins to access the interfaces exposed by the host application.

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 WinForms application, the main form generated by the wizard is inherited from the standard System.Windows.Forms.Form class and provides exactly the same design surface, so you can work with the host application like you do with any regular WinForms 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:

For host applications, the ModuleManager 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 module manager members.

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 a IHYCrossPlatformPlugin, however, you can also use the CreateVisualPlugin or CreateNonVisualPlugin 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.

Events that react to various loading events:

  • ModuleLoading
  • ModuleLoaded

Modules and Descriptors

As you can see, the module manager provides a couple of members that allow you to access the modules and descriptors.

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

LoadedModule 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 the list of plugin descriptors. Unlike the module manager property, it allows you to get descriptors only for this specific module.

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

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

  • 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.
  • CheckPluginAttribute - Method that allows you to check whether the plugin has a specified attribute.

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.

Basically the host object is an object that implements the IHYCrossPlatformHost interface. This interface doesn't have any methods, it just shows that the object is a host.

Most commonly, you will implement this interface in your main form, like this:

  public partial class Main : Form, IHYCrossPlatformHost
  {
    public Main()
    {
      InitializeComponent();
    }
  }

But of course you can have a dedicated object. After you have a host object, you need to assign it to plugin instances. There are a couple of ways to do this, the first is to assign it to the module manager's Host property:

moduleManager.Host = this; //in case when your main form is a host object

This will allow module manager to assign it to every module that is being loaded and to every instance that you will create. The second way is to assign it individually to plugin- and module controller instances:

  var Module = moduleManager.LoadModule("Plugin.dll");
  Module.ModuleController.Host = this;
  var Instance = moduleManager.CreateInstance("MyPlugin");
  Instance.Host = this;

Both ways do the same job, the first is a little simpler, while the second gives you more control over the process.

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:

  public partial class Main : Form, IHYCrossPlatformHost
  {
    public Main()
    {
      InitializeComponent();
    }

    private IHYCrossPlatformPlugin Instance = null;

    private void Main_Load(object sender, EventArgs e)
    {
      moduleManager.LoadModule("Plugin.dll");
      Instance = moduleManager.CreateInstance("MyPlugin");
      hostPanel1.HostPlugin(Instance as IBasePlugin);
    }
  }

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

  • IHYCrossPlatformPlugin Instance - This is a reference to an instance of the plugin. You can use it to perform any specific actions with a plugin. In case you need a specific plugin (visual or non-visual), you can cast this instance to a desired interface:
  if (Instance is IHYCrossPlatformNonVisualPlugin)
    (Instance as IHYCrossPlatformNonVisualPlugin).Start();
  • moduleManager.LoadModule("Plugin.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.
  • Instance = moduleManager.CreateInstance("MyPlugin"); - This method will create an instance of the plugin with the specified name and assign this instance to the Instance variable defined above.
  • hostPanel1.HostPlugin(Instance as IBasePlugin); - This method is used for visual plugins, it can show plugin content in a special container called HostPanel.

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