CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

August 2005 - Posts

  • COM, COM events, .NET and some Delphi

    At first sight creating COM clients and servers with .NET looks like a snap. As I recently found out implementing full COM event support has some quirks. Each of the major .NET languages has its own. When it comes to VB.NET it appears to be very hard (if not impossible) to dynamically attach or detach an event handling routine. When it comes to C# the only problem was in the end the documentation. In a comment savage mentions problems he has with responding to COM events from a Delphi client. In this post I'll run through some essential COM fundamentals and discuss how to use these to get COM interop working with a focus on C# and Delphi. My background lies in Delphi, working with COM in Delphi brought me to .NET.

    COM (Automation)

    An essential aspect of COM is the separation of interface and implementation. An interface defines a number of methods and properties but does not provide any implementation. A class has members with implementing code. Members are made available to objects instantiated from the class or in interfaces extracted from these objects. A COM object makes members available to COM clients by publishing interfaces. One COM class can implement any number of interfaces. C#, VB.NET and Delphi have a very natural syntax for this

        [Guid(FileWatcher.InterfaceId)]
        public interface IfileWatcher
        {
            int Watch(string dirName, string filter);
        }

        [Guid(FileWatcher.InterfaceId2)]
        public interface IfileDestroyer
        {
            bool EraseFile(string dirName, string filter);
        }


        [Guid(FileWatcher.ClassId)]
        [ClassInterface(ClassInterfaceType.None)]
        [ComSourceInterfaces(typeof(IfileWatcherEvents))]

        public class FileWatcher : IfileWatcher, IfileDestroyer
        {

            public int Watch(string dirName, string filter)
            {
                // Your code here
            }

            public bool EraseFile(string dirName, string filter)
            {
                // Your code here
            }
     

    The COM class FileWatcher implements the IfileWatcher and the IfileDestroyer COM interfaces. In C++ you need the dreaded mechanism of multiple inheritance to get that done.

    For the COM client the only way to get to the members of the COM object is via one of its interfaces. In essence a COM interface is a contract which the supplier of the interface promises to fulfill. The implementation of the methods and properties of the interface is hidden to the user of the interface. The COM interfaces are well described so any piece of software, be it a class library or a Word processor, can implement them. The base interface of all COM interfaces is Iunknown which has these members

    • AddRef
    • Release
    • RefCount
    • QueryInterface

    The first three deal with the lifecycle management of the COM object which implements the interface. In the CLR of .NET there is the garbage collector which takes care of this; in Delphi the VCL (the Delphi class library, comparable to the .NET FCL) manages the lifecycle of the COM objects. Which works pretty well but sometimes your object has been destroyed while COM client still tries to access it and more often a COM object is never destroyed while there are no longer any clients using it. In C++ it's always the programmer himself who is responsible for managing the object lifecycle. It is game you will always lose. Be happy with the .NET garbage collector and forget about these methods.

    QueryInterface is quite an interesting method. In short it is a way to get any COM interface the object implements. In the case of the snippet above IfileWatcher or IfileDestroyer. When talking about COM, you're usualy talking about Automation, based on the IDispatch interface. The FileWatcher class in the snippet does provide an implementation of this interface. You don't have to state this explicitly, the ClassInterface attribute takes care of that. Idispatch adds these members to the IUnknown interface

    • GetTypeInfoCount
    • GetTypeInfo
    • GetIDsOfNames
    • Invoke

    The first two methods are a way to get to type information describing the specific methods and properties of the interface. These methods are used when a COM reference is added to a .NET project. Out of the type information a .NET proxy class is built which has class members for all COM interface members found. In your .NET code you program against objects instantiated from these classes. The proxy object will pass the invocation to the actual COM object. When you register a .NET class for COM interop the COM type information for your class will be included in the assembly.

    A less sophisticated client, like VBscript, does not use all this type information. It follows a brute force way using the GetIDsOfNAmes and Invoke members. The first method takes a plain string containing the name of the desired method or property. If the interface has a member with that name it returns a DispID, which is a plain number. This DispID is passed to the Invoke method, together with an array of parameters to the method. The Invoke method invokes the method on the actual COM interface itself.

    There are three ways of invoking members on a COM interface

    1. Late binding. This is the way VBscript works. It has a lot of overhead and is very much prone to error. Any spelling mistake in a member name is not noticed until it's line of code executed. It is also very flexible. You can import code which does almost anything (which can be pretty bad as many a virus has proven) in plain text.
    2. Early binding. In early binding the GetIDsOfNames step is skipped. The compiler knows the DispId's of the intended members and passes them, with an array of parameters, to the Invoke method. This is the way classical VB works. It still has quite a lot of overhead but passing all parameters in one array makes optional parameters a snap.
    3. Vtable binding. This is the way .NET works. Out of the knowledge found in the TypeInfo the client has sufficient knowledge of the methods of the interface and their signatures. Your code (the proxy) directly invokes the methods. This has very little overhead, the performance difference between an in-process COM server and a "normal" DLL is hardly measurable. The down side is that you always have to pass every parameter. In the methods of some automation servers, like those in MS-Office, this list can be very long.

    Events

    So far we have been talking about COM servers, the piece of software providing an interface to some desired functionality; and COM clients, the consumer of the functionality. When it comes to VS.NET versus (classical) Delphi there is no great difference between the tools. In both you import a COM server and the tools will generate a proxy class which can be coded against as if it was just another object. When it comes to COM events things get different.

    COM events enable a COM server to notify a COM client. The server does this by invoking a method on the client. To be able to do that the COM service needs a COM interface implemented by the client. So to receive notifications the client has to pass an interface to the server. Now the client has an interface to the server to invoke the servers methods and the server has an interface to the client to invoke notifications. In such a scenario the terms client and server are somewhat confusing. A COM server which can fire (sink) events is called a connectable object. The COM client is said to pass an eventsink to the server. An eventsink is an IDispatch interface. The connectable object will invoke its members using late binding; that is it uses the interface's Invoke method.

    Consuming COM events

    .NET does a great job in setting up an eventsink to a connectable object. The most elegant way is in C#. A delegate type describes a method signature, that is the parameters and the return type of the method

    public delegate void NewFile(string fileName);
     

    The generated proxy class of a connectable COM object has eventhandler members. A delegate type describing the signature of the event is also generated. Now you can create delegate objects and add these to the eventhandler. In the constructor of the delegate you pass the event handling method which should have a signature which matches the delegate.

    private void Form1_Load(object sender, System.EventArgs e)
    {
        fw = new WordUtils.FileWatcher();
        fw.OnNewFile+=new WordUtilsVB.FileWatcher.OnNewFileEventHandler(fw_OnNewFile);
        fw.Watch(@"C:\USR", @"*.doc");
    }

    private void fw_OnNewFile(string fullFileName)
    {
        textBox1.Text = fullFileName;
    }
     

     In VB.NET hooking up eventhandlers can be done on several ways but behind the scenes the generated code does almost the same as C#.

    Consuming COM events in a Delphi client is far more complicated. In Delphi 4 and 5 you had to create the eventsink all by hand. Binh Lyh's tool does the most cumbersome part for you. In Delphi 6 Borland added native support, how well this works is beyond my experience. In this Delphi story you will find a deeper discussion on consuming COM events in a Delphi client.

    Adding event support to your COM servers

    Before diving into the implementation details we need a small background. According to the COM specification a connectable object can notify any number of clients by invoking methods on the client's eventsinks. An eventsink is a dispatch interface which has any number of members. According to the specification a connectable object can support multiple types of eventsinks. Each sink (interface) type has its own set of methods. A connectable object should implement the IconnectionPointContainer interface which contains ConnectionPoints. For each sink type there is a connectionpoint. which is a collection of the eventsinks passed by the connectable object's clients. So the connections are a collection of collections.

    Implementing this in C# is a breeze. The FileWatcher class in this snippet supports two types of eventsinks: IfileWatcherEvents and IfileWatcherEvents2..

        [Guid(FileWatcher.EventsId)]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IfileWatcherEvents
        {
            void OnNewFile(string fullFileName);
            void OnNewDir(string dirName);
        }

        [Guid(FileWatcher.EventsId2)]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IfileWatcherEvents2
        {
            void OnFileRename(string oldName, string newName);
        }

        [Guid(FileWatcher.ClassId)]
        [ClassInterface(ClassInterfaceType.None)]
        [ComSourceInterfaces(typeof(IfileWatcherEvents), typeof(IfileWatcherEvents2))]

        public class FileWatcher : IfileWatcher
        {
            internal const string ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69";
            internal const string InterfaceId = "959A6835-4768-4cd5-89BE-7D408C2B86AA";
            internal const string EventsId = "02E3954F-5DC1-4ad9-9167-354F0E82BCA3";
            internal const string EventsId2 = "C580BD8C-0CD8-4b85-9193-C5DB6CDD5183";

     

    The two eventsinks are declared as Idispatch interfaces, the ComSourceInterfaces attribute registers these connection points.

    In Delphi you can also create automation objects which sink events. In the IDE wizard to create a new COM class there is a checkbox to generate "event support code". Alas COM objects built this way are very limited in their use in .NET. When you inspect the generated code as well as the VCL source code you will find out that the Delphi implementation of IconnectionpointContainer supports exactly one eventsink. In a .NET client an eventsink is wrapped up in a delegate object. The moment you hook in a second eventhandler the connectable object is passed a second event sink. As it has no place to store that the handling of its events just does not work. As a solution to that I inherited from the Delphi automation base class to support multiple sinks. Read the full story on that here, That class does not support multiple sink types. At the time I was working my way through COM in Delphi, connected a C# client and it was love at first sight. The Delphi language was (and is) great. But even the (at that time) beta Visual Studio seemed the way to continue. I never regretted that.

    But I don't want to come down to hard on Delphi. The code found may have seemed sloppy, but it wasn't Borlands own code. Event support was added to Delphi when they added base classes for ActiveX controls to the framework. The automation base classes use code straight from the ActiveX control helper classes. And all that code is a straight port from the C++ code in the Microsoft book OLE Controls Inside out. This time you may blame the dark side.

  • The C# event keyword is an access modifier for delegate members

    Recently I had trouble getting COM events to work in a COM automation server written in C#. A visitor's comment taught me that I was one keyword away from my goal. The event keyword. The MSDN documentation on that is not clear at all. Browsing around on the web I found a very beautiful story which was an eyeopener. Let me me sum things up.

    I had written a small COM add-in for Word which watched the filesystem. Every time a new file was created the add-in directed Word to open the file. The first simple version in VB.NET worked like a snap. Creating a version which could watch multiple directories I ran into trouble. To recapitulate  the source code (this one does work)

    using System;
    using System.IO;
    using System.Collections;
    using System.Runtime.InteropServices;

    namespace WordUtils
    {
        [Guid(FileWatcher.InterfaceId)]
        public interface IfileWatcher
        {
            int Watch(string dirName, string filter);
        }

        [Guid(FileWatcher.EventsId)]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IfileWatcherEvents
        {
            void OnNewFile(string fullFileName);
        }

        [Guid(FileWatcher.ClassId)]
        [ClassInterface(ClassInterfaceType.None)]
        [ComSourceInterfaces(typeof(IfileWatcherEvents))]

        public class FileWatcher : IfileWatcher
        {
            internal const string ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69";
            internal const string InterfaceId = "959A6835-4768-4cd5-89BE-7D408C2B86AA";
            internal const string EventsId = "02E3954F-5DC1-4ad9-9167-354F0E82BCA3";

            private ArrayList watchers = new ArrayList();

            private FileSystemWatcher watcher;

            public int Watch(string dirName, string filter)
            {
                watcher = new FileSystemWatcher(dirName, filter);
                watcher.EnableRaisingEvents = true;
                watcher.Created += new FileSystemEventHandler(watcher_Created);
                watchers.Add(watcher);
                return watchers.Count;
            }

            public event NewFile OnNewFile;

            private void watcher_Created(object sender, FileSystemEventArgs e)
            {
                if (e.Name.Substring(0,1) != "~")
                    OnNewFile(e.FullPath);
            }
        }

        [ComVisible(false)]
        public delegate void NewFile(string fileName);
    }
     

    The code defines a delegate type NewFile and has the OnNewFile property of this type. For a small background on COM events see this post. The eventhandler did show up in the Word VBA code but trying to instantiate the add-in 459-errors popped up. The essential difference in the code presented here is prefixing the delegate property with the event keyword.

    To see what this keyword does took me some browsing on the web and a little experimentation. The ultimate source was a post on Julien Couvreur's blog (Curiosity is a bliss, what a title !) C# events vs. delegates. A delegate property (like the OnNewFile member of my FileWatcher class) is an eventhandler that will notify all delegate objects which have subscribed. Delegates are pretty strange creatures, just look what intellisense will show on one:

    That's quite a lot. You can do pretty heavy things here, like getting to all code which has subscribed to receive notifications. Looking at delegate property this way it doesn't surprise me that publishing it via COM  will lead to trouble.

    That is where the event keyword comes to the rescue. It turns the delegate field into a property and hides all members of the delegate except addhandler (+=) and removehandler (-+). These are the only two you need, one to subscribe and one to unsubscribe from receiving notifications. Having applied the keyword my COM add-in worked just fine.

    I can really recommend reading the full story yourself. Although there is one comment I would like to make. It states that .NET restricts the signature of delegates on which the event keyword can be applied to that of a System.Eventhandler, that is

    public delegate void EventHandler(
       object sender,
       EventArgs e
    );

    I have used several signatures (my NewFile delegate has one string parameter) tried several scenarios and everything worked as intended. Imho it would make no sense for a COM event to have this signature. The sender and the eventargs are parameters which make a lot of sense inside .NET but to a COM client written in (for instance) Delphi they have no meaning at all.

    In my original post I was a little disappointed in C# and had to praise VB.NET for producing a working add-in. Later on I discoverd that VB.NET did produce the same problem. Now I have learned how to do it right in C#. But I cannot give you a solution for VB.NET. There is so much more I have to learn. I'll continue to grow up in public.

  • Lego, Components, Denmark and Computer Languages

    This holiday my family re-visited the original LegoLand amusement park, next door to the Lego factories in Billund Denmark. It was just great. My kids, as well as their mum and dad, were Lego aficionados already and this visit further increased our addiction. The word Lego is an abbreviation of lek god (forgive my Danish) which means play good.

    Building software from components is often compared to building an application from Lego bricks. There are indeed quite a number of similarities:

    • The number of different building blocks is limited. Over the years the number has grown, but most new parts are variations of existing bricks.
    • The number of interfaces to click bricks together is very limited. There is only one size of hole and knob.
    • The bricks have very distinct ratios. Because of the universal "click-together-interface" the number of ways you can click bricks together is gigantic

    Visiting LegoLand made me aware of another similarity. The park has a high variety of themes. Central are adventures and stories you play with creations built from Lego. Sets often come with little story books which remind me of story cards in extreme software development methodologies. The essence is not about the components, it's how the components play a part in a good story.

    Besides Lego Denmark is world famous for great Computer Language creators. Both Anders Hejlsberg and Bjarne Stoustrup were born and educated in Denmark. Before (amongst other things) creating C# Anders made Pascal into Object Pascal. Bjarne created C++ out of of C. So both created object oriented languages which were meant to be productive. Not about metaphors of sending messages but about interfaces and (hidden) implementations. Exactly what you need to build components. Is it coincidence that such a small country (Denmark has only 5.5 million inhabitants) "produces" several influential software creators ? I don't quite think so. A culture of great toys and a good education system makes it child's play.

  • Closed for "summer"

    Me and my family are off for our summer holiday. Alas the weather is not looking to good. My Vista tablet is coming with me. Installed the WinFX SDK and can toy around in solitary confinement, there's no internet there. It's not work, when it comes to the tablet curiosity is my driving force. An anonymous visitor left a very valuable tip on solving my COM event problems. Thank you. But that is work and will have to wait till the end of this month. As the tablet is also our portable DVD player my kids will make sure I'll spend enough time with my books (not on IT) as well. We'll all be praying for beach weather.

    See you in September.

    Posted Aug 10 2005, 01:01 PM by pvanooijen with no comments
    Filed under:
  • The PDC, too hot to handle

    Don't take this to serious, these are just some nice remembrances of the PDC 2003. Let me be the devils advocate for one moment. Why would you go to the PDC ? Last time it was hard to get to LA with fierce fires surrounding the city. Having made it other things heated the atmosphere, like beamer airstreams:

    Which reminded me of gathering round a campfire. Just wait for Don and Chris to show up with a guitar and start singing your favorite evergreens. (In the link that's at end of the show)

    You didn't have to visit the MSDN TV site to watch TV, when the session was completely overcrowded you could watch it in the corridor.

    Just staring at an attribute of a method without implementation....

    But don't let this scare you, it was a fantastic conference ! Go and visit this edition, seats still available. What I am also still pretty serious about is this :

    blogging my way to pdc

  • Vista is a big push for the Tablet PC. But it's not a full tablet yet...

    My first install of Vista was on a Virtual PC. Nice but my main problem was that it's sluggish and unresponsive. Even if it has a 2.Ghz Hyperthreaded CPU and a full gigabyte of RAM at its disposal. The cause of that, as discussed here on Joel on Software is Virtual PC's graphics driver. Vista relies on DX and Virtual PC's emulated S3 doesn't support that. I decided to give Longhorn another spin on my Tablet PC. Not virtual but as a dual boot so it could work with some real hardware. You need an empty NTFS partition to install Vista and my tablet comes with FAT32. I'm not going to tell you the full story how I got two NTFS partitions (one to keep XP, one for Vista), it's something I would like to forget as  soon as possible. But I do want to thank my neighbour for salvaging things when it had really gone bad. After that installing Vista itself was no big problem, it just takes patience. I got the network (both fixed and wireless) up and running with the XP drivers.

    Vista running directly on the Tablet hardware behaves very well. Despite the machine's moderate specs (1 Ghz CPU and 512Mb's of RAM) it is responsive and a pleasure to work with. The good thing is that Vista does recognize the tablet pen straight out of the box. It is a pointer and click-and hold works fine. After installing the driver the logon button (which is by definition an equivalent of pressing ctrl-alt-del) did exactly what it's supposed to do. But the tablet input panel (TIP) is not a part of the beta; it is available to registered beta testers. I'm trying to get my hands on it.

    There are no tablet applications like Windows journal and the like included yet. To see what does work I set up development tools. Installing the VS 2005 beta had a small problem with MsXML. Dave Glover had several solutions at hand. The first one, to install the msxml6.msi by hand before installing VS worked like a charm. To start with the classical way of developing tablet apps the Tablet PC SDK came next. As a first test I took my sample on handwriting recognition. Which, after some small modifications, built fine. Running it shows the Tablet parts missing in Vista:

    Scribbling with the pen works, but there are no handwriting recognizers installed. Trying my gesture recognizer sample showed that also the gesture recognizer isn't part of Vista. But using the SDK is not really the way to go for Vista Tablet development. The API's have become part of the Avalon Presentation layer. Here comes XAML ! More on that later.

    The Beta 1 release Vista did also inspire several bloggers. To name the most interesting one's for developers :

    Subscribed !

    The Vista tablet is far from complete but looks very promising. I can't wait for the moment my Tablet will be single boot again. Just Vista..

  • Past, present and future (my favorite PDC sessions)

    Last PDC, the 2003 edition, was one big flood of new things. It was the first time Longhorn (now named Vista) and Whidbey (now named Visual Studio 2005) were presented to the developer community. The coming, 2005 edition, will be on the same, by now almost today's, tools. There is a searchable list of sessions online. The majority is on the same products but this time based on solid beta1 (Windows Vista) and beta 2 (Visual Studio 2005) versions. The goods we received in 2003 were more spectacular than the crystallized reality of today. Remember the WinFS and ObjectSpaces buzzwords ? Those concepts are still there, a further evolution of the ideas will be at the PDC 2005, but it takes some browsing to find them.

    The session summaries are of a diverse quality. Some are technical vague marketing babble but most are really promising. There is one on the tablet Windows Vista ("Longhorn") Tablet PC: Advances in Creating Ink Enabled Applications, co- presenter Shawn van Ness has a  blog post on what they are going to do there. And to get in the mood the tablet team has started a blog. Subscribed !

    When it comes to the future I pre-selected some sessions (titles in bold, snippets from the summary in italics). Despite the avalanche on rich applications I want to hear ASP.NET: A Sneak Peek at Future Directions in Web Development and Designer Tools.  Let's see what the future brings for me and my simple mortal users. My summit will be the next three, where the old buzzwords are back in a new form. This time as clear programming languages and API's : C#: Future Directions in Language Innovation from Anders Hejlsberg. Anders can talk in a very easy and understandable way about quite abstract ideas. In this session he will talk about the elements in the next C# to create powerful APIs for expressing queries and interacting with objects, XML, and databases in a strongly typed, natural way. What MS wants to do with these APIs will be told in The .NET Language Integrated Query Framework: An Overview. The APIs will not be restricted to C#, also in VB.NET you will be able to use them. Today you write your code in a language of choice and access data using a SQLadapter, XMLdocument or plain object. Each of them has a specific way of interacting with the data, and much of the complexity in today's applications is the result of these mismatches. The "Orcas" release of Visual Studio aims to unify the programming models through integrated query capabilities After the overview I am ready for Using the .NET Language Integrated Query Framework with Relational Data  Database-centric applications have traditionally had to rely on two distinct programming languages: one for the database and one for the application. That's what the query framework will bridge. Using these advances, database queries that previously were stored as opaque strings now benefit from static type checking, CLR metadata, design-time type inference, and of course IntelliSense. Sounds like dBase on steroids :) Back to the future.

    Will I be able to see all this live? It depends..

  • Handles, AddHandler and RemoveHandler in VB.NET

    After a crash course comes sinking in. This post is a rewrite of yesterdays one on events in VB.NET. I will concentrate on VB.NET include Daniel and Chi's comment and after that I will return to my beloved C#.

    There are several ways to declare events in VB.NET. The usual one is using the handles keyword. As Daniel pointed out this can link an event handling method to multiple events.

        Private Sub MyClick(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click, Button2.Click
            ListBox1.Items.Add(String.Format("You clicked {0}", CType(sender, Control).Name))
        End Sub
     

    This code will run on a click of button1 as well as a click of button2. The coupling of the events and handler is declared in the method signature.

    You can also add an eventhandler dynamically form code.

        Private Sub MyOtherClick(ByVal sender As Object, ByVal e As EventArgs)
            ListBox1.Items.Add("That other click")
        End Sub

        Private Sub ButtonAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonAdd.Click
            AddHandler Button2.Click, AddressOf MyOtherClick
        End Sub
     

    Every time ButtonAdd is clicked it will add another event handler to Button2. When ButtonAdd is clicked 4 times a click of button2 will result in MyClick being executed first after which MyOtherClick is executed four times in a row.

    You can also dynamically remove eventhandlers. This is not limited to handlers been set dynamically. This removes the event handler which was set with the handles keyword.

        Private Sub ButtonRemove_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonRemove.Click
            RemoveHandler Button2.Click, AddressOf MyClick
        End Sub
     

    This is no big surprise as reflector had already shown that the Handles keyword translates to an addhandler statement.

    Chid pointed out that you do not need to use the WithEvents keyword on an object when it's eventhandlers are set using AddHandler. Reflector shows that this does not make any difference at all. Just like handles, withevents is nowhere to be found in the actual generated code. In fact it all boils down to AddHandler and RemoveHandler which map to CLR functions. In this post I've shown that you can freely mix Handles and AddHandler. In last post Reflector showed that there are some subtltties behind the scenes. These subtleties are enough to make or break the sinking of COM events. I'm gratefull for VB.NET fixing that for me. And a little sad for C# not doing what I had hoped to do. After all it's eventhandling syntax is imho far less confusing.

  • Handles versus Addhandler , a crash course in VB.NET event support (Error 459 revisited)

    In my previous post on the handling of COM events I described how I was driven into the arms of VB.NET. A VB.NET class published to COM sinks events as desired, a C# class pops up a mysterious error 459. With the help of feedback I've been delving a little deeper into this.

    Events are handled by event handling methods. The way to set these in C# is by adding a delegate object to the event :

           FileSystemWatcher watcher = new FileSystemWatcher(dirName, filter);
           watcher.Created += new FileSystemEventHandler(watcher_Created);

    ...

           private void watcher_Created(object sender, FileSystemEventArgs e)
           {
               // Do something
           }
     

    The usual way to set event handlers in VB is by using the handles keyword :

        Private WithEvents fw As New FileSystemWatcher
     

        Private Sub watcher_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles fw.Created
            'Do something
        End Sub
     

    The nice way about the C# syntax is that it's no problem to hook in multiple methods as event handler's to the same event:

        watcher.Created += new FileSystemEventHandler(watcher_Created);
        watcher.Created += new FileSystemEventHandler(anotherwatcher);
     

    You can do the same thing, in VB.NET. In my post I admitted not knowing how to do that, Sean pointed me to the syntax to get that done using the AddHandler keyword. Instead of using handles the code could be written  as

    Public Class Class1
        Private WithEvents fw As New FileSystemWatcher
        Private Sub watcher_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs)
            'Do something
        End Sub

        Public Sub New()
            AddHandler fw.Created, AddressOf watcher_Created
        End Sub

    End Class
     

    Using this syntax I found out my VB.NET COM class started popping up the same 459 errors as the C# class. Apparently I was on to something.

    Inspecting the actual code using Reflector shows that Handles and AddHandler have a subtle difference in the generated code.

    The disassembly of a class using handles

    constructor .ctor()

    Public Sub New()
    Me.fw = New FileSystemWatcher End Sub

    Property setter of the object whose events are handled

    <MethodImpl(MethodImplOptions.Synchronized)> _
    Private Overridable Sub set_fw(ByVal WithEventsValue As FileSystemWatcher)
    If (Not Me._fw Is Nothing) Then RemoveHandler Me._fw.Created, New FileSystemEventHandler(AddressOf Me.watcher_Created)
    End If Me._fw = WithEventsValue If (Not Me._fw Is Nothing) Then AddHandler Me._fw.Created, New FileSystemEventHandler(AddressOf Me.watcher_Created)
    End If End Sub

    The disassmbly of the class when it sets the event handler in the constructor

    Constructor .ctor

    Public Sub New()
    Me.fw = New FileSystemWatcher AddHandler Me.fw.Created, New FileSystemEventHandler(AddressOf Me.watcher_Created)
    End Sub

    Fw property setter

    <MethodImpl(MethodImplOptions.Synchronized)> _
    Private Overridable Sub set_fw(ByVal WithEventsValue As FileSystemWatcher)
    If (Not Me._fw Is Nothing) Then End If Me._fw = WithEventsValue If (Not Me._fw Is Nothing) Then End If End Sub

    The handles keyword does translates to an AddHandler statement. But instead of in the constructor this statement is in the property setter of the object whose event is handled and adds the delegate to the internal object instead of its property wrapper. Apparently this subtle difference is enough to provide COM with a good event support. It matches the somewhat cryptic description Although you might think you could sink the events from the implemented object, that isn't automatically the case  on msdn.

    The difference between C# and VB.NET is not the language itself but the code generation, I would not know of a way to alter that.

  • Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword

    I am a C# guy. This is a matter of personal preference,  I know how to code with VB.NET but just prefer curly brackets. So far the differences were not really worth the (sometimes quite) flaming discussion, after all we're all programming against the same framework. But recently I felt forced into using VB.NET for a part of a project. Let me explain what happened.

    At first sight creating COM servers with .NET is a snap. When you set Register for COM interop to true in the project options all public types and their public members are published via COM and can be used in VBscript or from VBA code in an Office application. Use the ComVisible attribute to hide a public member from COM.

    Imports System.Runtime.InteropServices
    Public Class MyfirstComClass
        Public Sub DoSomethingForYourCOMclient()
            ' Your code here
        End Sub

        Public Sub DoMore()
            ' More code
        End Sub

        <ComVisible(False)> _
        Public Sub DotNetOnly()
            ' This code cannot be called from a COM client
        End Sub
    End Class
     

    A COM class, its interface and any events it might raise are identified by a couple of GUID's. The moment you need a little more control  over your class you apply these from code and identify an object as one raising events. In VB.NET this is all done in one attribute.

    Imports System.IO

    <ComClassAttribute(FileWatcher.ClassId, FileWatcher.InterfaceId, FileWatcher.EventsId)> _
    Public Class FileWatcher
        Public Const ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69"
        Public Const InterfaceId = "959A6835-4768-4cd5-89BE-7D408C2B86AA"
        Public Const EventsId = "02E3954F-5DC1-4ad9-9167-354F0E82BCA3"

        Private WithEvents watcher As FileSystemWatcher
        Private Sub watcher_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles watcher.Created
            RaiseEvent OnNewFile(e.FullPath)
        End Sub

        Public Sub Watch(ByVal dirName As String, ByVal filter As String)
            watcher = New FileSystemWatcher(dirName, filter)
            watcher.EnableRaisingEvents = True
        End Sub

        Public Event OnNewFile(ByVal fullFileName As String)

    End Class
     

    This example FileWatcher class contains the guids to identify it. The ComClassAttribute applies them. The class wraps up a .NET FileSytemWatcher. The Watch method instantiates the object, sets a directory to watch, and enables raising events. When a new file matching the filter is created the COMserver's OnNewFile event will fire. You can use the server in Word like this:

    Dim WithEvents mywatcher As WordUtilsVB.FileWatcher

    Private Sub Document_Open()
       Set mywatcher = New WordUtilsVB.FileWatcher
       mywatcher.Watch "C:\USR", "*.doc"
    End Sub

    Private Sub mywatcher_OnNewFile(ByVal fullFileName As String)
       Documents.Open (fullFileName)
    End Sub
     

    Opening the documents fires up the COM server which will start watching for new Word Documents in my C:|USR directory. When a new file is found Word will open it. (Note that the *.doc filter will also open Word temp file). This is a handy utility and took just a couple of VB.NET lines.

    Being a C# guy I would like to refactor this to C# because I want to be able to make multiple call to the watch method which should result in multiple directories being watched. The way VB.NET handles event handlers is somewhat clumsy. In C# I could code like this.

        public class FileWatcher
        {
            internal const string ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69";
            internal const string InterfaceId = "959A6835-4768-4cd5-89BE-7D408C2B86AA";
            internal const string EventsId = "02E3954F-5DC1-4ad9-9167-354F0E82BCA3";

            private ArrayList watchers = new ArrayList();

            public void Watch(string dirName, string filter)
            {
                FileSystemWatcher watcher = new FileSystemWatcher(dirName, filter);
                watcher.Created += new FileSystemEventHandler(watcher_Created);
                watchers.Add(watcher);
            }

            public NewFile OnNewFile;


            private void watcher_Created(object sender, FileSystemEventArgs e)
            {
                OnNewFile(e.FullPath);
            }
        }

        [ComVisible(false)]
        public delegate void NewFile(string fileName);
     

    To define the event I have to declare the NewFile delegate. This should not be exported to COM so the ComVisible attribute is applied. On every call to Watch a new FileSystemWatcher object is created and in C# I can attach an eventhandler on the fly, no need to declare a method which explicitly handles a specific event of a specific object. (Perhaps my VB knowledge falls short here, but I don't know how to do this elegantly in VB. The handles way does not work here) The ArrayList stores all watchers.

    The hard part is registering this class in COM. The COMclassAttribute is part of the MicroSoft.VisualBasic namespace so it is by default not available in a C# project. The easy way would be to reference the Microsoft.VisualBasic.dll and use it nevertheless. Which works to get to VB specific functions like the financial ones (summary).

        [Microsoft.VisualBasic.ComClassAttribute(FileWatcher.ClassId, FileWatcher.InterfaceId, FileWatcher.EventsId)]
        public class FileWatcher
        {
            internal const string ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69";
     

    This code will build and run. But will not do what you want it to do. By default all public members are published in COM, the moment you start applying attributes results vary. Applying this VB attribute will result in a COM class without any members. To satisfy the COM registration process in C# requires these steps

    • Declare a public interface which describes the COMinterface of the class
    • Declare the class as implementing this interface
    • Declare a public interface which describes the events the class can sink (COM jargon for raising events)
    • Decorate this interface with an InterfaceType attribute an IDispatch interface
    • Decorate the class with a ComSourceInterface attribute
    • Decorate the class with ClassInterface attribute
    • Decorate the COMinterface, the eventsink interface and the class with Guid attributes

    Resulting in :

    namespace WordUtils
    {
        [Guid(FileWatcher.InterfaceId)]
        public interface IfileWatcher
        {
            void Watch(string dirName, string filter);
        }

        [Guid(FileWatcher.EventsId)]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IfileWatcherEvents
        {
            void OnNewFile(string fullFileName);
        }

        [Guid(FileWatcher.ClassId)]
        [ClassInterface(ClassInterfaceType.None)]
        [ComSourceInterfaces(typeof(IfileWatcherEvents))]

        public class FileWatcher : IfileWatcher
        {
            internal const string ClassId = "44558DD7-87AD-433f-9B1B-C478233D6C69";
            internal const string InterfaceId = "959A6835-4768-4cd5-89BE-7D408C2B86AA";
            internal const string EventsId = "02E3954F-5DC1-4ad9-9167-354F0E82BCA3";

            private ArrayList watchers = new ArrayList();

            public void Watch(string dirName, string filter)
            {
                FileSystemWatcher watcher = new FileSystemWatcher(dirName, filter);
                watcher.Created += new FileSystemEventHandler(watcher_Created);
                watchers.Add(watcher);
            }

            public NewFile OnNewFile;


            private void watcher_Created(object sender, FileSystemEventArgs e)
            {
                OnNewFile(e.FullPath);
            }
        }

        [ComVisible(false)]
        public delegate void NewFile(string fileName);
    }
     

    This is a lot more code than the VB.NET version. On the other hand in this code you do have a better overview of what this class looks to COM. Just read the two interfaces

        [Guid(FileWatcher.InterfaceId)]
        public interface IfileWatcher
        {
            void Watch(string dirName, string filter);
        }

        [Guid(FileWatcher.EventsId)]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IfileWatcherEvents
        {
            void OnNewFile(string fullFileName);
        }
     

    But there is a nasty problem with this code. When it comes to events it just does not work. The VBA designer in Word will "build" the code but as soon you run it it pops up a very nasty error:

    This is described in the MS knowledge base here, but that does not really help as it makes clear there is no workaround. What is happening exactly is hidden inside the framework, the C# code seems to delegate the sinking of events to an object which VBA cannot work with. You can build C# COM servers which sink events properly. In the beta days of 1.0 I have been ploughing my way into .NET via COM. At the time unaware of the great (intended) COM event support I created a base class with working event support, implementing the desired interfaces (IConnectionPointContainer and its allies) all by hand. It's quite a long story, you can find it here and it does have sample code.

    I'm "afraid" this is another notch for VB.NET, the second one when it comes to COM. I have to admit VB works very nice with named parameters and this is another one. But I'm going to be happy with the nice things of both languages. I'll use C# to solve  the internal event handling stuff and I'll use VB.NET to make a COM wrapper. After all both languages live happy together in the .NET framework. To paraphrase Chuck Yeager: "it's the framework, not the language".

    <Update>It takes just one keyword to get the C# COM server working as intended. Change

            public NewFile OnNewFile;

    to

            public event NewFile OnNewFile;

    and everything works. Read the background here.

    </Update>

More Posts