Wednesday, August 20, 2014 Register   Login

This site uses DNS Made Easy. Use it for reliable and professional DNS services.

RSS Feeds
Blog

C# COM server to Delphi COM clients

I'm involved in creating a COM interface from a C# 4.0 library. This took a lot of hair-pulling and banging-head-against-the-wall moments. This skillset appears to be quickly disappearing; sad, but quite understandable. I wanted to store how this is done, because I found many little slivers across the Intertubes, but nowhere did I find the slivers put into a coherent picture.

I'm going to use a simple class that I'm calling ComDog. Here is the definition:

using System;

using System.Runtime.InteropServices;

namespace ComDog

{

    [ComVisible(false)]

    public delegate void DogEventHandler();

    [ComVisible(true)]

    [Guid("2406DD50-A3CE-43A6-9F20-112B621CB784")]

    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]

    public interface IDogEvents

    {

        [DispId(1)]

        void Bark(); 

        [DispId(2)]

        void Howl(); 

        [DispId(3)]

        void Eat();

    }

    [ComVisible(true)]

    [Guid("8C6DAD17-0612-4166-AD35-3A55DDEAF62E")]

    [ClassInterface(ClassInterfaceType.AutoDual)]

    [ComSourceInterfaces(typeof(IDogEvents))]

    public class Dog : MarshalByRefObject

    {

        public event DogEventHandler Bark;

        public event DogEventHandler Howl;

        public event DogEventHandler Eat;

 

        public void MakeDogBark()

        {

            if (Bark != null)

            {

                Bark();

            }

        }

 

        public void MakeDogHowl()

        {

            if (Howl != null)

            {

                Howl();

            }

        }

 

        public void MakeDogEat()

        {

            if (Eat != null)

            {

                Eat();

            }

        }

    }

Now let's get this compiled and registered. To get this registered with COM, well need to issue this command, in an elevated command prompt:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm comdog.dll /tlb:ComDog.tlb

Now on to Delphi 7...

Copy both the ComDog.dll and ComDog.tlb to the same folder as your Delphi project. I'm doing this, because I don't want to mess with the GAC.

In the Delphi IDE, select Project --> Import Type Library. It will look like this:

This in turn, should show the Import Type Library dialog, that looks like this:

If you registered the assembly (dll in .NET parlance), you should see an entry for ComDog, like the selected one in the dialog above. Click on the "Create Unit" button. This will add a "ComDog_TLB.pas" file to your Delphi project.

One important section that I want to direct your attention to:

// *********************************************************************//
// OLE Server Proxy class declaration
// Server Object    : TDog
// Help String      :
// Default Interface: _Dog
// Def. Intf. DISP? : No
// Event   Interface: IDogEvents
// TypeFlags        : (2) CanCreate
// *********************************************************************//
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
  TDogProperties= class;
{$ENDIF}
  TDog = class(TOleServer)
  private
    FOnBark: TNotifyEvent;
    FOnHowl: TNotifyEvent;
    FOnEat: TNotifyEvent;
    FIntf:        _Dog;

The events are of type TNotifyEvent, because they do not have parameters. This comes from the IDogEvents. Delphi will ALWAYS use the definition in your event interface, instead of the events listed in the main class. Make absolutely sure that the signature in the event interface match the signature of the events in your main C# class.

Here is what the definition of the events will look like in your Delphi main form/unit:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ActiveX, ComObj, ComDog_TLB, StdCtrls, OleServer;

