Why can't I use delegates of instance methods with external callbacks?

I must be getting senile, as I believe I must have understood this in the past.

Suppose I use a library (OSX: dylib) that provides function pointers for callbacks it can issue into my Xojo code.

I’d use code like this to pass my Xojo method to the library:

declare sub SetCallbackFunction lib "someLib" (callback as Ptr) SetCallbackFunction (AddressOf MyHandlerMethod)

That works, but only if MyHandlerMethod is a global or shared method.

If MyHandlerMethod is a non-shared method of a class or Window, then it’ll crash as soon as it tries to access a property of its class.

Now, why is that? I thought that if a delegate of an instance method is created, a stub code would be created that contains the instance’s self pointer, and that ptr will get reloaded when the delegate gets invoked. However, it seems that this is not the case, and the self ptr is nil, hence the crash. Of course, this can only work as long as the object remains alive, but I’ve ensured that in my testing.

What am I missing here?

are you sure someLib passes a pointer to the delegate object as first parameter?

No, it doesn’t. That’s why I talked about the stub - that should instead preserve the object ptr, just like Blocks do.

The caller (e.g. from someLib) can’t know the object ID anyway. If I create the delegate with AddressOf, then that’s where the object is known, so the delegate should remember it, not some other entity. I mean, one cannot even pass a Xojo object’s ptr to a declare, so that’s not an option.

It’s my understanding this is correct. I have a vague memory of Joe Ranieri mentioning it has to do with the ABI matching only on shared/global methods.

This is why MacOSLib uses a dispatch dictionary to bounce the callback from Shared to instance methods.

The problem is, however, that the language should protect against this kind of crash (which can’t be caught by an exception handler in Xojo), by making sure I cannot accidentally pass a “bad” delegate to a declare that way.

I ran into this problem because I’m providing a Xojo module that provides callbacks from a system lib. The user of this module is only shown a method like this:

sub SetCallback (handler as CallbackProc)

with CallbackProc being a Delegate.

Now, the user can pass both global and instance methods to it - the former work whereas the latter leads to a crash, and my code can’t protect the user from that mistake. That should not be possible. It’s a design bug.

So, I wonder, why can’t a stub be used in the way I suggested?

[quote=293636:@Thomas Tempelmann]The problem is, however, that the language should protect against this kind of crash (which can’t be caught by an exception handler in Xojo), by making sure I cannot accidentally pass a “bad” delegate to a declare that way.

[/quote]

It’s actually on my list of things to do (case 41804). There’s just one spot internally in the framework that relies on this and I haven’t reworked it yet.

How would the framework know how to manage the lifetime of the delegate, its target, and the stub itself?

The target could be retained (LockObject), or a WeakRef could be used, and if the target has vanished when the callback comes, you’d raise a runtime exception, or simply ignore the call and syslog it. Though, I admit, since it’s not sure where the call came from, the caller’s environment might not agree with your exception raising at that point.

But the lifetime of the stub…

Here’s a thought: How about attaching the stub’s storage to the object data storage it’s used with? The required information is available at compile time, after all, because AddressOf needs to be used for this. Therefore, the compiler could reserve a stub for each method that’s used with AddressOf. If the object dies, the stub dies along with it. That’s nothing worse than what we have now. But it would be an elegant solution to providing a stub without changing the language, about how Delegates are created and used. Admittedly, if the object has been freed when the callback is issued, we’ll still get a hard crash. But for the NORMAL case, where I program my code “properly”, i.e. where I logically take care of not storing callback to objects I don’t have allocated any more, this would solve the situation that’s currently not solved: Now I cannot even have object methods invoked by callbacks if I do everything right - with this stub it would work. Right?

Did something similar recently; how I got around it was to store the delegate in a dictionary. Pass the shared method from the function to the API, which then investigates the dictionary and calls the delegate.

It would be nice if we have a thread safe way to know if a thread is a Xojo thread.
That could help.

We all have to come up with something on our own – I do the same as you do –, but it would be definitively better if that was handled within the Xojo framework (I like Thomas’s proposal).