views:

239

answers:

4

In VB6, I'm trying to pass a late bound object to another form.

frmMain.vb

Dim x
Set x = CreateObject("MyOwn.Object")
Dim f as frmDialog
Set f = New frmDialog
f.SetMyOwnObject x

frmDialog

Dim y
Public Sub SetMyOwnObject(ByVal paramX As Variant)
  Set y = paramX
End Sub

The contents of y are a string containing the type name of the late bound object, "MyOwn.Object". ByVal and ByRef don't make a difference. Any clues? Having trouble remembering.

+2  A: 

I don't have a copy of VB6 handy, but I can remember doing the same thing more or less pretty often, and I believe that we used Object rather than Variant in the method signature. Variant is generally a lot less predictable in terms of what kinds of conversions it may run on a variable, whereas with Object I'm fairly sure VB won't attempt any sort of conversion.

Ryan Brunner
Could you give an example where variant will peform conversion of any kind?
shahkalpesh
Using a Variant won't cause a conversion, if you're storing an object instance in it. However, if the object has a default property or method that returns a value, the variable will evaluate to the value of the default property or method if you use it in a context where an expression is expected. For example, if you "Set y = someObject" and y is a Variant and someObject has a default property called Value that returns a string, then doing something like "Debug.Print y" will invoke Value, since VB6 can't evaluate the object itself as an expression and Value is the default property.
Mike Spross
On the same note, if you have an object with a default property/method, and you assign it to a Variant *without* using Set, it will assign the return value of the default property/method to the Variant, not the object itself (i.e. it won't generate an error). Something else to watch out for, and another good reason to Dim your object variables "As Object" explicitly if you know they will only be storing objects and not other types of data.
Mike Spross
Variants have their uses in some special situations but IMHO they are best avoided unless you're going to use their special features. Mike's given a good explanation of some of their drawbacks.
MarkJ
A: 

Are you sure you haven't omitted the Set keyword e.g.

Dim y
Public Sub SetMyOwnObject(ByVal paramX As Variant)
  ' Set y = paramX  ' thought you had this...
  y = paramX        ' ...actually have this
End Sub

If this were the case then value of y would be the object's default value. Does your MyOwn.Object class have a property that returns a description of its type and has been defined as the default member for the class (marked with a blue dot in the VB Object Browser)?

onedaywhen
Yes, I'm sure Set is there.
ssorrrell
Out of interest, does the class have a default member? If so, what does it return?
onedaywhen
There doesn't seem to be a default member or property that I defined or can see. It would have to be something that .Net added on its own.
ssorrrell
A: 

frmMain.vb

Dim x As Object
Set x = CreateObject("MyOwn.Object")
Dim f as frmDialog
Set f = New frmDialog
f.SetMyOwnObject x

frmDialog

Dim y As Object
Public Sub SetMyOwnObject(ByRef paramX As Object)
  Set y = paramX
End Sub

When you use CreateObject, you are creating an Object not a Variant. When you pass an Object generally you use ByRef.

Will Rickards
The code above generates a compiler error, "ByRef argument type mismatch".
ssorrrell
Did you update the signature of `SetMyOwnObject` as he suggested?
Pavel Minaev
@ssorrell: That would happen if the variable you are passing to `SetMyOwnObject` isn't `Dim`'ed as Object. `ByRef` requires the type of the incoming argument to match the type that the `ByRef` parameter was declared with, unless the `ByRef` parameter is declared as `Variant`.
Mike Spross
IMHO there aren't many reasons to use `ByRef` in VB6. If an object instance is passed `ByRef`, the called method can change what the object points to. Passing an object `ByVal` is safer. The calling method gets a copy of the object reference and thus can't reassign the object to a completely different object, but can still change the object's state. I would only use `ByRef` when I specifically needed an `out` parameter.
Mike Spross
@Mike Spross: I see your points. But passing an object ByVal just seems like a bad concept in my head. I realize what it does, it doesn't actually copy the object just the pointer to the object. But I still keep thinking of ByVal as a copy and thus just use ByRef for objects. That way I know when I'm modifying the object, those changes will be passed to the callee. And I very rarely if ever reassign the paramX in this case. I would be assigning it to a local variable before making changes. We have different ways of coding I guess.
Will Rickards
@ssorrrel: Guessing that if you are getting that error then you didn't change the invoking code to Dim x As Object, you still have it as Dim x which will declare a variant.
Will Rickards
@Will Rickards: Fair enough ;-). And I agree, if I write a method that takes an object, I don't think I would really ever "accidentally" reassign it to another object, so `ByRef`/`ByVal` is more a matter of preference most of the time...when you know what you're doing. I've seen enough abuses of it that I tend to lean towards `ByVal` in my own code to avoid the temptation to have mutable state all over the place. Different strokes for different folks ;-)
Mike Spross
@Will Rickards: I see. My previous comment was in changing only the "Dim y As Object" and leaving the "Dim x" Using "Dim x As Object" might work, but the reason I'm using a CreateObject is that the object is a .Net 1.1 library. It crashes on instantiation with the error "This application has requested the Runtime to terminate it in an unusual way..."
ssorrrell
@Mike Spross: You were right about the Dim statement.
ssorrrell
@ssorrrel: So it works now or it doesn't? If you are calling a .net assembly then you have to use the COM Interop. If you are using that then why use late binding? http://support.microsoft.com/kb/817248 I think we are beyond the VB6 syntax issues.
Will Rickards
I agree with Mike Spross i.e. usually pass object references ByVal. It states the intention of what the caller expects the callee to do with the object. Most often the caller would not expect to get an entirely different object back. I'd use ByRef for an object reference when I was expecting the caller to pass a 'null' (Nothing) reference and for the callee to create the object i.e. a return argument. So ByVal or ByRef for me declares the intention.
onedaywhen
P.S. I usually explicitly declare ByVal or ByRef and only omit it where a) I want to pass ByVal but VB doesn't let me e.g. an array, or b) I'm passing an intrinsic reference type (i.e. String) to a local sub procedure where passing by reference avoids a 'deep copy'. In both cases the intention is that by not *explicitly* passing it ByRef I would expect the callee not to change the value i.e. treat it as ByVal. The bottom line here is 'trusted code', of course.
onedaywhen
@onedaywhen: The issue isn't really about ByRef vs ByVal. Neither make a difference in this context.
ssorrrell
@Will Rickards: I'm using .Net 1.1 and not higher. Would love to use 2.0, but other dependencies prevent it. Registering the .tlb file requires removing the reference, exiting VB6, RegTLB'ing the .tlb file, reopening VB6, and re-adding the reference every time the .Net changes. Late Binding .Net and VB6 lets me edit and compile both at the same time. In .Net I'm using "ClassInterface(ClassInterfaceType.AutoDispatch)" and "ComVisible(true)".
ssorrrell
A: 