type
  TForm1 = class(TForm)
    btnBark: TButton;
    btnHowl: TButton;
    btnEat: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure btnBarkClick(Sender: TObject);
    procedure btnHowlClick(Sender: TObject);
    procedure btnEatClick(Sender: TObject);
  private
    { Private declarations }
  protected
    procedure Barked(Sender: TObject);
    procedure Howled(Sender: TObject);
    procedure Ate(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  TestDog: TDog;

The proctected section above defines the events. This you will have to type manually. Make sure that your _TLB file is listed in the uses section above.

Now to actually have the events handled, you will have to create a procedure that matches the signature of event. Like so:

procedure TForm1.Barked(Sender: TObject);
begin
  Memo1.Lines.Add('Dog Barked');
end;

procedure TForm1.Howled(Sender: TObject);
begin
  Memo1.Lines.Add('Dog Howled');
end;

procedure TForm1.Ate(Sender: TObject);
begin
  Memo1.Lines.Add('Dog ate something');
end;

This is how you wire up C# COM events up to a Delphi COM client app.

The next question is how to deal with events that have parameters. It's basically the same thing. You'll just have to make sure that the signatures for event definitions and event handlers in Delphi, match what's in the _TLB file.

I did a lot of hair-pulling in order to get this working. I wanted to post this in my blog, for the next time I'm doing this. Please feel free to comment here, if you need more help.

posted @ Saturday, October 09, 2010 6:34 AM by Hector Sosa, Jr

Posted in: Code, Visual Studio

Actions: Tweet This  Share on Facebook  Share on LinkedIn  Emakl  Permalink  del.icio.us

Previous Page | Next Page

COMMENTS

Hi there. The article was of exceptional use for me. However, I would also like to see the Delphi code for creating com object, calling the c# code, etc. Maybe You might post the complete example.

E.g. I tried several types of calls before Delphi library stopped my headache with its "The system cannot find the file specified" messages. I finally stopped with this (Delphi):

procedure TFormPranesimai.CallNETCOM;
var
FMyComObject : IDogEvents;
begin
OleCheck(CoCreateInstance(
Class_Dog,
nil,
CLSCTX_ALL,
IDogEvents,
FMyComObject));
Dog.Bark;
end;

However I'm not sure it's the right way.

posted @ Monday, April 18, 2011 7:53 AM by Andy


Andy,

You don't need to do anything convoluted on Delphi's side. I outlined the steps on Delphi's side above. It's the C# side that makes you jump through hoops.

This code setup only works for Delphi 7. It won't work for anything earlier than that. I briefly tested it with Delphi 2010, and it seemed to work. Delphi is very picky about COM signatures. The C# code above will not work with Delphi 6.

If you encounter problems, it usually has to do with how the C# COM code was composed.

posted @ Monday, April 18, 2011 10:22 AM by Hector Sosa, Jr


Hi there. I also noticed, that Your c# class is not inherited from IDogEvents interface. Is that as intended?
When i try not inheriting(implementing) the interface on c#, Delhpi side throws "Interface not supported" messages during runtime.

However, if I implement(inherit) interface, c# refuses to compile, says that class does not implements interface members (Bark Howl Eat), when I define them as events in class. It wants to see functions. It only works, when I define Bark, Howl and Eat in class as functions, not events.

posted @ Thursday, April 21, 2011 1:33 AM by Andy


"I also noticed, that Your c# class is not inherited from IDogEvents interface. Is that as intended?"

Correct. Notice the ComSourceInterfaces attribute on the Dog class. This is what actually "wires" up the interface to the class.

"However, if I implement(inherit) interface, c# refuses to compile, says that class does not implements interface members (Bark Howl Eat), when I define them as events in class. It wants to see functions. It only works, when I define Bark, Howl and Eat in class as functions, not events."

Correct. This is the only way I've found this to work. That's why I laid out a full C# example. I just copy and pasted the example code above into VS2010, and it compiled the first time.

One thing that might not be clear (as I didn't see it in the post above), is that you call the three methods in the C# class, from Delphi, in order to invoke\fire the events. These would look like this in your Delphi code:

TestDog.MakeDogBark; - Will fire the Bark event
TestDog.MakeDogEat; - Will fire the Eat event
TestDog.MakeDogHowl; - Will fire the Howl event

Notice the definitions for 3 buttons in the Delphi code:

procedure btnBarkClick(Sender: TObject);
procedure btnHowlClick(Sender: TObject);
procedure btnEatClick(Sender: TObject);

These are where I call the MakeDogXXX functions/methods. Sorry if that wasn't clear.

COM programming is weird. Remember this setup only works for Delphi 7. You are on your own if you are using a different version, as I don't have experience with other versions and their COM idiosyncrasies.

posted @ Thursday, April 21, 2011 11:14 AM by Hector Sosa, Jr


Hi Hector,

Thanks so much for posing this, it has helped a lot. One quick question, might be dumb, but what is the code you are using to create the TestDog object on from create? I can't seem to figure out how to initialize the object. (I'm not a Delphi programer, but I need to write a Delphi example for our SDK).

Any help would be great.

posted @ Wednesday, October 05, 2011 5:51 PM by John


John,

I don't have Delphi installed anymore, but I believe you do object instantiation like this:

TestDog := TDog.Create;

posted @ Wednesday, October 05, 2011 9:55 PM by Hector Sosa, Jr


Hi,

I followed the steps described in your great article. I am close to make it work, but there is a problem: my event handlers from the delphi win32 client application do not get called.

I am doing this:
TestLib := TTest.Create(nil);
TestLib.Connect;
TestLib.OnOperationCompleted := lfOnOperationCompleted;
TestLib.OnOperationCompletedNoArgs := lfOnOperationCompletedNoArgs;

I have the handlers:
procedure TForm1.lfOnOperationCompleted(ASender: TObject; const mess: WideString);
begin
ShowMessage(mess);
end;

procedure TForm1.lfOnOperationCompletedNoArgs(ASender: TObject);
begin
ShowMessage('lfOnOperationCompletedNoArgs');
end;

Please let me know I am missing.

posted @ Thursday, October 20, 2011 6:38 AM by mtest


Please post delphi client code.

posted @ Thursday, October 20, 2011 6:40 AM by mtest


I'm on the road, and will be back home on Monday, the 24th. I'll look at this then.

posted @ Friday, October 21, 2011 9:23 AM by Hector Sosa, Jr


Hi,
thank you very much for taking the time to answer.
I'll be waiting. I would really appreciate your help as this issue seems to be a show stopper for my application.
Best regards,
m

posted @ Wednesday, October 26, 2011 12:23 AM by mtest


Arrrrggghhhh! I don't have Delphi installed anymore, nor do I know where I put the install CD. In the meantime, make sure that the signatures for the events match the signature of the handler. Events with no parameters will have the following signature; procedure Barked(Sender: TObject); Events with parameters will have a very different signature. I remember fighting with this.

posted @ Wednesday, October 26, 2011 6:33 PM by Hector Sosa, Jr


Ok, I meant to say the signature will look like this; FOnBark: TNotifyEvent; TNotifyEvent translates into TObject in the handlers.

posted @ Wednesday, October 26, 2011 6:37 PM by Hector Sosa, Jr


Here is the complete Delphi Code:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComDog_tlb;

type
TForm1 = class(TForm)
Memo1: TMemo;
ButtonBark: TButton;
ButtonHowl: TButton;
ButtonEat: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ButtonBarkClick(Sender: TObject);
procedure ButtonHowlClick(Sender: TObject);
procedure ButtonEatClick(Sender: TObject);
private
{ Private declarations }

protected
procedure Barked(Sender: TObject);
procedure Howled(Sender: TObject);
procedure Ate(Sender: TObject);

public
{ Public declarations }
end;

var
Form1: TForm1;
TestDog: TDog;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
TestDog := TDog.Create(self);
TestDog.OnBark := Barked;
TestDog.OnHowl := Howled;
TestDog.OnEat := Ate;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
if (TestDog <> nil) then
TestDog.Free;
end;

procedure TForm1.ButtonBarkClick(Sender: TObject);
begin
TestDog.MakeDogBark;
end;

procedure TForm1.ButtonEatClick(Sender: TObject);
begin
TestDog.MakeDogEat;
end;

procedure TForm1.ButtonHowlClick(Sender: TObject);
begin
TestDog.MakeDogHowl;
end;

procedure TForm1.Barked(Sender: TObject);
begin
Memo1.Lines.Add('Barked');
end;

procedure TForm1.Howled(Sender: TObject);
begin
Memo1.Lines.Add('Howled');
end;

procedure TForm1.Ate(Sender: TObject);
begin
Memo1.Lines.Add('Ate');
end;

end.

posted @ Thursday, November 03, 2011 12:51 PM by Gopala


Valuable article! My personal hair-pulling and banging-head-against-the-wall moment:
1. Events that originated in Delphi code worked fine (Delphi -> C# COM -> Delphi)
2. Events that originated in C# code where not routed to Delphi (C# app -> C# COM -> Delphi)
I solved it by descending the class Dog from ServicedComponent:
using System.EnterpriseServices;
public class Dog : ServicedComponent
{
}
Other articles advice to descend from MarshalByRefObject, but that did not work for me.

posted @ Monday, February 20, 2012 9:50 AM by Jan Wicher


posted @ Monday, February 18, 2013 7:49 AM


Name (required)

Email (required)

Website

CAPTCHA image
Enter the code shown above:

  
Terms Of Use | Privacy Statement | SystemWidgets
Copyright 2002-2014 by SystemWidgets
Google Analytics Alternative