The Artima Developer Community
Sponsored Link

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

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Peter van Ooijen

Posts: 284
Nickname: petergekko
Registered: Sep, 2003

Peter van Ooijen is a .NET devloper/architect for Gekko Software
Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword Posted: Aug 3, 2005 10:57 AM
Reply to this message Reply

This post originated from an RSS feed registered with .NET Buzz by Peter van Ooijen.
Original Post: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
Feed Title: Peter's Gekko
Feed URL: /error.htm?aspxerrorpath=/blogs/peter.van.ooijen/rss.aspx
Feed Description: My weblog cotains tips tricks and opinions on ASP.NET, tablet PC's and tech in general.
Latest .NET Buzz Posts
Latest .NET Buzz Posts by Peter van Ooijen
Latest Posts From Peter's Gekko

Advertisement

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".

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

Topic: Microsoft .NET - Object-Relational Mapping with Codus Previous Topic   Next Topic Topic: Happy blog-day to me...

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use