Click here to Skip to main content
Click here to Skip to main content

Callback Functions and .NET C# COM Components

By , , 1 Feb 2008
 

Introduction

Recently, my co-author had a requirement where he needed to convert a C# class library into a COM compatible component which could be called from either a C++/C#/Perl/Scripting environment. There was already a lot of information on how to implement this, but he had a somewhat more involved requirement. The requirement was that his Perl script needed to call a C# function (say Analyze()). The Analyze() function would typically take a long time to process, and the client needed to be updated regularly with the progress counter. This would be best solved using Callbacks.

However, there was scanty information available in a ready-to-use form, which could explain how to achieve this.

The article below explains how to handle multiple .NET events in a Perl environment.

Background

This article is an attempt to explain the concept of Event Sources and Event Sinks as applicable to a COM client.

The code contains a C# COM Server, and a sample Perl Client which explains in the simplest manner possible, the concept of tapping the power of callback functions.

This article does the above by showing how to use the Win32::OLE module present in Perl, and using it to trap events generated in the .NET component.

Using the Code

To use the code attached, there are a few steps that need to be performed.

  1. Unzip the CSharp_and_Scripting_New_src.zip.
  2. Browse to the CSharp_and_Scripting_New\CallBack Server\CallBack folder.
  3. Run register.bat
    1. In case the register.bat script does not work for you, open the project CallBack.sln in Visual Studio 2005. Go to the Tools Menu -> Visual Studio 2005 Command Prompt.
    2. Change directory in the command window to the CSharp_and_Scripting_New\CallBack Server\CallBack\bin\debug folder.
    3. Type regasm CallBack.dll /tlb:Callback.tlb
    4. type gacutil -i CallBack.dll
  4. Now that the .NET C# COM server is registered in your system, you can run the CSharp_and_Scripting_New\CallBack Client\Sample1.pl to view the output as shown below:

    CSharp_and_Scripting_New_Snapshot.png

Description

COM Server Code

The COM server has been written in C#. C# does not have any direct concept of function pointers. Instead, it defines something called delegates which is a safe form of a reference to a function. Now to tap the power of delegates, let us define some delegates as shown below in our class DotNetEventSender:

// No need to show this delegate to COM
[ComVisible(false)]
public delegate void MyEventTarget(string msg);
[ComVisible(false)]
public delegate void MyEventTarget2(int nTimerCounter);

public event MyEventTarget TheEvent;
public event MyEventTarget2 TheEvent2;  

As you can see above, we've declared two delegates of different types and associated two events with these delegates. Now let us declare an interface which our class will expose:

//This interface defines purely the events.
//DotNetEventSender should implement this interface with the
//ComSourceInterfaces() attribute to become an Event Source.
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

public interface CallBackEventInterface
{
    [DispId(1)] void TheEvent(string msg); 
    [DispId(2)] void TheEvent2(int nTimerCounter);
}  

Our class will then expose this interface as shown below:

//Identifies these interfaces that are exposed as COM event sources 
//for the attributed class.
[ComSourceInterfaces(typeof(CallBackEventInterface))]

//Tells the compiler not to generate an interface automatically and that we are 
//implementing our own interface (IDotNetEventSender)
[ClassInterface(ClassInterfaceType.None)]
public class DotNetEventSender:IDotNetEventSender
{ ... 

Notice the attribute tag:

[ComSourceInterfaces(typeof(CallBackEventInterface))] 

This tells the compiler that we are interested in exposing an event source for our class. The event sink will be then implemented in the Perl Client in a package which is expressly consuming (handling) all events generated by our C# server.

Now, it's not much use exposing an Event source, if we don't also implement some server functions that could be called by the Perl client. For example, the Perl client would call the function Run() which will start the timer in the C# server. This timer would then intermittently fire the event sources, which could then be handled in the Perl client.

So, let's now implement some class functions in the C# server.

private int m_nLoopCounter=0;
public void Run()
{
        Console.Out.WriteLine("Inside Run");
        //Note that the event handlers will be specified in the client
        //(e.g. Perl Script)     
 
        //start a timer which call the Events every one second.
        System.Timers.Timer progress = new System.Timers.Timer(1000);
        progress.Elapsed += new ElapsedEventHandler(TimerFunction);           
        progress.Start();
}

private void TimerFunction(object source, ElapsedEventArgs e)
{
        m_nLoopCounter++;
        TheEvent("Hello, World!");
        TheEvent2(m_nLoopCounter);
} 

Perl Client Code

In the Perl client code, the basic step is to create an object of the C# server as shown below:

my $TM = Win32::OLE->new('CallBack.DotNetEventSender');

Once this is done, we want to inform the C# server that we are interested in receiving notifications when events of interest are generated on the server. We do this by stating:

Win32::OLE->WithEvents($TM, 'MyEvents');

Here, we are informing the C# server that all events should be forwarded to the package MyEvents. This package will define subroutines with names that are exactly the same as the events in the C# server.

package MyEvents;

#The name of the subroutine should be the exactly the same as the name of the
#event in the C# component.

sub TheEvent 
{
        my ($obj,$args) = @_;
        print "TheEvent() : ".$args."\n";
}

sub TheEvent2 
{
    my ($obj,$args) = @_;
    print "TheEvent2(): Timer Fired Count: ".$args."\n";
} 

Now, it's not much use defining all the above code if we don't run a loop, which will keep the Perl client waiting for event messages to arrive. To do this, we add the code to the very end of the main package as shown below:

#keeps the Perl client active, processing the event messages as they
#occur repeatedly. 

Win32::OLE->MessageLoop(); 

That's it, folks! Just run the sample1.pl to see the above logic in action. Happy 'Interop'ing! :-)

History

  • 1st February, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Authors

Bharat Mallapur
Technical Lead
India India
Member
No Biography provided

sriraj
Software Developer (Junior) Mediatek Inc
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralWindows 7 [modified]memberSaamry22 Sep '10 - 10:11 
Thanks for the article! It addresses the issue i have.
 
However, it is not working for me on windows 7. Anyone had a chance to use this code on Windows 7?
 
I did create a command window using 'Run As Administrator' option. I was able to execute the commands (gacutil, and regasm) provided in the article correctly. However, when running perl code, i get the following error.
 
Win32::OLE(0.1709): GetOleObject() Not a Win32::OLE object at Sample1.pl line 34.
Can't call method "PrintHello" on an undefined value at Sample1.pl line 36.
Error creating the .NET COM Server: Win32::OLE(0.1709) error 0x80040154: "Class
not registered"
 

 
Many Thanks!

modified on Wednesday, September 22, 2010 4:26 PM

GeneralRe: Windows 7memberzorrothefox24 Sep '10 - 22:12 
Hi,
 
I think this might be an issue of Perl installation not being a 64 bit version (?). If you have installed a 64 bit version of Windows 7, you will have to install the perl 64 bit version.Below is a link from one guy who seemed to have the exact same problem...
 
Please do post back if this solves the problem! Cool | :cool:
 
http://www.issociate.de/board/post/405403/Using_Win32::OLE_on_64_Bit_Windows.html[^]
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralRe: Windows 7memberSaamry28 Sep '10 - 11:22 
Thanks for the response.
 
I do have 64 bit Windows 7, and i did install the 64 bit Perl on the system.
However, i had to use the specifically the 'regasm' from the 64 bit framework.
So, specifically, use the "C:\Windows\Microsoft.NET\Framework64\v2.0.50727\regasm CallBack.dll /codebase". (I used codebase option for my own use).
 
I hope it helps other folks.
 
Thanks!
GeneralRe: Windows 7memberBharat Mallapur29 Sep '10 - 1:33 
Great! Thanks for posting the info back here... It will definitely help others! Smile | :)
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

QuestionHow to make COM visible to othersmemberprashant_14430 Jul '09 - 2:07 
Hi,
It is superb.
Just i have some doubt please clarify me.
 
I am creating a ActiveX control in c#.
 
for that i am started with Windows Control Library in VS2005.
I am defined some function on that classes.
After compiled i get my test.dll.
 
Now when i adding this control in VB it is saying that it is not com.
Even it is also giving error while register on System32.
 
What more step should i do to make a COM compatible.
Please help me.
Or give me any good link for that.
 
adad

AnswerRe: How to make COM visible to othersmemberzorrothefox30 Jul '09 - 20:21 
Hi,
 
From what I see, it is very easy to create an ActiveX control in .net... To make it COM compatible, you need to implement some interface to the outside world that will allow the client to get/set values in your controls.
 
please refer to these links below...
 
http://dotnetslackers.com/articles/csharp/WritingAnActiveXControlInCSharp.aspx[^]
 
http://www.c-sharpcorner.com/UploadFile/dsandor/ActiveXInNet11102005040748AM/ActiveXInNet.aspx[^] for more info...
 
Hope this helps... Please post back links and feedback when you successfully solve your problem... Share the wealth... Smile | :)
 
Bharat
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralRe: How to make COM visible to othersmemberprashant_14430 Jul '09 - 21:01 
Hi Bharat,
Thanks for your quick reply.
I will see that link.Its good and i am working on that.
 
But currently i have some more question.
1.While creating my ActiveX control i am using 5 more other dlls.
They are not com component.
Are my ActiveX works fine with those dll when call from Vb or other.
User not do any thing with other dlls they just add my activeX control.
 
Why i am asking this question because when i am adding a key.snk in my project then while compilation it is giving me error saying "The other dll's have not using any key". Because they are not COM.
 
It means i cannot use any third party dll on my ActiveX if that was not COM.Is it?
Thanks in advance.
 
adad

GeneralRe: How to make COM visible to othersmemberzorrothefox3 Aug '09 - 1:51 
Hi,
 
As I've never developed an ActiveX control, I don't know exactly what might be the problem here. my doubt is that the 5 other dlls are not strong-named, so the activex control is not allowed to use them.
 
Probably you can ask read the article below and post a question there, as the guy who's written this article seems to be more suited for this.
 
Create ActiveX in .NET Step by Step[^]
 
Hope this helps,
Bharat
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralIn C++memberjarekf1 Jul '09 - 8:12 
Any idea how to do the same in C++ client?
 
When try to hook like that:
 
__hook(&CallBackEventInterface::TheEvent, pSource, &CReceiver::MyHandler);
 
I get error: event source and event handler must have the same event type
GeneralRe: In C++memberzorrothefox1 Jul '09 - 23:08 
Hi,
 
I'm not sure exactly what you're doing here... Could you post a minimal sample source code which replicates what you're trying to achieve ? Also, what platform are you working on (obviously Windows, but which compiler, which project type, etc) would be helpful.
 
I'll try my best to help out in whatever way I can.
 
One general tip would be to use COM (CoCreateInstance, QueryInterface, etc) which would allow the C++ code to interact through COM. Please refer the books below for more info on what COM is and how to interface with it.
Inside COM - Dale Rogerson (Microsoft Press)
Essential COM - Don Box (Pearson)
.NET and COM Interoperability - Adam Nathan (Techmedia)
 
Thanks for posting... Please send me some more info/code so that I can try to understand exactly what you're trying to do here.
 
Hope this helps
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralRe: In C++memberjarekf2 Jul '09 - 6:30 
Hi,
 
Thanks for your response!
 
Below is one example of what I tried under Visual Studio 2005:
 
#import raw_interfaces_only
#import "..\\CallBack\\bin\\debug\\CallBack.tlb" no_namespace named_guids
[ module(name="EventReceiver") ];
[ event_receiver(com) ]
class CReceiver {
public:
HRESULT MyHandler2(long nValue) {
printf_s("MyHandler1 was called with value %d.\n", nValue);
return S_OK;
}
 
void HookEvent(IDotNetEventSender* pSource) {
__hook(&CallBackEventInterface::TheEvent2, pSource, &CReceiver::MyHandler2);
}
};
 
int _tmain(int argc, _TCHAR* argv[]) {
CoInitialize(NULL);
IDotNetEventSenderPtr pIM(__uuidof(DotNetEventSender));
pIM->PrintHello(); // This works!
CReceiver receiver;
receiver.HookEvent(pIM);
pIM->FireTheEvent();
//receiver.UnhookEvent(pIM);
CoUninitialize();
return 0;
}
 
