How to pass objects
(This page is considered "good" on a technical level, but is pending review for grammar and typos)
Contents |
Marshaling Objects
Marshaling is the process of exchanging between managed code and unmanaged code. When you pass data from an unmanaged host to a managed plugin, or vice versa, this data is converted to a data format suitable for storage or transmission. When you use our converter (described above) data types are automatically converted to the appropriate types on the other side, but in some cases you may need to manually tune data types.
One of these cases is objects. It is impossible to pass objects from .NET to Delphi and vice versa, since System.Object and TObject is not compatible. To solve this problem we can use custom interfaces.
Sample Objects
For this article we will use simple object, that we want to pass from Delphi to .NET and vice versa.
| |
|
|---|---|
public class User { public string Name { get; set; } public string Password { get; set; } public void Login() { } public void Logoff() { } public User(string Name, string Password) { this.Name = Name; this.Password = Password; } } |
TUser = class private fName: string; fPassword: string; public constructor Create(const Name, Password: string); procedure Login; procedure Logoff; property Name: string read fName write fName; property Password: string read fPassword write fPassword; end; |
Of course this could be done with any objects, even if you don't have a source code for it.
Defining an Interfaces
Since we can't pass these objects as is, we need to define an interfaces that will be passed instead of objects.
| |
|
|---|---|
[Guid("2CC7E0E2-5C97-4D5D-A097-95D4D96A5D73")] public interface IUser : IHYCrossPlatformInterface { string Name { get; set; } string Password { get; set; } void Login(); void Logoff(); } |
IUser = interface(IHYCrossPlatformInterface) ['{2cc7e0e2-5c97-4d5d-a097-95d4d96a5d73}'] function get_Name: WideString; safecall; procedure set_Name(const value: WideString); safecall; function get_Password: WideString; safecall; procedure set_Password(const value: WideString); safecall; procedure Login; safecall; procedure Logoff; safecall; property Name: WideString read get_Name write set_Name; property Password: WideString read get_Password write set_Password; end; |
Of course there is no need to manually define both interfaces, you can do this only once, and then convert them using our interface importer tool.
Now when we have interface for our object, we need to create a special wrapper class, that will implement specific interface and forward all calls to the wrapped object.
.NET Wrapper
public class UserWrapper : IUser { private User _instance = null; public UserWrapper(User user) { if (user == null) throw new ArgumentNullException("User"); _instance = user; } public string Name { get { return _instance.Name; } set { _instance.Name = value; } } public string Password { get { return _instance.Password; } set { _instance.Password = value; } } public void Login() { _instance.Login(); } public void Logoff() { _instance.Logoff(); } }
As you can see, .NET wrapper is pretty much simple class, that implements our IUser interface, and calls corresponding methods of a "real" object.
Delphi Wrapper
Delphi wrapper is a little bit complex than its .NET counterpart. Since all cross-platform interfaces inherit from IDispatch, every object that implements this interface must also implement the IDispatch methods. In most cases, these methods are not needed and the THYFakeIDispatch class can be used as base class for objects that implement cross-platform interfaces.
TUserWrapper = class (THYFakeIDispatch, IUser) private fInstance: TUser; function get_Name: WideString; safecall; procedure set_Name(const value: WideString); safecall; function get_Password: WideString; safecall; procedure set_Password(const value: WideString); safecall; public constructor Create(User: TUser); procedure Login; safecall; procedure Logoff; safecall; property Name: WideString read get_Name write set_Name; property Password: WideString read get_Password write set_Password; end; constructor TUserWrapper.Create(User: TUser); begin if User = nil then raise EArgumentNilException.Create('Argument "User" can not be nil'); fInstance := User; end; function TUserWrapper.get_Name: WideString; begin Result := fInstance.Name; end; function TUserWrapper.get_Password: WideString; begin Result := fInstance.Password; end; [...]
Putting All Together
Now we have everything that we need to be able to pass objects from Delphi to .NET and vice versa.
First, we need an interface that returns our object, and that is implemented by both host and plugin:
[Guid("87BFF08E-1281-42D3-AF13-3F4A0CA4488D")] public interface IObjectProvider : IHYCrossPlatformInterface { IUser CreateObject(); }
Now, we can complete our sample, host (.NET):
public partial class Main : Form, IHYCrossPlatformHost, IObjectProvider { [...] //implementing IObjectProvider interface public IUser CreateObject() { return new UserWrapper(new User("Admin", "12345")); } //calling IObjectProvider form a plugin instance private void GetDelphiUserButton_Click(object sender, EventArgs e) { if (instance is IObjectProvider) { IUser user = (instance as IObjectProvider).CreateObject(); MessageBox.Show(String.Format("Name: {0} Password: {1}", user.Name,user.Password)); } } }
And the plugin (Delphi):
TUserVisualPlugin = class(THYVisualPlugin, IObjectProvider) [...] public function CreateObject: IUser; safecall; [...] //Accessing IObjectProvider from a host procedure TUserVisualPlugin.GetNetUserButtonClick(Sender: TObject); var Provider: IObjectProvider; User: IUser; begin if Supports(Host, IObjectProvider, Provider) then begin User := Provider.CreateObject; ShowMessage(Format('Name: %s Password: %s', [User.Name, User.Password])); end; end; //Implementing IObjectProvider function TUserVisualPlugin.CreateObject: IUser; begin Result := TUserWrapper.Create(TUser.Create('Admin', '12345')); end;
Other Articles
Product Articles — Data Abstract — RemObjects SDK
Glossary — Architecture — Articles — Library — Samples