I used VarType(y). The result is 8, for vbString. It should be 9 for object. – ssorrrell 1 hour ago

Use Print y in the Immediate window to find the contents of y. – ssorrrell 55 mins ago

This seems to confirm my suspicions. The MyOwn.Object class must have a default property or method that returns a string.

Therefore, when you try to Debug.Print it, it will return the value of the default property/method. When you hover over the variable in the IDE, VB6 will display the value of the default property/method. When you do a VarType call on y it will return the variable type of the default property or method.

The reason is that when you have a variable of type Variant that stores an Object, and the class of the object defines a default method or property, the variable will evaluate to the return value of the default method or property in most situations.

You can quickly check to see if the MyOwn.Object class has a default member by opening the Object Browser to the MyOwn.Object class and looking at the its list of properties and methods. If you see a method or property that has an icon with small blue circle in the corner, that indicates the method or property is the default member of the class. If you find one, I'm willing to bet it's declared to return a string.

Note that even if you changed all your VariantS to ObjectS, you would still encounter this behavior in a number of places. For example, even if y is declared As Object, doing a Debug.Print y will still print out the value of the default property or method, and doing a VarType(y) will still return 8 (string).

Knowing exactly when VB6 will use the default member and when it won't can be confusing. For example, if you declare y as Object, then doing TypeName(y) will return MyOwn.Class, but VarType(y) will still return 8 (string). However, if you declare y as Variant, then TypeName(y) returns String.

If you are using late-binding, it's hard to avoid this side-effect, since you'll only be able to declare your object variable as Object or Variant.

Mike Spross
The MyOwn.Object is a .Net 1.1 library and it's properties and members aren't listed, only the class names. I don't see any default properties defined.
ssorrrell
Aha. Since it is a .NET library, it's quite possible that the library is exposing the class's `ToString` method as the default. Since you mentioned that the string you get back is the name of the class, I'd say that this is very likely what is happening.
Mike Spross
I say that because `ToString` returns the full name of the class unless it's overridden.
Mike Spross
Revealing the ToString method would explain it.
ssorrrell