Consuming COM events from ADX add-in in C' client

Add-in Express™ Support Service
That's what is more important than anything else

Consuming COM events from ADX add-in in C' client
 
OliverM


Guest


Event implementaion in C# is typically done somehow like so..
namespace ComAutomation
{
// Standard C# implementation
	class Program
	{
		public interface IFoo
		{
			event System.EventHandler Boo;
		}

		class Foo : IFoo
		{
			public event System.EventHandler Boo;
			public void RaiseBoo()
			{
				if (Boo != null)
					Boo(this, EventArgs.Empty);
			}
		}

		private void TestEvent()
		{
			object o = new Foo();
			((IFoo)o).Boo += Program_Boo;
			((Foo)o).RaiseBoo();
		}

		private void Program_Boo(object sender, EventArgs e)
		{
			throw new NotImplementedException();
		}

	}
}

But when used in an out-of-process context (e.g. contacting ADX add-in from C# client), there ara serious drawbacks. The above code works but is everything but user-friendly. Unfortunately there is no IntelliSense at this point! No press += TAB will work. No event method signature will be created automatically and developers need to know that:
- Event Boo exists
- The exact Boo event signature
before the can sucessfully consume an event.

In order to come up with a more developer friendly solution, I tried the following:
namespace ComAutomation
{
	// Adding a delegate to the ComAutomation namespace
	public delegate void CSharpEventHandler(object sender, EventArgs e);

	// Adding a new interface to the ComAutomation namespace
	public interface ICSharp
	{
		event CSharpEventHandler CSharpTestEvent;
		
		void RaiseCSharpTestEvent();
	}

	class CSharpTestProgram
	{
		// Enhancing the consuming class with
		public event CSharpEventHandler CSharpTestEvent;

		public void RaiseCSharpTestEvent()
		{
			if (CSharpTestEvent != null)
				CSharpTestEvent(this, EventArgs.Empty);
		}
		
		private void TestCSharpEvent()
		{
			object o = new CSharpTestProgram();
			((ICSharp)o).CSharpTestEvent += Program_CSharpTestEvent; // Error pops here
			((CSharpTestProgram)o).RaiseCSharpTestEvent();

		}

	}
}

That looked promising at the first glance as it nicely allows to use += TAB syntax. Moreover the correct signature was created! But when it came to execution, I got the following error: "Unable to cast object of type 'System.__ComObject' to type 'MyProject.ComAutomation.CSharpEventHandler'"
I tried other solutions as well but none of it compiled. Am I overlooking something or is this a .NET restriction? Or may be behaviour by design and I simply do not see the full picture?

Can you guys please help clarify?
Posted 09 May, 2016 11:36:24 Top
Horatiu Muresan


Guest


I'm not a Addin Express support, I just give my 2 cents.
Two questions:

1. Where do you specify that CSharpTestProgram implements interface ICSharp? Shouldn't it be something like :
class CSharpTestProgram : ICSharp //CSharpTestProgram IMPLEMENTS interface ICSharp

2. object o = new CSharpTestProgram(); - do you have to declare o as object , is it a problem to instead do :
CSharpTestProgram o = new CSharpTestProgram();
or
var o = new CSharpTestProgram();


Cheers,
Horatiu
Posted 10 May, 2016 04:24:43 Top
OliverM


Guest


Hi Horatiu,

Thank you for contributing. You are of course right. Like in the base example where class class Foo implements IFoo, class CSharpTestProgram needs to implement ICSharp.

The actual issue I have described poorly, hopefully my 2nd approach is clearer.
Consider the following scenario: We want to connect to an ADX add-in running in Excel. The add-in has a class ComServer which is used to publish events to 3rd party clients.
(e.g. Something is calculated in Excel and the result is propagated using an event). So creating an instance of class ComServer would not help as the created object would not have any connection to the calculating Excel instance.
Hence we need to connect to the ComServer object created by the ADX add-in on excel start up and wait for the calculation to finish. This is perfectly possible as described in the below ComClient air code example. So connecting is not an issue at all. But note the 2 different implementations in interface ICSharp. Only the method implementation will work, the event implementation fails despite they are both using the same delegate. The failing one supports IntelliSense, the working one does not.
// COM Server
namespace ComAutomation 
{ 
	class ComServer
	{
		// Adding a delegate to the ComAutomation namespace 
		public delegate void CSharpEventHandler(object sender, EventArgs e); 
	 
		// Adding a new interface to the ComAutomation namespace 
		public interface ICSharp 
		{ 
			// Implement as event => does compile but throws error on client side
			event CSharpEventHandler CSharpTestEvent; 
			
			// Implement as void => compiles and works
			void OnCSharpTestEvent(object sender, EventArgs e);
			
		} 
	 
		class CSharpTestComServer : ICSharp
		{ 
			// Used by event implementation 
			public event CSharpEventHandler CSharpTestEvent; 
			
			// Used by method implementation 
			private event CSharpEventHandler OnCSharpTestEvent;
			
			// Triggered by add-in
			private void RaiseCSharpTestEvent() 
			{ 
				if (CSharpTestEvent != null) 
					CSharpTestEvent(this, EventArgs.Empty); 
			} 
			
			// Triggered by add-in
			private void RaiseOnCSharpTestEvent()
			{
				if (OnCSharpTestEvent != null)
					OnCSharpTestEvent(this, EventArgs.Empty); 
			}
		} 
	}
} 


// COM Client
namespace ComClientTest 
{
	class ComClient
	{
		private ComTest()
		{
			// Create XL instance
			_Application xlApp = new Microsoft.Office.Interop.Excel.Application();
			xlApp.Workbooks.Open(@"C:SomeWorkbook.xlsm");
			xlApp.Visible = true;
			
			// Get access to add-in
			object addinName = "SomeProgId";

			// Get the COM add-in
			COMAddIn addin = xlApp.COMAddIns.Item(ref addinName);
			
			// InvalidCast error pops here at runtime but creates correct event signature at design time
			((ICSharp)addin.Object).CSharpTestEvent += Program_CSharpTestEvent; 
			
			// Works but no IntelliSense. We need to know event name and signature
			((ICSharp)addin.Object).OnCSharpTestEvent += new CSharpEventHandler(Program_OnCSharpTestEvent)
			
		}
	}
}
Posted 10 May, 2016 06:38:23 Top
Dmitry Kostochko


Add-in Express team


Posts: 2875
Joined: 2004-04-05
Hi Oliver,

I am trying to reproduce the issues on my machine and I am a bit confused. I cannot compile the ComServer class because of:
error CS0535: 'ComAutomation.ComServer.CSharpTestComServer' does not implement interface member 'ComAutomation.ComServer.ICSharp.OnCSharpTestEvent(object, System.EventArgs)'

And, as far as I can see, the ICSharp interface contains one event and one method, right? Why do you use the OnCSharpTestEvent method as an event in the ComTest class?

Could you please send me your test projects for testing? Probably I will find a solution.
Posted 11 May, 2016 05:35:03 Top
OliverM


Guest


Hi Dimitry,

I am using the OnCSharpTestEvent method as an event in the ComTest class in order to provide event notification for VBA clients. In a VBA environment declaring a ComServer instance by using the WithEvents key word creates an event sink for the VBA client, they can choose the event the want to consume from a dropdown menu and the event signature is automatically created.

The above code is air code, so please appologize the fact it is not compiling.

I will put together an example project and send it to the support addresss.
Posted 11 May, 2016 06:29:06 Top
Dmitry Kostochko


Add-in Express team


Posts: 2875
Joined: 2004-04-05
Hi Oliver,

We received your test project, thank you. I will inform you about the results as soon as I get any from our developers.
Posted 11 May, 2016 10:55:04 Top
Dmitry Kostochko


Add-in Express team


Posts: 2875
Joined: 2004-04-05
Hi Oliver,

I have just sent the modified code back to you. Please test it on your side.
Posted 12 May, 2016 09:58:31 Top
OliverM


Guest


Hi Dmitry,

Thank you very much for the modified code example. It works like a charme for the C# client now. Full IntelliSense support and no more invalid cast exceptions.

The only drawback is, the event support for VBA clients no longer works. When declaring the ComServer instance using the WithEvents keyword I now get an "Object library feature not supported" error.
I checked the ComServer class in the object browser and it looks fine, I can see the 2 events and its corresponding methods but the TestEventArgs class looks somehow suspicious as it does not expose the 2 members it has.
Posted 13 May, 2016 03:03:50 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello Oliver,

I've sent you a modified version of code. Please check your Inbox.


Andrei Smolin
Add-in Express Team Leader
Posted 13 May, 2016 06:17:06 Top
OliverM


Guest


Hi Andrei,

Shifting the IDispatch attribute to the ITestMethods interface did the trick.

The VBA code sample does nicely compile now, offers IntelliSense support. Events and methods are available and working. Same holds true for the C# client.

The above mentioned not exposed TestEventArgs class members were due to my own fault. I forgot to decorate the TestEventArgs class with the AutoDual attribute.

I now much better understand why many people are scared of coding COM :-)

You guys rock!
Posted 13 May, 2016 08:01:44 Top