progrium/darwinkit

Methods being skipped when generating wrapping

Closed this issue · 5 comments

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?

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.

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
	}
}

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.

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?

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