Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mac Callback #129

Closed
leaanthony opened this issue Apr 12, 2023 · 11 comments
Closed

Mac Callback #129

leaanthony opened this issue Apr 12, 2023 · 11 comments

Comments

@leaanthony
Copy link

leaanthony commented Apr 12, 2023

Hi there!

I'm trying to hook into Apple's GCD method dispatch_async to schedule callbacks on the main thread. I've gotten pretty far but the final call never returns.

The obj-c code I'm trying to replace is:

	dispatch_async(dispatch_get_main_queue(), ^{
		// some code
	});

I discovered that dispatch_get_main_queue is actually a macro and returns the address of _dispatch_main_q.
The following is where I'm up to. It feels like I'm missing just one final piece:

func dispatchTest() {
	libSystem, err := purego.Dlopen("libSystem.dylib", purego.RTLD_NOW|purego.RTLD_GLOBAL)
	if err != nil {
		panic(err)
	}
	dispatchMainQueue, err := purego.Dlsym(libSystem, "_dispatch_main_q")
	if err != nil {
		panic(err)
	}
	var dispatch_async func(uintptr, uintptr)
	goFunc := func() {
		fmt.Println("Hello from the main thread")
	}
	callback := purego.NewCallback(goFunc)
	purego.RegisterLibFunc(&dispatch_async, libSystem, "dispatch_async")
	dispatch_async(dispatchMainQueue, callback)
	println("i never get here")
}

func main() {

	// Since the error wasn't checked before check it before calling any code.
	NSApp := NewNSSharedApplication()
	go func() {
		time.Sleep(1 * time.Second)
		dispatchTest()
	}()
	NSApp.Run()
}

The NSApp code is just the same as from last ticket so left out to simplify the example.
Nothing is crashing or suggesting anything bad is happening. Am I using NewCallback correctly? My guess is that it's to do with C vs Obj-C calling conventions...

Any help appreciated!

chip: Apple M2 Pro
macOS: 13.2.1 (22D68)

@TotallyGamerJet
Copy link
Collaborator

So it does crash on my system (M1 Max, 13.3.1).

From my research I've discovered that blocks are not the same thing as a normal C function pointer despite the fact that it's header definition would like you to believe so. My suspicions were raised by this gist. See Block_private.h which shows that its implementation is an ObjC object. Now there are no public nor private methods for creating a Block struct that I can find. So I don't think it's a good idea to attempt to create one of these. Here's an article about its implementation. I'd recommend choosing a different approach. One perhaps where you intercept the main run loop and use go channels to pass the Go callback and run it on the main thread. I think ebitengine does something similar using glfw

@leaanthony
Copy link
Author

leaanthony commented Apr 12, 2023

Thanks for taking the time to respond to this 🙏Blocks are NASTY! I'll look at how to get the main loop running in Go and pump Cocoa's event loop 👍

@TotallyGamerJet
Copy link
Collaborator

@leaanthony Hey! So I came across DarwinKit and it appears they support blocks. Looking at the code it appears to also be possible to do with purego. I hope this helps! https://github.com/progrium/macdriver/blob/main/objc/block.go

@leaanthony
Copy link
Author

Thanks for replying! How would it be possible in purego?

@TotallyGamerJet
Copy link
Collaborator

This gist is basically replacing each call to C with one using purego

@leaanthony
Copy link
Author

The linked source code appears to use CGO? Maybe I'm missing something?

@TotallyGamerJet
Copy link
Collaborator

Yes replace the calls to Cgo with purego calls to those same functions

@hajimehoshi
Copy link
Member

I'd like to use dispatch_data_create, and this requries a block. In the current situation, we should create a block by ourselves reffering https://github.com/progrium/macdriver/blob/main/objc/block.go, right?

@TotallyGamerJet
Copy link
Collaborator

I'd like to use dispatch_data_create, and this requries a block. In the current situation, we should create a block by ourselves reffering https://github.com/progrium/macdriver/blob/main/objc/block.go, right?

Yes refer to that code on how to implement a dispatch block.

@leaanthony
Copy link
Author

Amazing! If you get a basic example working, please share! 🙏

@hajimehoshi
Copy link
Member

In my use case, passing 0 (NULL) to a block argument was enough, so I realized I didn't have to port that...

K1000000 pushed a commit to K1000000/purego that referenced this issue Jul 16, 2024
Adds new type, objc.Block, which is an objc.ID referencing an
Objective-C "block" function pointer.

Adds methods to create a Block from a Go function value,
get a Go function value from a block, directly invoke
a block function, and handle Objective-C memory management
(e.g. Copy/Release).

Mitigates pressure on purego Callback limit by relying on the
fact the first argument passed to a block implementation is the
block itself. This allows for a single callback to handle every
block instance that has the same signature, by way of keeping
an association between the Go func value and the block instance
it was used to create.

Was refactored from code in a different personal project to
better fit the purego convetions and architecture.

Directly addresses (closed) purego issue:
ebitengine#129
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants