Skip to content

Instantly share code, notes, and snippets.

@andlabs
Last active September 15, 2016 16:00
Show Gist options
  • Save andlabs/9282874 to your computer and use it in GitHub Desktop.
Save andlabs/9282874 to your computer and use it in GitHub Desktop.
// 28 february 2014
package main
/*
These are wrapper functions for the functions in bleh_darwin.m to wrap around stdint.h type casting.
This will eventually be expanded to include the other Objective-C runtime support functions.
*/
// #cgo LDFLAGS: -lobjc -framework Foundation
// #include "objc_darwin.h"
import "C"
func objc_msgSend_rect(obj C.id, sel C.SEL, x int, y int, w int, h int) C.id {
return C._objc_msgSend_rect(obj, sel,
C.int64_t(x), C.int64_t(y), C.int64_t(w), C.int64_t(h))
}
func objc_msgSend_uint(obj C.id, sel C.SEL, a uintptr) C.id {
return C._objc_msgSend_uint(obj, sel, C.uintptr_t(a))
}
func objc_msgSend_rect_uint_uint_bool(obj C.id, sel C.SEL, x int, y int, w int, h int, b uintptr, c uintptr, d C.BOOL) C.id {
return C._objc_msgSend_rect_uint_uint_bool(obj, sel,
C.int64_t(x), C.int64_t(y), C.int64_t(w), C.int64_t(h),
C.uintptr_t(b), C.uintptr_t(c), d)
}
/* 28 february 2014 */
/*
I wanted to avoid invoking Objective-C directly, preferring to do everything directly with the API. However, there are some things that simply cannot be done too well; for those situations, there's this. It does use the Objective-C runtime, eschewing the actual Objective-C part of this being an Objective-C file.
The main culprits are:
- data types listed as being defined in nonexistent headers
- 32-bit/64-bit type differences that are more than just a different typedef
Go wrapper functions (bleh_darwin.go) call these directly and take care of stdint.h -> Go type conversions.
*/
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdint.h>
#include <Foundation/NSGeometry.h>
/*
NSUInteger is listed as being in <objc/NSObjCRuntime.h>... which doesn't exist. Rather than relying on undocumented behavior or explicitly typedef-ing NSUInteger to the (documented) unsigned long, I'll just place things here for maximum safety. I use uintptr_t as that should encompass every possible unsigned long.
*/
id _objc_msgSend_uint(id obj, SEL sel, uintptr_t a)
{
return objc_msgSend(obj, sel, (NSUInteger) a);
}
/*
These are the objc_msgSend() wrappers around NSRect. The problem is that while on 32-bit systems, NSRect is a concrete structure, on 64-bit systems it's just a typedef to CGRect. While in practice just using CGRect everywhere seems to work, better to be safe than sorry.
I use int64_t for maximum safety, as my coordinates are stored as Go ints and Go int -> C int (which is what is documented as happening) isn't reliable.
*/
#define OurRect() (NSMakeRect((CGFloat) x, (CGFloat) y, (CGFloat) w, (CGFloat) h))
id _objc_msgSend_rect(id obj, SEL sel, int64_t x, int64_t y, int64_t w, int64_t h)
{
return objc_msgSend(obj, sel, OurRect());
}
id _objc_msgSend_rect_uint_uint_bool(id obj, SEL sel, int64_t x, int64_t y, int64_t w, int64_t h, uintptr_t b, uintptr_t c, BOOL d)
{
return objc_msgSend(obj, sel, OurRect(), (NSUInteger) b, (NSUInteger) c, d);
}
// 27 february 2014
package main
import (
"fmt"
"unsafe"
)
// #cgo CFLAGS: -Dqqq
// #cgo LDFLAGS: -lobjc -framework Foundation
// #include <stdlib.h>
// #include "objc_darwin.h"
// extern void windowShouldClose(id, SEL, id);
// extern void buttonClicked(id, SEL, id);
// extern void gotNotification(id, SEL, id);
// /* cgo doesn't like nil or Nil */
// extern Class NilClass; /* in runtimetest.go because of cgo limitations */
// extern id Nilid;
import "C"
var NSObject = C.object_getClass(objc_getClass("NSObject"))
func newClass(name string) C.Class {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
c := C.objc_allocateClassPair(NSObject, cname, 0)
if c == C.NilClass {
panic("unable to create Objective-C class " + name)
}
return c
}
//export windowShouldClose
func windowShouldClose(self C.id, sel C.SEL, sender C.id) {
fmt.Println("-[hello windowShouldClose:]")
C.objc_msgSend_id(NSApp,
sel_getUid("stop:"),
sender)
}
//export buttonClicked
func buttonClicked(self C.id, sel C.SEL, sender C.id) {
fmt.Println("button clicked; sending notification...")
notify("button")
}
//export gotNotification
func gotNotification(self C.id, sel C.SEL, object C.id) {
source := (*C.char)(unsafe.Pointer(
C.objc_msgSend_noargs(object,
sel_getUid("UTF8String"))))
fmt.Println("got notification from %s",
C.GoString(source))
}
func addOurMethod(class C.Class, sel C.SEL, imp C.IMP) {
// ty := []C.char{'v', '@', ':', 0} // according to the example for class_addMethod()
ty := []C.char{'v', '@', ':', '@', 0}
// clas methods get stored in the metaclass; the objc_allocateClassPair() docs say this will work
// metaclass := C.object_getClass(C.id(unsafe.Pointer(class)))
// ok := C.class_addMethod(metaclass,
ok := C.class_addMethod(class,
sel,
imp,
&ty[0])
if ok == C.BOOL(C.NO) {
panic("unable to add ourMethod")
}
}
func mk(name string, selW C.SEL, selB C.SEL, selN C.SEL) C.id {
class := newClass(name)
addOurMethod(class, selW,
// using &C.ourMethod causes faults for some reason
C.IMP(unsafe.Pointer(C.windowShouldClose)))
C.objc_registerClassPair(class)
addOurMethod(class, selB,
C.IMP(unsafe.Pointer(C.buttonClicked)))
addOurMethod(class, selN,
C.IMP(unsafe.Pointer(C.gotNotification)))
return objc_getClass(name)
}
/* 28 february 2014 */
/*
This includes all Objective-C runtime headers for convenience. It also creates wrappers around objc_msgSend() out of necessity.
cgo doesn't support calling variable argument list C functions, so objc_msgSend() cannot be called directly.
Furthermore, Objective-C selectors work by basically sending the arguments to objc_msgSend() verbatim across the wire. This basically means we're stuck making wrapper functions for every possible argument list. What fun!
The format should be self-explanatory.
*/
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdint.h>
/* TODO this HAS to be unsafe, but <objc/NSObjCRuntime.h> not found?! */
typedef unsigned long NSUInteger;
inline id objc_msgSend_noargs(id obj, SEL sel)
{
return objc_msgSend(obj, sel);
}
#define m1(name, type1) \
inline id objc_msgSend_ ## name (id obj, SEL sel, type1 a) \
{ \
return objc_msgSend(obj, sel, a); \
}
#define m2(name, type1, type2) \
inline id objc_msgSend_ ## name (id obj, SEL sel, type1 a, type2 b) \
{ \
return objc_msgSend(obj, sel, a, b); \
}
#define m3(name, type1, type2, type3) \
inline id objc_msgSend_ ## name (id obj, SEL sel, type1 a, type2 b, type3 c) \
{ \
return objc_msgSend(obj, sel, a, b, c); \
}
#define m4(name, type1, type2, type3, type4) \
inline id objc_msgSend_ ## name (id obj, SEL sel, type1 a, type2 b, type3 c, type4 d) \
{ \
return objc_msgSend(obj, sel, a, b, c, d); \
}
m1(str, char *) /* TODO Go string? */
m1(id, id)
extern id _objc_msgSend_rect(id obj, SEL sel, int64_t x, int64_t y, int64_t w, int64_t h);
m1(sel, SEL)
extern id _objc_msgSend_uint(id obj, SEL sel, uintptr_t a);
m2(id_id, id, id)
m3(id_id_id, id, id, id)
m3(sel_id_bool, SEL, id, BOOL)
extern id _objc_msgSend_rect_uint_uint_bool(id obj, SEL sel, int64_t x, int64_t y, int64_t w, int64_t h, uintptr_t b, uintptr_t c, BOOL d);
m4(id_sel_id_id, id, SEL, id, id)
// 27 february 2014
package main
import (
"fmt"
"unsafe"
"time"
)
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include <stdlib.h>
// #include "objc_darwin.h"
// Class NilClass = Nil; /* for newtypes.go */
// id Nilid = nil;
import "C"
func objc_getClass(class string) C.id {
cclass := C.CString(class)
defer C.free(unsafe.Pointer(cclass))
return C.objc_getClass(cclass)
}
func sel_getUid(sel string) C.SEL {
csel := C.CString(sel)
defer C.free(unsafe.Pointer(csel))
return C.sel_getUid(csel)
}
var NSApp C.id
var defNC C.id
var delegate C.id
var notesel C.SEL
func init() {
// need an NSApplication first - see https://github.com/TooTallNate/NodObjC/issues/21
NSApplication := objc_getClass("NSApplication")
sharedApplication := sel_getUid("sharedApplication")
NSApp = C.objc_msgSend_noargs(NSApplication, sharedApplication)
defNC = C.objc_msgSend_noargs(
objc_getClass("NSNotificationCenter"),
sel_getUid("defaultCenter"))
selW := sel_getUid("windowShouldClose:")
selB := sel_getUid("buttonClicked:")
selN := sel_getUid("gotNotification:")
mk("hello", selW, selB, selN)
delegate = C.objc_msgSend_noargs(
objc_getClass("hello"),
alloc)
notesel = selN
}
const (
NSBorderlessWindowMask = 0
NSTitledWindowMask = 1 << 0
NSClosableWindowMask = 1 << 1
NSMiniaturizableWindowMask = 1 << 2
NSResizableWindowMask = 1 << 3
NSTexturedBackgroundWindowMask = 1 << 8
)
const (
// NSBackingStoreRetained = 0 // "You should not use this mode."
// NSBackingStoreNonretained = 1 // "You should not use this mode."
NSBackingStoreBuffered = 2
)
const (
NSRoundedBezelStyle = 1
)
var alloc = sel_getUid("alloc")
func notify(source string) {
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
// we need to make an NSAutoreleasePool, otherwise we get leak warnings on stderr
pool := C.objc_msgSend_noargs(
objc_getClass("NSAutoreleasePool"),
sel_getUid("new"))
src := C.objc_msgSend_str(
objc_getClass("NSString"),
sel_getUid("stringWithUTF8String:"),
csource)
C.objc_msgSend_sel_id_bool(
delegate,
sel_getUid("performSelectorOnMainThread:withObject:waitUntilDone:"),
notesel,
src,
C.BOOL(C.YES)) // wait so we can properly drain the autorelease pool; on other platforms we wind up waiting anyway (since the main thread can only handle one thing at a time) so
C.objc_msgSend_noargs(pool,
sel_getUid("release"))
}
func main() {
NSWindow := objc_getClass("NSWindow")
NSWindowinit :=
sel_getUid("initWithContentRect:styleMask:backing:defer:")
setDelegate := sel_getUid("setDelegate:")
makeKeyAndOrderFront := sel_getUid("makeKeyAndOrderFront:")
style := uintptr(NSTitledWindowMask | NSClosableWindowMask)
backing := uintptr(NSBackingStoreBuffered)
deferx := C.BOOL(C.YES)
window := C.objc_msgSend_noargs(NSWindow, alloc)
window = objc_msgSend_rect_uint_uint_bool(window, NSWindowinit,
100, 100, 320, 240,
style, backing, deferx)
C.objc_msgSend_id(window, makeKeyAndOrderFront, window)
C.objc_msgSend_id(window, setDelegate,
delegate)
windowView := C.objc_msgSend_noargs(window,
sel_getUid("contentView"))
NSButton := objc_getClass("NSButton")
button := C.objc_msgSend_noargs(NSButton, alloc)
button = objc_msgSend_rect(button,
sel_getUid("initWithFrame:"),
20, 20, 200, 200)
C.objc_msgSend_id(button,
sel_getUid("setTarget:"),
delegate)
C.objc_msgSend_sel(button,
sel_getUid("setAction:"),
sel_getUid("buttonClicked:"))
objc_msgSend_uint(button,
sel_getUid("setBezelStyle:"),
NSRoundedBezelStyle)
C.objc_msgSend_id(windowView,
sel_getUid("addSubview:"),
button)
go func() {
for {
<-time.After(5 * time.Second)
fmt.Println("five seconds passed; sending notification...")
notify("timer")
}
}()
C.objc_msgSend_noargs(NSApp,
sel_getUid("run"))
}
func helloworld() {
_hello := C.CString("hello, world\n")
defer C.free(unsafe.Pointer(_hello))
NSString := objc_getClass("NSString")
stringWithUTF8String :=
sel_getUid("stringWithUTF8String:")
str := C.objc_msgSend_str(NSString,
stringWithUTF8String,
_hello)
UTF8String := sel_getUid("UTF8String")
res := C.objc_msgSend_noargs(str,
UTF8String)
cres := (*C.char)(unsafe.Pointer(res))
fmt.Printf("%s", C.GoString(cres))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment