v0.2.0-alpha b26478a

objc_runtime_darwin.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3
4// Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best
5// to use a wrapper that provides the functionality you need in a safer way.
6package objc
7
8import (
9	"fmt"
10	"math"
11	"reflect"
12	"runtime"
13	"unsafe"
14
15	"github.com/ebitengine/purego"
16	"github.com/ebitengine/purego/internal/strings"
17)
18
19//TODO: support try/catch?
20//https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
21
22var (
23	objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
24
25	objc_msgSend              = purego.Dlsym(objc, "objc_msgSend")
26	objc_msgSendSuper2        = purego.Dlsym(objc, "objc_msgSendSuper2")
27	objc_getClass             = purego.Dlsym(objc, "objc_getClass")
28	objc_allocateClassPair    = purego.Dlsym(objc, "objc_allocateClassPair")
29	objc_registerClassPair    = purego.Dlsym(objc, "objc_registerClassPair")
30	sel_registerName          = purego.Dlsym(objc, "sel_registerName")
31	class_getSuperclass       = purego.Dlsym(objc, "class_getSuperclass")
32	class_getInstanceVariable = purego.Dlsym(objc, "class_getInstanceVariable")
33	class_addMethod           = purego.Dlsym(objc, "class_addMethod")
34	class_addIvar             = purego.Dlsym(objc, "class_addIvar")
35	ivar_getOffset            = purego.Dlsym(objc, "ivar_getOffset")
36	object_getClass           = purego.Dlsym(objc, "object_getClass")
37)
38
39// ID is an opaque pointer to some Objective-C object
40type ID uintptr
41
42// Class returns the class of the object.
43func (id ID) Class() Class {
44	ret, _, _ := purego.SyscallN(object_getClass, uintptr(id))
45	return Class(ret)
46}
47
48// Send is a convenience method for sending messages to objects.
49func (id ID) Send(sel SEL, args ...interface{}) ID {
50	tmp, keepAlive := createArgs(id, sel, args...)
51	defer keepAlive()
52	ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
53	return ID(ret)
54}
55
56// objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
57// as the receiver of a message. It specifies the class definition of the particular superclass that should
58// be messaged.
59type objc_super struct {
60	receiver   ID
61	superClass Class
62}
63
64// SendSuper is a convenience method for sending message to object's super
65func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
66	var super = &objc_super{
67		receiver:   id,
68		superClass: id.Class(),
69	}
70	tmp, keepAlive := createArgs(0, sel, args...)
71	defer keepAlive()
72	tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
73	ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
74	return ID(ret)
75}
76
77func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr, keepAlive func()) {
78	out = make([]uintptr, 2, len(args)+2)
79	out[0] = uintptr(cls)
80	out[1] = uintptr(sel)
81	var alive []interface{}
82	for _, a := range args {
83		switch v := a.(type) {
84		case ID:
85			out = append(out, uintptr(v))
86		case Class:
87			out = append(out, uintptr(v))
88		case SEL:
89			out = append(out, uintptr(v))
90		case IMP:
91			out = append(out, uintptr(v))
92		case bool:
93			if v {
94				out = append(out, uintptr(1))
95			} else {
96				out = append(out, uintptr(0))
97			}
98		case string:
99			c := strings.CString(v)
100			alive = append(alive, c)
101			// NOTE: that although the pointer will be kept alive
102			// if there are any stack splits before this uintptr
103			// is no longer needed the pointer will point to random memory.
104			out = append(out, uintptr(unsafe.Pointer(c)))
105		case unsafe.Pointer:
106			alive = append(alive, v)
107			// NOTE: that although the pointer will be kept alive
108			// if there are any stack splits before this uintptr
109			// is no longer needed the pointer will point to random memory.
110			out = append(out, uintptr(v))
111		case uintptr:
112			out = append(out, v)
113		case int:
114			out = append(out, uintptr(v))
115		case uint:
116			out = append(out, uintptr(v))
117		default:
118			panic(fmt.Sprintf("objc: unknown type %T", v))
119		}
120	}
121	return out, func() {
122		runtime.KeepAlive(alive)
123	}
124}
125
126// SEL is an opaque type that represents a method selector
127type SEL uintptr
128
129// RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
130// and returns the selector value.
131func RegisterName(name string) SEL {
132	n := strings.CString(name)
133	ret, _, _ := purego.SyscallN(sel_registerName, uintptr(unsafe.Pointer(n)))
134	runtime.KeepAlive(n)
135	return SEL(ret)
136}
137
138// Class is an opaque type that represents an Objective-C class.
139type Class uintptr
140
141// GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
142func GetClass(name string) Class {
143	n := strings.CString(name)
144	ret, _, _ := purego.SyscallN(objc_getClass, uintptr(unsafe.Pointer(n)))
145	runtime.KeepAlive(n)
146	return Class(ret)
147}
148
149// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
150func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
151	n := strings.CString(name)
152	ret, _, _ := purego.SyscallN(objc_allocateClassPair, uintptr(super), uintptr(unsafe.Pointer(n)), extraBytes)
153	runtime.KeepAlive(n)
154	return Class(ret)
155}
156
157// SuperClass returns the superclass of a class.
158// You should usually use NSObject‘s superclass method instead of this function.
159func (c Class) SuperClass() Class {
160	ret, _, _ := purego.SyscallN(class_getSuperclass, uintptr(c))
161	return Class(ret)
162}
163
164// AddMethod adds a new method to a class with a given name and implementation.
165// The types argument is a string containing the mapping of parameters and return type.
166// Since the function must take at least two arguments—self and _cmd, the second and third
167// characters must be “@:” (the first character is the return type).
168func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
169	t := strings.CString(types)
170	ret, _, _ := purego.SyscallN(class_addMethod, uintptr(c), uintptr(name), uintptr(imp), uintptr(unsafe.Pointer(t)))
171	runtime.KeepAlive(t)
172	return byte(ret) != 0
173}
174
175// AddIvar adds a new instance variable to a class.
176// It may only be called after AllocateClassPair and before Register.
177// Adding an instance variable to an existing class is not supported.
178// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
179// It takes the instance of the type of the Ivar and a string representing the type.
180func (c Class) AddIvar(name string, ty interface{}, types string) bool {
181	n := strings.CString(name)
182	t := strings.CString(types)
183	typeOf := reflect.TypeOf(ty)
184	size := typeOf.Size()
185	alignment := uint8(math.Log2(float64(typeOf.Align())))
186	ret, _, _ := purego.SyscallN(class_addIvar, uintptr(c), uintptr(unsafe.Pointer(n)), size, uintptr(alignment), uintptr(unsafe.Pointer(t)))
187	runtime.KeepAlive(n)
188	runtime.KeepAlive(t)
189	return byte(ret) != 0
190}
191
192// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
193func (c Class) InstanceVariable(name string) Ivar {
194	n := strings.CString(name)
195	ret, _, _ := purego.SyscallN(class_getInstanceVariable, uintptr(c), uintptr(unsafe.Pointer(n)))
196	runtime.KeepAlive(n)
197	return Ivar(ret)
198}
199
200// Register registers a class that was allocated using AllocateClassPair.
201// It can now be used to make objects by sending it either alloc and init or new.
202func (c Class) Register() {
203	purego.SyscallN(objc_registerClassPair, uintptr(c))
204}
205
206// Ivar an opaque type that represents an instance variable.
207type Ivar uintptr
208
209// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
210//
211// For instance variables of type ID or other object types, call Ivar and SetIvar instead
212// of using this offset to access the instance variable data directly.
213func (i Ivar) Offset() uintptr {
214	ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
215	return ret
216}
217
218// IMP is a function pointer that can be called by Objective-C code.
219type IMP uintptr
220
221// NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. It returns an IMP function
222// pointer that can be called by Objective-C code. The function pointer is never deallocated.
223func NewIMP(fn interface{}) IMP {
224	ty := reflect.TypeOf(fn)
225	if ty.Kind() != reflect.Func {
226		panic("objc: not a function")
227	}
228	// IMP is stricter than a normal callback
229	// id (*IMP)(id, SEL, ...)
230	switch {
231	case ty.NumIn() < 2:
232		fallthrough
233	case ty.In(0).Kind() != reflect.Uintptr:
234		fallthrough
235	case ty.In(1).Kind() != reflect.Uintptr:
236		panic("objc: NewIMP must take a (id, SEL) as its first two arguments")
237	}
238	return IMP(purego.NewCallback(fn))
239}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close