Click here to Skip to main content
15,868,164 members
Articles / Programming Languages / Perl
Article

Callback Functions and .NET C# COM Components

,
Rate me:
Please Sign up or sign in to vote.
4.36/5 (7 votes)
1 Feb 2008CPOL3 min read 100.9K   873   30   19
How to make your C# component callback a Perl subroutine

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:

C#
// 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:

C#
//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:

C#
[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.

C#
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)


Written By
Technical Lead
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Written By
Software Developer (Junior) Mediatek Inc
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralWindows 7 [modified] Pin
Saamry22-Sep-10 10:11
Saamry22-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 7 Pin
Bharat Mallapur24-Sep-10 22:12
Bharat Mallapur24-Sep-10 22:12 
GeneralRe: Windows 7 Pin
Saamry28-Sep-10 11:22
Saamry28-Sep-10 11:22 
GeneralRe: Windows 7 Pin
Bharat Mallapur29-Sep-10 1:33
Bharat Mallapur29-Sep-10 1:33 
QuestionHow to make COM visible to others Pin
prashant_14430-Jul-09 2:07
prashant_14430-Jul-09 2:07 
AnswerRe: How to make COM visible to others Pin
Bharat Mallapur30-Jul-09 20:21
Bharat Mallapur30-Jul-09 20:21 
GeneralRe: How to make COM visible to others Pin
prashant_14430-Jul-09 21:01
prashant_14430-Jul-09 21:01 
GeneralRe: How to make COM visible to others Pin
Bharat Mallapur3-Aug-09 1:51
Bharat Mallapur3-Aug-09 1:51 
GeneralIn C++ Pin
jarekf1-Jul-09 8:12
jarekf1-Jul-09 8:12 
GeneralRe: In C++ Pin
Bharat Mallapur1-Jul-09 23:08
Bharat Mallapur1-Jul-09 23:08 
GeneralRe: In C++ Pin
jarekf2-Jul-09 6:30
jarekf2-Jul-09 6:30 
GeneralRe: In C++ Pin
Bharat Mallapur7-Jul-09 1:00
Bharat Mallapur7-Jul-09 1:00 
GeneralIn Javascript Pin
Navneet Hegde18-Mar-09 6:27
Navneet Hegde18-Mar-09 6:27 
GeneralRe: In Javascript Pin
Bharat Mallapur18-Mar-09 19:34
Bharat Mallapur18-Mar-09 19:34 
GeneralRe: In Javascript Pin
Navneet Hegde18-Mar-09 20:51
Navneet Hegde18-Mar-09 20:51 
GeneralMFC client Pin
manchukuo24-Nov-08 6:07
manchukuo24-Nov-08 6:07 
GeneralRe: MFC client Pin
Bharat Mallapur1-Jul-09 23:13
Bharat Mallapur1-Jul-09 23:13 
GeneralHelp with C# client Pin
alon198026-Mar-08 10:42
alon198026-Mar-08 10:42 
GeneralRe: Help with C# client Pin
Bharat Mallapur1-Apr-08 2:37
Bharat Mallapur1-Apr-08 2:37 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.