ByRef doesn't make it across invoke?

L

Luc

I just found that passing arguments by reference to a function across
object.Invoke doesn't work (or rather, getting them back *from* the
function is what doesn't work).

Is that by design?

What do you think is the best way to get more data than just a simple
return value back from a function that's called by invoke?
I thought of passing in a class as argument (ByVal) and returning an
updated copy as return value.


I think a code sample illustrates it best.
The code below prints these two lines in the immediate window:

Same-thread call: result 1, string 'Succeeded'
Cross-thread invoked call: result 1, string 'Failed'


The code calls the same function twice, first directly, then it starts a
worker thread from where it's called through Me.Invoke.

Start a new VS2008 windows forms project, paste the code below in the
form's code window.

Line continuation is used only to prevent long lines from being broken by
my newsreader, you may want to re-join those lines for better readability.


Imports System.Threading

Public Class Form1

Private MyThread As Thread
Private Shared StaticString As String

Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
StaticString = "Failed"
Dim rv As Integer = Func1(StaticString)
Debug.WriteLine("Same-thread call: result " & rv _
& ", string '" & StaticString & "'")

MyThread = New Thread(AddressOf ThreadMain)
MyThread.Start()
End Sub

Private Sub ThreadMain()
Dim rv As Integer = Func1(StaticString)
StaticString = "Failed"
Debug.WriteLine("Cross-thread invoked call: result " & rv _
& ", string '" & StaticString & "'")
End Sub

Private Delegate Function dFunc1(ByRef s As String) As Integer
Private Function Func1(ByRef s As String) As Integer
If Me.InvokeRequired Then
Return DirectCast(Me.Invoke(New dFunc1(AddressOf Func1), _
s), Integer)
Else
s = "Succeeded"
Return 1
End If
End Function

End Class
 
L

Luc

I said:
Private Sub ThreadMain()
Dim rv As Integer = Func1(StaticString)
StaticString = "Failed"
Debug.WriteLine("Cross-thread invoked call: result " & rv _
& ", string '" & StaticString & "'")
End Sub

I just noticed a little bug here, but it doesn't change anything (it
slipped in only in the sample code).

Make that
Private Sub ThreadMain()
StaticString = "Failed"
Dim rv As Integer = Func1(StaticString)
Debug.WriteLine("Cross-thread invoked call: result " & rv _
& ", string '" & StaticString & "'")
End Sub

and it still prints "Failed".
 
M

Michel Posseth [MCP]

Luc said:
I just noticed a little bug here, but it doesn't change anything (it
slipped in only in the sample code).

Make that


and it still prints "Failed".

Hello

It would be a good idea to show us what is what is happening in Func1 , to
answer your question on how someone would retrive values from a asynchronous
methods well i just use a event structure , that works fine for me


HTH

Michel Posseth


Michel
 
A

Armin Zingler

Luc said:
Private Function Func1(ByRef s As String) As Integer
If Me.InvokeRequired Then
Return DirectCast(Me.Invoke(New dFunc1(AddressOf Func1), _
s), Integer)

The 2nd argument is declared: ByVal ParamArray args() As Object

So, variable s is put into the array of objects before the function is
called. The changed value is also stored back in the array. The problem is
that you never put the changed value back into variable s. Try this:

Dim values(0) As String
dim retValue as integer
values(0) = s
retValue = DirectCast(Invoke(New dFunc1(AddressOf Func1), values),
integer)
s = values(0)
return retValue


Armin
 
L

Luc

The 2nd argument is declared: ByVal ParamArray args() As Object

So, variable s is put into the array of objects before the function is
called. The changed value is also stored back in the array. The problem is
that you never put the changed value back into variable s. Try this:

Dim values(0) As String
dim retValue as integer
values(0) = s
retValue = DirectCast(Invoke(New dFunc1(AddressOf Func1), values),
integer)
s = values(0)
return retValue


Armin

You're probably right, maybe I was thinking in C too much.

The way I saw it was: s is passed to Func1 by reference, so the function
gets a pointer. Then Invoke uses a copy of that pointer, but still
pointing to the same thing.
 
L

Luc

Hello

It would be a good idea to show us what is what is happening in Func1 , to
answer your question on how someone would retrive values from a asynchronous
methods well i just use a event structure , that works fine for me

The full code could be found one message upstream in the thread. There was
a bug in it there, the message you replied to just fixed that bug.

What Armin Zingler said in his reply is probably correct: Me.Invoke takes
the arguments to the invoked function by value, and that's what causes it.

Func1 gets a pointer to the original string (because it's passed ByRef),
then has to pass it ByVal. Somehow I expected it to act like C and pass a
new copy of the pointer, still pointing to the original string, but that
doesn't make sense in VB of course: the compiler creates a copy of the
original string instead and passes that.



Here is the full original code again, without the bug:

Imports System.Threading

Public Class Form1

Private MyThread As Thread
Private Shared StaticString As String

Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
StaticString = "Failed"
Dim rv As Integer = Func1(StaticString)
Debug.WriteLine("Same-thread call: result " & rv _
& ", string '" & StaticString & "'")

MyThread = New Thread(AddressOf ThreadMain)
MyThread.Start()
End Sub

Private Sub ThreadMain()
StaticString = "Failed"
Dim rv As Integer = Func1(StaticString)
Debug.WriteLine("Cross-thread invoked call: result " & rv _
& ", string '" & StaticString & "'")
End Sub

Private Delegate Function dFunc1(ByRef s As String) As Integer
Private Function Func1(ByRef s As String) As Integer
If Me.InvokeRequired Then
Return DirectCast(Me.Invoke(New dFunc1(AddressOf Func1), _
s), Integer)
Else
s = "Succeeded"
Return 1
End If
End Function

End Class
 
G

Göran Andersson

Luc said:
Func1 gets a pointer to the original string (because it's passed ByRef),
then has to pass it ByVal. Somehow I expected it to act like C and pass a
new copy of the pointer, still pointing to the original string, but that
doesn't make sense in VB of course: the compiler creates a copy of the
original string instead and passes that.

No, it doesn't create a copy of the original string, it passes a copy of
the reference to the string.

If you were sending any mutable object to the method, it could change
the contents of the object. A string is immutable, which means that you
can never change the value of a string object. When you assign another
string to the variable, you don't change the string object that the
reference points to, you replace the reference with the referece to the
new string.

When you return from the method, the changed reference is then put back
in the object array that was sent to the Invoke method, so you need to
get the new reference from the array.
 
L

Luc

If you were sending any mutable object to the method, it could change
the contents of the object. A string is immutable, which means that you
can never change the value of a string object. When you assign another
string to the variable, you don't change the string object that the
reference points to, you replace the reference with the referece to the
new string.

When you return from the method, the changed reference is then put back
in the object array that was sent to the Invoke method, so you need to
get the new reference from the array.

Thanks for pointing out where my reasoning was wrong.
I'm always interested in learning details like this.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top