Question

I'm trying to duplicate the behavior that I observe in JScript in C#. I am using IDispatch to enumerate members and call them on late-bound objects. I am a complete C++ noob and know just enough about COM to be very dangerous. Here are my question(s):

  • Is DISPID_VALUE always zero (0)? (seems yes)
  • When calling COM objects, when should I call out to the DISPID_VALUE member? (something like, when the interface itself is indexed or invoked...?)
  • Are there any rules/hints for when to call .Item?
  • Why, in the example below, does BindingFlags.SetProperty work on .Cells(x, x) (as opposed to BindingFlags.InvokeMethod)? Is it calling _Default(x, x)? Item(x, x)? How does it know to do this? How can I find out which it is calling?
  • Is there some good documentation on calling late-bound IDispatch COM objects?

In the example below, an Excel spreadsheet's cell 1,1 has the value set to some text and "bolded".

Consider the following WSH JScript:


var objExcel = new ActiveXObject("Excel.Application");
objExcel.Workbooks.Add();
objExcel.Visible = true;
objExcel.Cells(1,1).Value = "some test value";
objExcel.Cells(1,1).Font.Bold = true;

This C# code creates the same result (yes, sorry it's very verbose):


Type axType = Type.GetTypeFromProgID("Excel.Application");
object objExcel = Activator.CreateInstance(axType);
object workbooks = objExcel.GetType().InvokeMember("Workbooks", System.Reflection.BindingFlags.GetProperty, null, objExcel, null);
objExcel.GetType().InvokeMember("Visible", System.Reflection.BindingFlags.SetProperty, null, objExcel, new object[] { true });
workbooks.GetType().InvokeMember("Add", System.Reflection.BindingFlags.InvokeMethod, null, workbooks, new object[] { true });
object cell = objExcel.GetType().InvokeMember("Cells", System.Reflection.BindingFlags.GetProperty, null, objExcel, new object[] { 1, 1 });
cell.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, cell, new object[] { "some test value" });
object font = cell.GetType().InvokeMember("Font", System.Reflection.BindingFlags.GetProperty, null, cell, null);
font.GetType().InvokeMember("Bold", System.Reflection.BindingFlags.SetProperty, null, font, new object[] { true });

When I get time one way I plan to try and learn more about this is to have JScript call out to a C# COM class I would create with logging/debugging.

Was it helpful?

Solution

Yes, DISPID_VALUE is #defined to 0 in oaidl.idl. Giving a property a dispip of 0 makes it the default property. Lots of languages permit omitting the name of the default property. Equivalent to the C# indexer.

It is just a convention, there is certainly no requirement for a COM IDispatch derived interface to expose a default property. Nor does the property have to be named "Value". It is merely common to do this. The indexer of a [ComVisible] C# interface will get dispid 0 but with the name "Item". Lots of other variations around, you can't assume anything.

BindingFlags.SetProperty works because the Cells member is a property, not a method. It looks a bit like a method only because it is an indexed property. That's barely supported in C# (only for the indexer) but unrestricted in COM or VB.NET. The COM IDispatch interface is used in your example code, it permits looking up members by name. IDispatch::GetIDsOfNames() gets that done, mapping a string to a number (the dispid) when then can be used in to invoke the property or method with IDispatch::Invoke(). Note that this works only one way, name to number. IDispatch doesn't support the equivalent of Reflection.

Get rid of the ugly C# code by writing this in VB.NET or by using the C# version 4 dynamic keyword.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top