0

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.

aikeru
  • 3,773
  • 3
  • 33
  • 48

1 Answers1

4

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks, Hans! This is my second COM question you've answered :) I've seen your work on other questions too! I wish absorb all of your knowledge... I am still wondering about WSH behavior. IE: On Scripting.FileSystemObject .Drives, doing a WScript.Echo(x); on a particular drive yields the drive letter, which I presume it knows to do this via the DISPID_VALUE. I was wondering if this sort of behavior is documented somewhere, so I can have the most broad compatibility with COM objects possible. Do you know anything about this aspect or where I might learn more about it? – aikeru Jun 18 '12 at 19:25
  • Also, I seem to find that there might be some sort of standard for enumeration using dispids after doing a Google search but I didn't find much in-depth... so, also interested in that if you know. I appreciate your help :) hopefully others will also. – aikeru Jun 18 '12 at 19:29
  • That isn't documented for the Echo function. But sure, it is likely that it uses the default property. The default property for the Drive object isn't documented either, but you can find out by looking at the type library. From the VS Command Prompt, run oleview.exe c:\windows\system32\cscript.exe – Hans Passant Jun 18 '12 at 19:45
  • Right, I have C# code that loops through the TYPEATTR cFuncs and cVars looking for one that matches dispid 0. I guess I was hoping to find documentation on this convention somehow ... – aikeru Jun 18 '12 at 19:51
  • Thanks for the tip on oleview - I'll have to Google how to get that setup and check it out.. – aikeru Jun 18 '12 at 19:52
  • I found the tool here: http://www.microsoft.com/en-us/download/confirmation.aspx?id=7007 and it is very cool! – aikeru Jun 18 '12 at 19:55