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

Methods being skipped when generating wrapping #130

Closed
programmingkidx opened this issue Jul 8, 2023 · 5 comments
Closed

Methods being skipped when generating wrapping #130

programmingkidx opened this issue Jul 8, 2023 · 5 comments

Comments

@programmingkidx
Copy link
Contributor

I am trying to add NSNotificationCenter to MacDriver. MacSchema was able to create a schema file successfully. When I look inside the file I do see the postNotification method and others there. For some reason I can't generate bindings for methods like postNotification. In the terminal I see these messages that tell me these methods are being skipped:

2023/07/08 19:17:32 skip NSNotificationCenter.addObserver:selector:name:object:: mapType: NSNotificationName
2023/07/08 19:17:32 skip NSNotificationCenter.addObserverForName:object:queue:usingBlock:: mapType: NSNotificationName
2023/07/08 19:17:32 skip NSNotificationCenter.postNotification:: pointers schema.DataType{Name:"NSNotification", IsPtr:true, IsPtrPtr:false, Annotations:[]string(nil), FuncPtr:(*schema.Func)(nil), Block:(*schema.Func)(nil), Params:[]schema.DataType(nil)}
2023/07/08 19:17:32 skip NSNotificationCenter.postNotificationName:object:: mapType: NSNotificationName
2023/07/08 19:17:32 skip NSNotificationCenter.postNotificationName:object:userInfo:: mapType: NSNotificationName
2023/07/08 19:17:32 skip NSNotificationCenter.removeObserver:name:object:: mapType: NSNotificationName

Why are these methods being skipped?
Is there a way to make the gen command not skip these methods?

@progrium
Copy link
Owner

progrium commented Jul 8, 2023

They're being skipped because macdriver doesn't support them yet. I think in this case it's because they are using blocks/callbacks. For every callback signature we'd need to generate a CGO re-entry wrapper and the code to set that up. There is a very simple but also special case of this for the dispatch API, using a channel to queue Go functions to be called by a generic C callback sent to the actual dispatcher.
https://github.com/progrium/macdriver/tree/main/dispatch

I don't know if blocks are any different from function pointer callbacks.

@programmingkidx
Copy link
Contributor Author

I am trying to work with Objective-C methods calling Go functions. I think I have figured out this problem but I can't make my code compile due to a mysterious duplicate system error. Since it looks about done I thought would share it and see what you think.

package core

/*
 This file tries to implement NSNotificationCenter's AddObserver:selector:name:object in Go.
 Since NSNotificationCenter does call back methods on objects I have to make a system that
 can call Go functions instead. A table in Go is made that keeps track of a Go function,
 a Go object, and Go observer object. Each item in this table is identified by ID number.

 Well when AddObserverSelectorNameObject() is called it sends to its objective-c method the
 ID number and the name of the notification. A small class is made as a wrapper for the ID number
 called ObjCCallback. When a notification is delivered, the ObjCCallback instance calls the
 runCallbackFunc: method that then calls the Go CallGoCode() function. CallGoCode() is sent
 the ID and uses it to search the table for a matching instance. The Go function that the
 instance points to is finally called.
 */

/*
 #cgo CFLAGS: -x objective-c
 #cgo LDFLAGS: -framework foundation
 #include <foundation/foundation.h>
 
 extern void CallGoCode(int, char *, char *);
 
 // This class will be used to connect the ID field in
 // the GoCallback struct in Go to this class' goID field.
 @interface ObjCCallback : NSObject
 {
	 @public int goID;
 }
 
 - (void) runCallbackFunc: (NSNotification *) n;
 @end
 
 
 @implementation ObjCCallback
 
 - (void) runCallbackFunc: (NSNotification *) n
 {
	 char *name = [[n name] cString];
	 char *anObject = [[n object] cString];
	 CallGoCode(self->goID, name, anObject);
 }
 @end
 
 ObjCCallback *callbackObject;
 
 void addObserverSelectorName(int objectID, char *name)
 {
	callbackObject = [ObjCCallback new];
	callbackObject->goID = objectID;
	NSString *nameStr = [NSString stringWithCString: name];
    [[NSNotificationCenter defaultCenter] addObserver: callbackObject selector: @selector(runCallbackFunc:) name: nameStr object: nil];
 }
 
 void postNotificationNameObject(char *name, char *anObject) {
	NSString *nameStr = [NSString stringWithCString: name];
	if (anObject != nil) {
		NSString *objStr = [NSString stringWithCString: anObject];
		[[NSNotificationCenter defaultCenter] postNotificationName: nameStr object: objStr];
	}
	else {
		[[NSNotificationCenter defaultCenter] postNotificationName: nameStr object: nil];
	}
 }
 
 */
