views:

407

answers:

5

I have a C# application that reference a VB6 dll. When I pass null from C# into VB6 dll function, the null is translated as value Empty (value) in VB6, instead of Nothing (object). For example:

 // function in vb6 dll that referenced by c# app
 Public Sub TestFunc(ByVal oValue As Variant)
 {
   ...
   if oValue is Nothing then
     set oValue = someObject
   end if
   ...

 }

 // main c# code
 private void Form1_Load(object sender, EventArgs e)
 {
    object testObject = new object();
    testObject = null;
    TestFunc(testObject);
 }

When I pass an object (not null) then it will be passed into the VB6 as object. But when null passed into vb6, it becomes value type Empty, instead of object type Nothing. Any one knows why? and is there anyway I can force null as Nothing in VB6 when passed from c# app?

Thanks a lot.

+2  A: 

Have you tried:

Public Sub TestFunc(ByVal oValue As Variant)
 {
   ...
   If oValue Is Nothing Then
     Set oValue = someObject
   ElseIf IsEmpty(oValue) Then
     Set oValue = someObject
   End If
   ...

 }

Edit - And I would agree wtih Sander Rijken's answer as to why Empty is being returned instead of null

Heather
I tried this and it works. But I am not suppose to change any code in the VB6 dll, because it can be used by other apps. I wounder is there any way I can force null object marshaled into VB6 as object nothing? Public Sub TestFunc(ByVal oValue As Variant) is translated as Void TestFunc(object oValue) in c# when I referecing the VB6 Dll. It also correctly marshale an initialized object as object in VB6. It only incorrectly marshaled a null object as value type into vb6.
tiftif
+1. Pragmatic solution. Put this new test beneath the block `If oValue Is Nothing Then ... End If` and it shouldn't change the behaviour for other apps. Presumably the other apps are never passing `Empty` for this argument.
MarkJ
@tiftif - I agree with MarkJ here, why not put this code beneath the current VB6 code block checking for `Nothing`?
Heather
+1 from me too - This is simple solution that, as MarkJ and Heather already mentioned, shouldn't affect other code calling the function (if it did, I would consider that a bug in the other apps anyway, since the function wasn't originally written to handle `Empty` specially).
Mike Spross
I agree this would be my wrokaround for the timebing, while investigating options for customising the marshalling MarkJ and Robert suggested. Thanks a lot everyone for your help.
tiftif
+3  A: 

The reason is probably that it's a ByVal function. null is probably marshaled to a valuetype Variant that's as 'null' as possible.

Sander Rijken
`ByVal` doesn't mean it has to be a value type, it just means if an object variable is passed, the routine can't point it to a different object. VB6 Variants can hold `Nothing`
MarkJ
See my answer for a bit more on this.
MarkJ
+4  A: 

Some more information, rather than an answer. I just ran this VB6 scratch program to confirm whether Nothing can be passed ByVal. It can be.

Private Sub Form_Load()
  Call TestSub(Nothing)
End Sub
Private Sub TestSub(ByVal vnt As Variant)
  Debug.Print VarType(Nothing)
  Debug.Print VarType(vnt)
  If vnt Is Nothing Then Debug.Print "vnt Is Nothing"
  If IsEmpty(vnt) Then Debug.Print "vnt Is Empty"
End Sub

I got the following output. Note that 9 is vbObject and indicates a Variant holding an Object reference.

 9 
 9 
vnt Is Nothing

I haven't tested moving TestStub into another component but I think that'd still work. So I think the .NET marshalling to COM could do better.

MarkJ
Yeah the problem is probably in the marshaling, I'm not sure though wether it's fixable
Sander Rijken
I don't know whether it's fixable. I believe the .NET marshalling is very customisable but I know very little about it
MarkJ
+2  A: 

Since you aren't supposed to modify the functions in the VB dll, what about adding one?

Public Sub TestFuncEx(ByVal oValue As Variant)
{
   If IsEmpty(oValue) Then
    TestFunc(Nothing)
   Else
    TestFunc(oValue)
   End If
}

Presumably this would work as 1) it's a sub an not a function, so there's no return value and 2) you're passing by value so it's not modifying the object itself.

BarrettJ
+1 for not modifying stable code.
gWiz
A: 

As the parameter for the VB6 method is a Variant you should be testing for Nothing, Missing and Empty because they are the possible "not value or object" that can be passed when no real value or object is available.

In answer to your question, i think it is because a Variant in VB6 defaults to Empty, if your parameter was of type Object Nothing will be passed in, in your case.

You may want to try passing Nothing from VB.Net and seeing what happens, if the Nothing persists to the VB6 DLL then you know it is possible to pass it in and will find the answer by looking at the IL code.

Robert
I suppose it's worth trying passing `Nothing` from VB.Net, but I've always read Vb.Net `Nothing` is equivalent to C# `null`. I think it would be more promising to investigate options for customising the marshalling from .Net to COM
MarkJ
In the managed world VB.Nets Nothing and C# null are the same but the interop code generator that generates the interop DLL works differently. This i think is because of different design goals at the beginning when the VB.Net team worked independently of the C# team. I think the VB.Net team had an impetuses to consider how VB.Net interacts with VB6 code and as a result used different techniques to accomplish their goal.
Robert
Isn't the interop code generator the same in both VB.Net and C#? Certainly there's only one standalone version of it: `tlbimp` http://msdn.microsoft.com/en-us/library/tt0cf3sx.aspx
MarkJ