PSA: Don't convert a Delegate to a Pointer

Apparently passing

AddressOf SomeMethod

Into a routine as a pointer parameter and then converting the pointer back into a new delegate is bad.

No compile time errors but invoking the delegate causes hard crashes or the delegate.invoke works but the method immediately returns ignoring subsequent code!

Who knew! Well, you did, but I apparently didn’t.

It would be nice to understand why this happens.

Has anyone looked into rthis?

There be demons in that box! Or at least daemons.

Sample project?

It may just be working as designed…
Did you keep reference to the delegate object?

I would imagine it is because a delegate is usually a pointer to a method + a pointer to a instance.

And in this process he described above the pointer to the instance probably got lost.

If you make a Delegate to a method of an object, then the new Delegate object that’s created holds both the method address and the object instance reference, and if you make a Ptr from that Delegate, then that jump pointer is jumping into a little bit of code inside the Delegate object that will load the instance ref and then jump to the actual Xojo method. This extra code is called a stub.

Now, I’d think, like Christian also pointed out, that if you keep your Delegate object around, i.e. stored it in a global property, then you should be able to do what Stephen says. But if you drop the Delegate object, then it’ll get deallocated, and in turn the stub code may get overwritten, and you end up with a crash if you invoke the Ptr that jumps into the stub code.

What does PSA mean here? I don’t think is the test for prostate cancer and it’s also not Peugeot Socit Anonyme (I’m still in recovery from that one).

Proper Safety Announcement?

PSA = Public Safety Announcement

I think I found a bug in the Delegate handling, indeed: The way I explained it is how it WOULD / SHOULD work. However, when I test this, I see that the delegate I call (after recreating it from a Ptr) does NOT contain the correct object reference, i.e. the one the method belonged to, but it instead uses the object ref of the CALLER.

Here’s a demo project showing this bug: http://files.tempel.org/RB/Bugs/Delegate%20from%20Ptr%20bug.rbp

So it’s clearly just a case of not passing the correct object instance to the invoked method.

Searching the Feedback reports it appears to me that Xojo believes that this kind of crash is “by design”.

That’s disappointing as it appears it’s just a misunderstanding of how a Delegate stub has to be generated, and it could be fixed.

The result of the second delegate call is “App” in your IDE?
In current Xojo the result is a NOE because Self in Testmethod is Nil. (Just in case this should make a difference.)

Ah, right, Ulrich: I tested this with Real Studio 2012r2. I see that newer Xojo versions set self to nil - not sure if that’s a precaution Xojo added on purpose to avoid the previous nonsense self value, or if that’s a side effect of some other change.

Would have to look at the compiled code to figure this out.

I’ve updated the demo project to deal with the “self is nil” case.

And if Xojo added this “fix” on purpose, then why didn’t they instead fix it properly, by passing the correct instance instead of nil?

Doesn’t a Delegate hold the instance reference anyway, so that the object does not get freed as long as the delegate exists (unless you use “WeakAddressOf”)? So, the instance must be stored in the delegate - what could be the reason for not passing it when calling the instance method, then?

Weird.

@Stephen Dodd I believe that you got this wrong. If you hold on to the Delegate and if you use AddressOf with a global or shared method, then you can convert the value from AddressOf to a Ptr and back to a Delegate just fine. It’s only with instance methods, where this fails. Have you checked?

I had a look at the disassembly and I think I know where the bug is.

With a delegate there are two entry points:

  1. The entry into the target method that you used with AddressOf
  2. The stub code that Xojo generates and that sets up the calling environment for the target method, such as setting the self parameter if the target is an instance method.

When you create a delegate and call its Invoke method, then Xojo jumps to #2, i.e. the stub, that in turn calls #1, the target.

But when you convert a Delegate to a Ptr, then you get not the entry to the stub (#2) but to the target (#1). Which means that the necessary stub code does not get executed, hence self is not set as required.

Would the Delegate’s Ptr conversion return the address of the stub, we should be fine and not get crashes.

And if Xojo is concerned that this could break existing code because existing code may not be holding on to a Delegate (and thus the stub code would get overwritten, leading to a crash on invocation), this could be easily solved as well, with a test in the Ptr conversion: If the stub is for an instance method, then return the entry to the stub, and if it’s to a global/shared method, then return, like, now, the ptr to the target.

And then we’d have a working Delegate for instance methods (only caveat: One need to hold a reference to the Delegate - but then, whoever uses Ptrs and Delegates is advanced enough to not have a problem with this requirement).

Here’s a feedback request for this bug: <https://xojo.com/issue/53453>

Thanks for creating the feedback issue Thomas. I don’t have easy access to the original code anymore as I rewrote it.

I believe as Björn said, it is indeed holding a reference to only the method and not the instance of the method as another side effect was that calls from other instances of the method were coming up as already existing in my array of delegates to store.

It’s possible the language reference could use some clarification though I’m not entirely sure what. As a delegate newbie I could see:

  1. Delegates were constructed from a pointer
  2. While addressOf might return a Delegate, it didn’t return MY delegate
  3. I therefore had to find a way to construct MY delegate and a pointer seemed the only option.

I created a bug report to improve the documentation: <https://xojo.com/issue/53473>

Didn’t a – sorry – Delegate work for you in that case? Delegate definition 2; the method scheme that you create in the IDE I mean

Yes, in the end I simply passed AddressOf MethodToCallBack to something that took MyDelegate as a parameter and not a pointer as a parameter. Then there was no need to construct a delegate from a pointer. I didn’t realize that AddressOf SomeMethod would implicitly convert to MyDelegate. I thought it would convert to a generic delegate. Works now. All my misunderstanding how to construct delegates.