1

I have a legacy app written in Delphi 7. We are adding new modules to the app. The modules are written in Visual Studio 2010, .NET 4, C#, and exposed to the app through COM.

I have successfully defined a class, registered the assembly, exported the type library, imported the type library into Delphi, created the COM client in Delphi and executed the module. Now comes the tricky part: I want to pass another object (that has been defined-registered-exported-blah-blah-blah as above) as a parameter to a method on the first module.

.NET

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("11111111-1111-1111-1111-AAAAAAAAAAAA")]
public interface IUserMaintenance
{
    bool AssignUser(IUserInfo);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("11111111-1111-1111-1111-BBBBBBBBBBBB")]
public class UserMaintenance: IUserMaintenance
{
    private IUserInfo _UserInfo;

    public bool AssignUser(IUserInfo userInfo)
    {
        _UserInfo = userInfo;
        LoadUser();
    }

    private void LoadUser()
    {
        //load user data from database using _UserInfo.UserName
    }
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
    void Initialize(string userName);
    string UserName { get; }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
    public string UserName { get; private set };

    public void Initialize(string userName)
    {
        UserName = userName;
    }
}

Assuming that I have separate classes implementing each interface, and the assemblies containing those classes compile and register successfully, I import the type libraries into Delphi creating UserMaintenance_TLB.pas and UserInfo_TLB.pas. However, I see something unexptected: while the interfaces that I defined in .NET exist (the ones beginning with "I"), Delphi has generated another set of interfaces (the ones beginning with "_"). Delphi does not use the I-interfaces that I declared in .NET at all.

Delphi

UserMaintenance_TLB.pas

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IUserMaintenance = interface;
  IUserMaintenanceDisp = dispinterface;
  _UserMaintenance = interface;
  _UserMaintenanceDisp = dispinterface;

UserInfo_TLB.pas

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IUserInfo = interface;
  IUserInfoDisp = dispinterface;
  _UserInfo = interface;
  _UserInfoDisp = dispinterface;

Delphi has also created the corresponding Delphi types:

// *********************************************************************//
// OLE Server Proxy class declaration
// Server Object    : TUserMaintenance
// Help String      : 
// Default Interface: _UserMaintenance
// Def. Intf. DISP? : No
// Event   Interface:
// TypeFlags        : (2) CanCreate
// *********************************************************************//

  TUserMaintenance = class(TOleServer)
  private
    FIntf:        _UserMaintenance;
    function      GetDefaultInterface: _UserMaintenance;
  protected
    procedure InitServerData; override;
    function Get_ToString: WideString;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure Connect; override;
    procedure ConnectTo(svrIntf: _UserMaintenance);
    procedure Disconnect; override;
    function Equals(obj: OleVariant): WordBool;
    function GetHashCode: Integer;
    function GetType: _Type;
    WordBool AssignUser(IUserInfo userInfo);
    property DefaultInterface: _UserMaintenance read GetDefaultInterface;
    property ToString: WideString read Get_ToString;
  published
  end;

What I would like to do is create an instance of TUserMaintenance, and then pass an instance of TUserInfo to it. However, two things are immediately obvious: The Delphi class TUserInfo does NOT implement IUserInfo, and the DefaultInterface is the new interface that Delphi generated, _UserInfo. I cannot declare my UserInfo variable as type IUserInfo because TUserInfo does not implement the interface. Nor can I declare it as type _UserInfo because UserMaintenance.LoadUser expects an IUserInfo instance.

Admittedly, this is a much simplified example of my actual issue, but I think that it sufficiently illustrates the problem.

So my question is this: is there any way for me to force the interface type in Delphi to remain consistent with the interface that is declared in .NET? Or is there another way that I can pass an instance of UserInfo to UserMaintenance?

2 Answers 2

2

Part of the problem that in Delphi, Objects may implement interfaces, but they are not in and of themselves interfaced objects. To understand this distinction, you have to look at the raw implementation of an interface on a Delphi object, and understand the reference counting mechanism used by COM. Another thing that has to be understood is that .NET doesn't work the same way, so what appears to be an interface in .NET for an object IS the same as the ComVisible aspect presented.

TYPE
  TMyObject = class(TInterfacedObject, IMyObject, IMyNewInterface)
  end;

Given the above, the following things can be said of this object. You created a new COM object using the Delphi COM object wizard. The Interface IMyObject is defined as the default interface, and TMyObject is the concrete class that will implement that interface. IMyNewInterface is a secondary interface defined somewhere else, that you have indicated your object implements.

You can do the following with this object

 var
   I1: IMyObject;
   I2: IMyNewInterface;
   T: TMyObject;
 begin
   I1:=TMyObject.Create;
   I2:=TMyObject.Create;
   T:=TMyObject.Create;
 end;

