[ComVisible(true)]
X Register for COM interoper
2) http://codebetter.com/blogs/peter.van.ooijen/archive/2005/08/02/130157.aspx
Sign in | Join | Help
Do you twitter? Follow us @CodeBetter
Peter's Gekko
* Home
* About
* Contact
Sponsors
ASP.NET Web Hosting – Click Here: 3 Months Free!
The Lounge
Telerik
Telerik OpenAccess - extensive LINQ support, forward & reverse mapping, stored procedures and more
Ads by The Lounge
Syndication
* RSS for Posts
* Atom
* RSS for Comments
Recent Posts
* Keeping a long running Silverlight application alive under forms authentication
* Every picture tells a story
* DDD and repositories. Without nHibernate but with lazy loading
* The return of CopySourceAsHtml
* Paging in a sql result set
Tags
* ASP.NET
* Chatter
* Coding
* Data
* Featured
* Hardware
* Mobile
* Out of control
* Tablet PC
* User groups and meetings
View more
News
*
Subscribe with Bloglines
I'm test-driven!
Links
* Gekko Software
* My publications
* Is this code available in VB ?
* This blog is out of control
Archives
* April 2009 (2)
* March 2009 (1)
* February 2009 (3)
* January 2009 (1)
* December 2008 (3)
* November 2008 (2)
* October 2008 (1)
* September 2008 (2)
* August 2008 (1)
* July 2008 (1)
* June 2008 (4)
* May 2008 (4)
* April 2008 (2)
* March 2008 (2)
* February 2008 (2)
* January 2008 (3)
* December 2007 (2)
* November 2007 (3)
* October 2007 (4)
* September 2007 (2)
* August 2007 (1)
* July 2007 (1)
* June 2007 (2)
* May 2007 (1)
* April 2007 (2)
* March 2007 (3)
* February 2007 (6)
* January 2007 (4)
* December 2006 (6)
* November 2006 (7)
* October 2006 (4)
* September 2006 (4)
* August 2006 (6)
* July 2006 (4)
* June 2006 (9)
* May 2006 (6)
* April 2006 (8)
* March 2006 (9)
* February 2006 (10)
* January 2006 (9)
* December 2005 (4)
* November 2005 (7)
* October 2005 (12)
* September 2005 (11)
* August 2005 (10)
* July 2005 (9)
* June 2005 (9)
* May 2005 (11)
* April 2005 (12)
* March 2005 (11)
* February 2005 (27)
* January 2005 (15)
* December 2004 (12)
* November 2004 (12)
* October 2004 (11)
* September 2004 (9)
* August 2004 (9)
* July 2004 (15)
* June 2004 (13)
* May 2004 (13)
* April 2004 (15)
* March 2004 (21)
* February 2004 (23)
* January 2004 (14)
* December 2003 (15)
* November 2003 (21)
* October 2003 (30)
* September 2003 (13)
* August 2003 (12)
* July 2003 (5)
* June 2003 (4)
Advertisement
Peter's Gekko » Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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
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
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".
public NewFile OnNewFile;
to
public event NewFile OnNewFile;
and everything works. Read the background here.
Posted 08-02-2005 12:11 PM by pvanooijen
Filed under: Coding
[Advertisement]
Red-Gate
Comments
Sean wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-02-2005 10:44 AM
In VB.NET, you should be able to add your event handlers via code as in:
Dim watcher as FileSystemWatcher = New FileSystemWatcher(dirName, filter)
AddHandler watcher.Created AddressOf watcher_Created
watchers.Add(watcher)
pvanooijen wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-02-2005 12:01 PM
Thanks, that is the syntax I was looking for.
The most interesting thing is that after refactoring the VB class to the same pattern as the C# class results the VB class will show the same error.
Now the 459 error begins to make more sense. Delegating the handling of the event to another object, in the case the watcher object, will produce the problem. In both languages.
So instead of looking for the VB addhandler syntax I'll have to look for the C# equivalent of handles.
To be continued..
Peter's Gekko wrote Handles, AddHandler and RemoveHandler in VB.NET
on 08-03-2005 4:37 PM
Äfter a crash course comes sinking in. This post is a rewrite of yesterdays one on events in VB.NET....
Peter's Gekko wrote Handles, AddHandler and RemoveHandler in VB.NET
on 08-04-2005 4:23 AM
After a crash course comes sinking in. This post is a rewrite of yesterdays one
on events in VB.NET....
Peter's Gekko wrote Handles, AddHandler and RemoveHandler in VB.NET
on 08-05-2005 12:07 PM
After a crash course comes sinking in. This post is a rewrite of yesterdays one
on events in VB.NET....
Peter's Gekko wrote Handles versus Addhandler , a crash course in VB.NET event support (Error 459 revisited)
on 08-05-2005 12:10 PM
In my previous post
on the handling of COM events I described how I was driven into the
arms of VB.NET....
Visitor wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-10-2005 8:28 AM
To fix the nasty problem on using the C# COM server you have to declare the event with:
public event NewFile OnNewFile;
pvanooijen wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-10-2005 9:51 AM
That's great. Now Word does create the object without any errors. But as far as I can see the events don't hook in :? Look into that after my holiday.
A bit of a pitty you would need such a, at first sight superfluous keyword, to get it working. Not really in the spirit of C#.
Visitor wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-11-2005 2:19 AM
You can see the events, if you enable the watcher to raise some events with:
watcher.EnableRaisingEvents = true;
So it should do what you want.
savage wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-11-2005 10:01 AM
I'm trying to get this very thing working between Delphi and a C# application. I'm creating a COM class in C# and attempting to attach an event from within Delphi, but not having any luck. Any suggestions?
pvanooijen wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 08-30-2005 5:00 AM
The Events keyword indeed does the trick. I had indeed forgotten to set the filewatcher's enableraisingevents property. Setting it gave me my intended utility.
I'll write a wrapup post on the events keyword, it's documentation is bad and the idea is (looking back) quite simple.
I'll also write a post on eventsupport in Delphi, for the time you can find loads off material on this on my website www.gekko-software.nl
Peter's Gekko wrote The C# event keyword is an access modifier for delegate members
on 08-31-2005 4:08 PM
Recently I had trouble getting COM events to work in a COM automation server written in C#. A visitor's...
Peter's Gekko wrote The C# event keyword is an access modifier for delegate members
on 08-31-2005 4:11 PM
Recently I had trouble getting COM events to work in a COM automation server written in C#. A visitor's...
Peter's Gekko wrote The C# event keyword is an access modifier for delegate members
on 09-01-2005 11:54 AM
Recently I had trouble getting COM events to work in a COM automation server written in C#. A visitor's...
Peter's Gekko wrote Is this code available in VB ?
on 10-06-2005 5:32 AM
This is a question I often get. I'm a C# guy but that is just a matter of personal preference. It's the...
Khash wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 12-16-2005 12:28 PM
I think I have found a bug in CCW
Have a look at this:
http://sajadi.co.uk/dflat/archives/2005/12/net_com_callabl.html
Rogelio wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 09-21-2006 10:16 AM
I created a COM class using the first sample you provided:
Imports System.Runtime.InteropServices
Public Class MyfirstComClass
Public Sub DoSomethingForYourCOMclient()
' Your code here
End Sub
Public Sub DoMore()
' More code
End Sub
Public Sub DotNetOnly()
' This code cannot be called from a COM client
End Sub
End Class
The problem I'm having is calling the DoMore() public sub from javascript. Is there a way to do this?
pvanooijen wrote re: Creating a COM server with .NET. C# versus VB.NET and the WithEvents keyword
on 09-21-2006 1:20 PM
To make the call from JavaScript you have to create the COM object in JavaScript. Creating a COM (ActiveX) object in the browser is not a path to follow except when you don't see any other solution.
Add a Comment
Name: (required) *
Website: (optional)
Comments (required) *
Remember Me?
Verify that you are a human,
drag scissors into the circle.
*
*
*
*
*
Ajax Fancy Captcha
About CodeBetter.Com
CodeBetter.Com FAQ
Our Mission
Advertisers should contact Brendan
Subscribe
Google Reader or Homepage
del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo
Member Projects
Sarasota Web Design - David Hayden
Patterns & Practices - David Hayden
dotMath - Steve Hebert
Structure Map - Jeremy D. Miller
StoryTeller - Jeremy D. Miller
The Code Wiki - Karl Seguin
Friends of CodeBetter.Com
Red-Gate Tools For SQL and .NET
Telerik
ComponentArt
VistaDB
JetBrains - ReSharper
Beyond Compare
.NET Memory Profiler
NDepend
AliCommerce
Ruby In Steel
SlickEdit
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
AForge.NET
Unfuddle
Balsamiq Mockups
Scrumy <-- NEW Friend!
3)How to create Excel UDFs in VSTO managed code
One question that I frequently get is how to call managed code from VBA. In general it is not recommended to mix VBA with managed code mainly due to the non-deterministic eventing model. In other words if VBA and managed code are listening for the same event there is no guarantee of the order that the handlers will be called. Another issue with using VBA and VSTO in the same solution is that you now have to deal with two separate security models. With that said, there are still times when you want to call VSTO code from VBA. One scenario is that you are upgrading an existing VBA solution to use VSTO. In this scenario you are keeping all of the existing VBA and are adding new capabilities to your solution using VSTO. Another scenario is that you want to create a solution in VSTO but you want to use User Defined Functions (UDF) in Excel. UDFs still require that they be written in VBA, but you can create your UDFs in managed code and call them from VBA. This is the technique that I describe below. This solution requires that you pass a reference to your managed code to VBA. Once the you have a reference to the managed code you can call that code from VBA. I recommend creating a wrapper in VBA for the managed functions this allows you to “call” the managed code from VBA.
Here is any easy way to call Managed functions from VBA.
*
1. Create a class with your functions in VSTO
Public Class MyManagedFunctions
Public Function GetNumber() As Integer
Return 42
End Function
End Class
2. Wire up your class to VBA in VSTO
Private Sub ThisWorkbook_Open() Handles Me.Open
Me.Application.Run("RegisterCallback", New MyManagedFunctions)
End Sub
3. Create Hook for managed code and a wrapper for the functions in VBA
In a VBA module in your spreadsheet or document
Dim managedObject As Object
Public Sub RegisterCallback(callback As Object)
Set managedObject = callback
End Sub
Public Function GetNumberFromVSTO() As Integer
GetNumberFromVSTO = managedObject.GetNumber()
End Function
Now you can enter =GetNumberFromVSTO() in a cell, when excel starts the cell value should be 42.
Published Friday, December 31, 2004 8:01 PM by pstubbs
No comments:
Post a Comment