import "C"
import "fmt"

type NSNotificationCenter struct {
	gen_NSNotificationCenter
}

type GoCallback struct {
	ID int
	Callback func(n NSNotification)
	Observer interface{}
	AnObject interface{}
}


func (n NSNotificationCenter) PostNotificationNameObject(name string, anObject string) {
	cName := C.CString(name)
	cAnObject := C.CString(anObject)
	C.postNotificationNameObject(cName, cAnObject)
}

var goCallbackTable []GoCallback


// Keeps track of number of times newGoCallback is called.
// Used an an ID
var callbackCount int

// Creates a new GoCallback object
func newGoCallback(callbackFunc func(n NSNotification), anObserver interface{}, anObject interface{}) GoCallback {
	callbackCount++
	return GoCallback{callbackCount, callbackFunc, anObserver, anObject}
}

func (n NSNotificationCenter) AddObserverSelectorNameObject(anObserver interface{}, callbackFunc func(n NSNotification),
															name string, object interface{}) {
	newObserver := newGoCallback(callbackFunc, anObserver, object)
	goCallbackTable = append(goCallbackTable, newObserver)
	C.addObserverSelectorName(C.int(newObserver.ID), C.CString(name))
}

//export CallGoCode
func CallGoCode(CID C.int, CName *C.char, CAnObject *C.char) {
	// Convert arguments to Go types
	ID := int(CID)
	name := C.GoString(CName)
	anObject := C.GoString(CAnObject)
	
	fmt.Println("Hello from Go:", ID, name, anObject)  // Maybe anObject can only be a string 🤨
	for _, callbackObj := range goCallbackTable {
		if callbackObj.ID == ID  {
			if callbackObj.AnObject != nil && callbackObj.AnObject == anObject {
				notification := NSNotification{name, anObject}
				callbackObj.Callback(notification)
			} else {
				notification := NSNotification{name, anObject}
				callbackObj.Callback(notification)
			}
		}
		break
	}
}

@progrium
Copy link
Owner

I don't remember specifics but this might be related to an issue I ran into with the dispatch package. It's why the Go exported dispatcher is in a separate file. Try putting your CallGoCode into a separate source file.

@Charliego3
Copy link
Contributor

I also have the same question.

2023/07/11 17:05:59 skip NSToolbarItem.itemWithItemIdentifier:barButtonItem:: mapType: NSToolbarItemIdentifier
2023/07/11 17:05:59 skip NSToolbarItem.initWithItemIdentifier:: mapType: NSToolbarItemIdentifier
2023/07/11 17:05:59 skip NSToolbarItem.itemIdentifier: mapType: NSToolbarItemIdentifier
2023/07/11 17:05:59 skip NSSegmentedControl.segmentedControlWithImages:trackingMode:target:action:: mapType: NSSegmentSwitchTracking
2023/07/11 17:05:59 skip NSSegmentedControl.segmentedControlWithLabels:trackingMode:target:action:: mapType: NSSegmentSwitchTracking

Is it because there is no NSSegmentSwitchTracking enum and NSToolbarItemIdentifier generated?

@progrium
Copy link
Owner

Callbacks are supported in this PR and probably these methods and enums too.
#176

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