How to pass objects

From RemObjects Wiki
Jump to: navigation, search

This is a HowTo topic about Hydra


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

.NET
Delphi

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.

.NET
Delphi

[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  


Product: RemObjects Hydra
Current version: Hydra 4.0

GlossaryArchitectureArticlesLibrarySamples

Personal tools
Namespaces

Variants
Actions
Navigation
products
platforms
special
Toolbox