5

Question summary

When I use VBA's VarType function, passing it an instance of the Object class available in a mscorlib.dll library reference (a .NET library reference), the value returned is 8.

According to VBA documentation here, this means the object is a string. This seems ridiculous.

My question is why is the VarType function returning a string-type value for instances of this Object class from the .NET-library VBA reference? Is it a bug?

Background information

I suspect that the fact that VBA's VarType function is saying a certain COM object is a string, may be why I'm having problems using the DispCallFunc function on certain methods of certain COM objects. The COM objects are COM versions of .NET objects, made available through the .NET framework.

I'm using the mscorlib.dll VBA reference to get early-binding functionality for these objects. The reference refers to version 4.0.30319 of the .NET framework. On my computer, the type library for the reference is stored at:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb.

It is looking like when the object method specifies that an argument or return value be of type System.Object, DispCallFunc fails to work for me. Anyway, that is a separate problem that is circumstantial to the problem this question is dedicated to.


At the time of writing, I'm running the latest version of Excel (Version 1903, Build 11425.20202). My operating system is Windows 8.1.

When I use the VarType function with instances of other classes from the mscorlib.dll library, I sometimes get return values of 9 & 13 (VbVarType.vbObject & VbVarType.vbDataObject constants) which seems correct.

I researched on the internet to see whether anyone else had encountered the problem but couldn't find anything.

Code that can be used to reproduce problem

Dim o As mscorlib.Object
Set o = New mscorlib.Object
Debug.Print "TypeName(o) = " & TypeName(o) ' TypeName function seems to work correctly.
Debug.Print "o.Equals(o) = " & o.Equals(o) ' System.Object.Equals method is working.

Debug.Print "VarType(CVar(o)) = " & VarType(CVar(o)) ' IMPORTANT LINE
' VBA VarType function says o is string (type 8) but it isn't?!

Debug.Print "VbVarType.vbString = " & VbVarType.vbString

I expected VarType(CVar(o)) to return 9, 13 or some other appropriate integer. Instead, it returned 8 which does not seem appropriate at all (8 represents strings.)

  • 1
    Perhaps the object has a default property, in which case `VarType` (as per the docs) returns the type of that property. – Rory Apr 12 '19 at 20:45
  • Does `Debug.Print o` output anything or blow up? – Mathieu Guindon Apr 12 '19 at 20:59
  • Yes Rory, I believe the answer has to do with the type of the default property being returned. I should have read the VarType documentation more thoroughly - didn't expect such functionality. Thanks for looking into this. – Mark Fernandes Apr 13 '19 at 16:36

3 Answers3

8

If you open the typeLib C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb with OleView for example, and navigate to the _Object interface (not the first _Object one, the dispinterface, but the second one) you'll see this:

enter image description here

So, the .NET method Object.ToString() is declared to COM/Automation clients as

[id(00000000), propget, custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
HRESULT ToString([out, retval] BSTR* pRetVal);

Which means it's seen to COM clients that understand this "syntactic sugar" (VB/VBA/VBScript/JScript/.NET, etc.) as a Property (propget + out + retval) named ToString that returns a String (BSTR).

Now, id(0) means it's the default property, because 0 represents DISPID_VALUE, a special well-known id.

And lastly, VB/VBA VarType's documentation states this:

If an object has a default property, VarType(object) returns the type of the object's default property.

(which I always found a pretty strange design decision...)

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • how does one actually get to the view you show in the image please? Is a download required? – QHarr Apr 13 '19 at 11:09
  • @QHarr - OleView.exe is available with Windows SDK, in a path like C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86 fore example. If you have Visual Studio, you should have it as well. – Simon Mourier Apr 13 '19 at 11:43
  • This answer seems to be spot on the correct answer. Thank you so much for this. I should have read the VarType documentation properly - didn't expect such behaviour. I learnt of the function from someone else's code who seems to have more or less made the same mistake as me. I've learnt so much from these Stack Overflow answers. Thank you. – Mark Fernandes Apr 13 '19 at 16:32
  • @MarkFernandes feel free to mark the answer as "accepted" by clicking the hollow green checkmark under the up/down voting buttons near the top of the post. IMO the explanation doesn't get any more canonical that this. – Mathieu Guindon Apr 13 '19 at 16:38
  • For anyone who is interested, I've created a pull request on GitHub for the VarType documentation, to make it more clear in the documentation that VarType has this 'unusual' behaviour. See 'https://github.com/MicrosoftDocs/VBA-Docs/pull/956' for the request. – Mark Fernandes Apr 15 '19 at 09:25
  • Thanks, and sorry for the duplicate (little search didn't retrieve this question). Upvoted. Sounds like it's not possible out of the box to write a function wich returns either a COM visible Assembly object or a string and differentiate them upwards (I'm trying to set an efficient way to check fail creation and get the error message). – Amessihel May 16 '19 at 17:07
1

Looking at the reference source for object.cs, I'm not seeing any [DispId] attributes, but assuming the first member gets marshaled with [DispId(0)], that would make the ToString method the COM type's default member.

That's the only explanation I have for Debug.Print o outputting System.Object, rather than blowing up with error 438 as it normally would without a default member.

So the problem isn't so much with .NET/COM interop, rather it's about dealing with getting metadata out of an object that has a default member: you would have the exact same problem with any COM object that has a String default member:

?VarType(Application), VarType(Application.Name)
 8             8 

I can't think of a way off the top of my head to make VarType work with these. On the other hand, a TypeOf...Is check works fine:

?TypeOf Application Is Object
True

Hence:

Debug.Print TypeOf o Is Object ' True
Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • This answer appears to be more or less correct. Thank you so much for this. I should have read the VarType documentation properly - didn't expect such function behaviour. I learnt of the function from someone else's code who seems to have more or less made the same mistake as me. I've learnt so much from these Stack Overflow answers. Thank you. – Mark Fernandes Apr 13 '19 at 16:30
0

I think a part of the problem is that VBA evaluates expressions in certain cases (maybe someone can add more information on when/why this happens). So, when you make the call to VarType(o), the o variable is actually being converted to a string representation, and the type is taken.

As an example, if you write Debug.Print o, the output will be System.Object. If you write Debug.Print x on a different, concrete object, the system will possibly throw an error.

Try the following syntax:

Debug.Print VarType(o.GetType)

In my case, that returns the value 13.

basodre
  • 5,720
  • 1
  • 15
  • 23
  • `Object.GetType()` returns a `Type`, which is an `Object` indeed - but that's not the type of `o`. – Mathieu Guindon Apr 12 '19 at 21:05
  • @MathieuGuindon This makes sense. Thanks – basodre Apr 12 '19 at 22:46
  • Yes, I thought that the ToString method probably had something to do with it, but missed reading the VarType documentation where it says that the type of the default property is returned - didn't expect such behaviour. Thanks for looking into this. Hopefully, this Stack Overflow question will help others. – Mark Fernandes Apr 13 '19 at 16:35