And here is the error:
error C3731: incompatible event 'HRESULT CallBackEventInterface::TheEvent2(long)' and handler 'HRESULT CReceiver::MyHandler2(long)'; event source and event handler must have the same event type
 
I must admit I am not experienced in COM under C++.
 
Appreciate your help!
 
Jarek
GeneralRe: In C++memberzorrothefox7 Jul '09 - 1:00 
Hi Jarek,
 
Sorry that I couldn't reply earlier... The basic problem being killer schedule at work! Smile | :) I've not really written C++ based COM clients, so please forgive me if I'm being vague.
 
Here's an example c++ client using a COM dll http://www.csharphelp.com/archives/archive190.html[^]
 

Hope this helps at least in some measure. Sorry that I can't do more for you right now. But if you do find out what the problem is, it'd be really great if you could post back your findings on this thread.
 
Hope this helps,
Best of luck,
Zorro
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralIn JavascriptmemberNavneet Hegde18 Mar '09 - 6:27 
Hi
How can I use this dll in Javascript to catch event
 
Thanks!
 
Develop2Program & Program2Develop

GeneralRe: In Javascriptmemberzorrothefox18 Mar '09 - 19:34 
Hi Navneet,
 
I've not really used Javascript till now, but I think this post might help you...
 
http://www.mombu.com/programming/basic/t-c-usercontrol-in-ie-no-events-750471.html[^]
 

Also, the following link might be of use
http://www.codeproject.com/KB/cs/CreateActiveXDotNet.aspx?display=Print[^]
 
You can also try searching in google for the following keywords "comsourceinterfaces javascript"
 

Hope this helps,
Bharat
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralRe: In JavascriptmemberNavneet Hegde18 Mar '09 - 20:51 
Thanks Zorro.. working on it will let you know soon.
 
Thanks!
 
Develop2Program & Program2Develop

GeneralMFC clientmembermanchukuo24 Nov '08 - 6:07 
Hi guys great job in this article!
 

How can catch the Callbacks in MFC client?, i am new to COM, i have read some documentation but i just don´t know how to implement it
 
Thanks
GeneralRe: MFC clientmemberzorrothefox1 Jul '09 - 23:13 
Sorry for the late reply... Didn't get a notification for this query post. Still, for those who might refer this thread, I'm posting the general info that I can post...
 
One general tip for any client not written in .NET or a scripting language (which is described in the article above) would be to use COM (CoCreateInstance, QueryInterface, etc) which would allow the client code to interact through COM. Please refer the books below for more info on what COM is and how to interface with it.
Essential COM - Don Box (Pearson)(What COM is, and what it's trying to achieve)
Inside COM - Dale Rogerson (Microsoft Press) (More info on what you need to do to use COM)
.NET and COM Interoperability - Adam Nathan (Techmedia)
(Superb book, my best reference for actually making my c# code COM usable)
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

GeneralHelp with C# clientmemberalon198026 Mar '08 - 10:42 
Hello,
very good article!
I want to to something like this but implement a C# based client that will consume the events.
How can i do it? can you show me a basic template or code sample to this?
 
thanks in advance!!!
Alon
GeneralRe: Help with C# clientmemberzorrothefox1 Apr '08 - 2:37 
Hi alon,
Doing the same for a c# client should be pretty simple. Add a reference to the dll. Since the events are public, you could add your handlers to the event like below. This would register your event handlers with the events of DotNetEventSender.
 
your code below....
*****************
 
class myclass
{
    //constructor
    myclass()
    {
        CallBack.DotNetEventSender dnes = new CallBack.DotNetEventSender();
        RegisterEventHandlers();
    }
 
    int RegisterEventHandlers()
    {
        dnes.TheEvent += this.TheEventHandler(); //registers your handlers with the dll events
        dnes.TheEvent2 += this.TheEvent2Handler();
    }
 
    void TheEventHandler(string msg)
    {
        ...
    }
 
    void TheEvent2Handler(int nTimer)
    {
        ....
    }
}
 

*********
 
Hope this helps,
Bharat
 
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
-- The Walrus and the Carpenter (by Lewis Carroll)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 1 Feb 2008
Article Copyright 2008 by Bharat Mallapur, sriraj
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid