Hosting a Silverlight Plugin
There are a couple of differences between Silverlight and all the other plugins, so in order to properly host this kind of plugin, you may need to perform some specific actions.
Loading the Plugin
Let's take a look at this small example:
VCL/FMX:
procedure TMainForm.FormCreate(Sender: TObject);
begin
ModuleManager.LoadModule('SilverlightPlugin.xap');
ModuleManager.CreateVisualPlugin('SilverlightPlugin', fInstance, Panel1);
end;
WinForms:
private void MainForm_Load(object sender, System.EventArgs e)
{
moduleManager.LoadModule("SilverlightPlugin.xap");
fInstance = moduleManager.CreateInstance("SilverlightPlugin");
hostPanel1.HostPlugin(fInstance as IBasePlugin);
}
The loading procedure looks pretty much like for any other plugin, so let's review the example step by step:
-
ModuleManager.LoadModule('SilverlightPlugin.xap'); - Loads a
*.xap file that holds our plugin. The
LoadModule
method automatically detects the type of the plugin and calls the appropriate method, you can, however, directly tell the module manager to load a Silverlight plugin by callingLoadSilverlightModule
. -
ModuleManager.CreateVisualPlugin('SilverlightPlugin', fInstance,
Panel1); - Creates an instance of the plugin, assigns a reference to this
instance to the
fInstance
property and shows it in a plugin container. - fInstance = moduleManager.CreateInstance("SilverlightPlugin"); - Same for the .NET side, except you need to show the plugin content in a host panel manually.
- hostPanel1.HostPlugin(fInstance as IBasePlugin); - Shows the plugin content in a host panel.
- ModuleManager.ReleaseInstance(fInstance); - You must release an instance of a plugin before the module is unloaded.
Please note
: The major difference between Silverlight and other plugins is
that the name of the Silverlight plugin instance will always be the same as the
name of the xap
file.
Another difference lies in the communication between host and plugin.
Communicating with the Plugin
Starting from Silverlight 4, Microsoft introduces the ability for
Silverlight applications to access automation objects like Office
or
scripting by using the AutomationFactory
.
Hydra allows you to hook the calls from AutomationFactory to provide a custom object that can be used from the Silverlight side.
Because Silverlight is running in the sandbox, communication with the Silverlight plugins are really different than communication with other types of plugin.
The first difference is that the host cannot acquire a reference to the plugin instance, the only reference it gets is to the interface that is used internally to control Silverlight object behavior, which cannot be used to communicate with the plugin. So only the plugin itself can initiate communication with the host, but not vice versa.
The second difference is that Silverlight supports only late binding,
that means that unlike in other supported platforms you cannot have a
predefined interface and all work must be done with help of the
dynamic
type.
Let's discuss in detail how communication can be achieved.
Delphi Host
Silverlight can use only based automation objects, so in Hydra 4 we are
introducing a new
THYDispatchWrapper
class that you
can use as a base class for your custom objects. This class implements
IDispatch and allows Silverlight to call its public methods and access
its published properties.
Both VCL and FireMonkey use the same methods to communicate with the plugin.
Let's take a look at this small example:
TCustomObject = class (THYDispatchWrapper)
private
function GetIntProperty: Integer;
function GetStringProperty: WideString;
procedure SetIntProperty(const Value: Integer);
procedure SetStringProperty(const Value: WideString);
public
procedure SendMessage(const Message: WideString);
published
property IntProperty: Integer read GetIntProperty write SetIntProperty;
property StringProperty: WideString read GetStringProperty write SetStringProperty;
end;
As you can see, our custom object is inherited from the
THYDispatchWrapper
class. By default this class
provides reference counting, and if the reference count hits 0, it
will be destroyed, but you can change this behavior by overriding the
\_AddRef
and \_Release
methods. This class will automatically
process calls from the Silverlight side and route them to the appropriate
methods or properties.
Please note that in order to be visible to Silverlight, your methods
must be placed in the public
section and your properties should be
in the published
section.
You can pass data types as described in this article. There are, however, a couple of limitations: For one, you need to use the type for arrays, as in this example:
TCustomObject = class (THYDispatchWrapper)
private
function GetArrayProperty: Variant;
procedure SetArrayProperty(const Value: Variant);
published
property ArrayProperty: Variant read GetArrayProperty write SetArrayProperty;
end;
function TCustomObject.GetArrayProperty: Variant;
begin
Result := VarArrayCreate([0, 2], varOleStr);
VarArrayPut(Result, 'Delphi', [0]);
VarArrayPut(Result, 'Host', [1]);
VarArrayPut(Result, 'Data', [2]);
end;
procedure TCustomObject.SetArrayProperty(const Value: Variant);
var
LBound,HBound: Integer;
StrValue: String;
I: Integer;
begin
LBound := VarArrayLowBound(Value, 1);
HBound := VarArrayHighBound(Value, 1);
for I := LBound to HBound do
StrValue := StrValue + VarToStr(VarArrayGet(Value, [i]));
end;
You can see how to create and fill a new string array and how to read a string array that is coming from Silverlight.
The second limitation is that you can't pass custom types such as
records
.
If you need to pass a custom object to a
Silverlight plugin, and, as stated before, only the plugin itself can initiate
communication, the host needs to react. To achieve this
we've introduced a new event in the
THYModuleManager and the
THYFMXModuleManager that is
called OnGetAutomationObject
.
Let's take a look at the following sample:
procedure TMainForm.ModuleManagerGetAutomationObject(Sender: THYModuleManager; const ProgId: string;
const Flags: THYGetAutomationObjectFlags; var Disp: IDispatch);
begin
if ProgId='Hydra.Host' then
Disp := TCustomObject.Create;
end;
-
This event will fire every time the Silverlight plugin calls ''
AutomationFactory.CreateObject'' or
AutomationFactory.GetObject
. -
Depending on the call, the
Flags
parameter will be set toaoGet
or toaoCreate
. You can handle this manually to create a new object or return an existing instance. -
We're checking for our custom
ProgId
value which can be set on the Silverlight side to ensure that we are reacting on a proper call. -
If you set the
Disp
property that means the call is handled and this object will be returned to the plugin; if you set this property tonil
, Silverlight will perform a default call that allows to create an automation object that is registered in the system asExcel.Application
orWScript.Shell
.
There is one more event in the module manager that is related to
Silverlight: OnNotifySilverlightError
. This event
handler invokes whenever Silverlight reports an error within XAML DOM
of the hosted control.
WinForms Host
On the .NET side you don't need any base object to be able to communicate with the plugin since .NET already handles this for you. First of all, we need to do some basic setup for the host:
public partial class Main : Form, IHYCrossPlatformHost, IHYCrossPlatformSilverlightHost
{
[..]
public void GetAutomationObject(string ProgId, GetAutomationObjectFlag Flags, ref object Dispatch)
{
if (ProgId == "Hydra.Host")
{
Dispatch = this;
}
}
public void NotifyError(string Error, string Source, int Line, int Column)
{
MessageBox.Show(Error);
}
}
Unlike Delphi, WinForms hosts don't come with a default host object, so you need to define one manually. To do so, you need to implement the IHYCrossPlatformHost interface, in the example it is implemented it in the main form, but you can choose whatever object you like.
To be able to process calls from Silverlight, you also need to implement
the
IHYCrossPlatformSilverlightHost.
This interface has two methods, GetAutomationObject
and NotifyError
,
which act exactly the same as in Delphi.
GetAutomationObject allows you to react to
AutomationFactory.CreateObject
and AutomationFactory.GetObject
calls
(the Flags
parameter will be set accordingly). NotifyError will be
invoked whenever Silverlight reports an error within XAML DOM of the
hosted control.
Note that we're passing this
as our automation object, so all calls will
go to the main form, but you can pass every object you want, not just the main
form.
As a second step, we need to assign our host to the plugin, for example:
private void Main_Load(object sender, EventArgs e)
{
moduleManager.LoadModule("SilverlightPlugin.xap");
Instance = moduleManager.CreateInstance("SilverlightPlugin");
Instance.Host = this;
hostPanel1.HostPlugin(Instance as IBasePlugin);
}
We've assigned the host reference directly to the plugin instance by setting the
Instance.Host
property, but it is also possible to set the
moduleManager.Host
property so every new plugin instance will receive a
reference to the host automatically.
When the host is ready to work with Silverlight, you can add methods and properties that will be visible to the plugin.
public void SendMessage(string Message)
{
MessageBox.Show(Message);
}
public int IntProperty
{
get { return 1; }
set { MessageBox.Show(value.ToString()); }
}
public string StringProperty
{
get { return "Hello for WinForms"; }
set { MessageBox.Show(value); }
}
public string[] ArrayProperty
{
get { return new string[]{"The", "Net", "Data"}; }
}
public void SetArrayProperty(string[] Arr)
{
//work with array
}
As you can see, all methods and properties that should be accessible for
the plugin should be declared as public
.
There is a problem in Silverlight with using array properties, you can read them just fine, but attempting to set them will lead to an exception. But there is a workaround: you can use array parameters in methods to set a property, and this approach works just fine.