You can do these things because TMyObject IMPLEMENTS these interfaces. Also, since these are reference counted, you don't have to release them from memory, when the method scope ends, so does the lifetime of the object - WITH THE EXCEPTION OF THE LAST ONE Because you are using an OBJECT REFERENCE instead of an INTERFACE REFERENCE, you must free the OBJECT you created.

Given the code you posted, if you look closely, you will actually find the same situation. In your case, your object is completely implemented in .NET, so what you are trying to use is a Delphi code Wrapper - which is an OBJECT, not an INTERFACE.
You notice on the wrapper, that there is no second method to pass an instance of the IUserInfo object to, and that is because this OBJECT is not implementing the INTERFACE, (your .NET object was created to do that) - what you need to do in Delphi is "select that interface"

You would accomplish that task by doing the following:

If you have already done a TUserMaintenance.Create(nil) call and have an instance of that object, get the default interface, then "cast" it to the appropriate implemented interface

 var
   UMDelphiWrapper: TUserMaintenance;
   UIDelphiWrapper: TUserInfo;
   UI: IUserInfo;
   UM: IUserMaintenance;


 begin
   //this part creates a Delphi OBJECT reference to the Implementation class -
   //this is NOT Reference counted, because it doesn't implement an interface.
   UIDelphiWrapper:=TUserInfo.Create(nil);
   try
     //this is the part where you acquire the interface of the object that actually
     //implementes this interface - e.g. your .NET class
     //not that this is the INTERFACE reference - which WILL be reference counted
     UI:=UIDelphiWrapper.DefaultInterface as IUserInfo;

     //UI.<Set some properties of your IUserInfo object>

     try
       //this part creates a Delphi OBJECT reference to the Implementation class -
       //this is NOT Reference counted, because it doesn't implement an interface.
       UMDelhpiWrapper:=TUserMaintenance.Create(nil);
       try
         //this is the part where you acquire the interface of the object that actually
         //implementes this interface - e.g. your .NET class
         //not that this is the INTERFACE reference - which WILL be reference counted
         UM:=UMdelphiWrapper.DefaultInterface as IUserMaintenance;
         try
           //Here, you have an interface type implemented by your .NET class that you are
           //sending to the implementation of your management object (Also a .NET class)
           UM.SendUser(UI);

           //do whatever else you need to do with your interface and user/management .NET object(s)
         finally
           //this IS a reference counted COM object - no "free" necessary 
           //this would naturally happen when the reference goes out of scope in the method
           //but for clairity sake is set to NIL to explicitly release your reference
           UM:=nil;
         end;

       finally
         //This is a delphi object that is NOT reference counted and must be released
        FreeAndNil(UMDelphiWrapper);     
       end;

     finally
       //this IS a reference counted COM object - no "free" necessary 
       //this would naturally happen when the reference goes out of scope in the method
       //but for clairity sake is set to NIL to explicitly release your reference
       UI:=nil; 
     end;
   Finally
     //This is a delphi object that is NOT reference counted and must be released
     FreeAndNIl(UIDelphiWrapper);
   end;

In addition to actually using the Delphi provided wrappers you could instead directly create references to your .NET objects as long as you know the correct information.

var
  UI: IUserInfo;
  UM: IUserManager;

begin
  UI:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserInfo;
  UI.SomeProperty:=SomeValue;
  UM:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserManger;
  UM.SomeMetohd(UI);
end;

This code is much cleaner - however, you must still have the accurate definition of IUserInfo and IUserMaintenance, as well as know the class friendly name of your objects as they have been registered with COM.

You could do this yourself by typing the code out - this is what the type import function should have done for you when you imported the COM exposed DLL from your assembly. I didn't see the actual implementation in the code you provided, but you should still find this information in your header file. -- If you don't, then you didn't import the correct assembly, or the Delphi 7 import didn't function correctly - or it needs to be refreshed (e.g. you added new methods to your .NET implementation, but didn't re-register (with COM) and re-import the new assembly type info).

type
  IUserInfo = interface
  ['22222222-2222-2222-2222-AAAAAAAAAAAA']
  //define your methods
  end;
Sign up to request clarification or add additional context in comments.

Comments

1

Hope i understand your question correctly. If you want to get rid of the confusing (and unnecessary) _UserInfo in the typelibrary, you should not export your CoClass, only the interface.

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
}

Note the first attribute of the CoClass is set to ClassInterfaceType.None, this way DotNet will not reveal the CoClass itself to the typelibrary. Nevertheless you can instantiate your object in Delphi as you always do:

var
  pUserInfo: IUserInfo;
begin
  pUserInfo := CoUserInfo.Create;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.