Skip to content

Commit 17acc46

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
docs: add gomobile bind interoperability examples
Two examples showing how to pass NDK handles between Java/Kotlin and Go via gomobile bind using int64 transport (gomobile does not support unsafe.Pointer or uintptr): - sensor-bridge: wrapping sensor.Manager for Java access - window-interop: passing ANativeWindow and EGL handles across boundary
1 parent d2756a9 commit 17acc46

2 files changed

Lines changed: 367 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Exposing NDK sensors to Java/Kotlin via gomobile bind.
2+
//
3+
// gomobile bind generates Java/Kotlin bindings for exported Go functions.
4+
// It supports signed integers, floats, strings, booleans, []byte, errors,
5+
// interfaces, and structs — but NOT unsafe.Pointer or uintptr.
6+
//
7+
// To pass NDK handles across the Go/Java boundary, use int64 (Java long)
8+
// as the transport type and convert via uintptr inside Go:
9+
//
10+
// func NewSensorBridge(managerHandle int64) *SensorBridge {
11+
// mgr := sensor.NewManagerFromUintPtr(uintptr(managerHandle))
12+
// return &SensorBridge{mgr: mgr}
13+
// }
14+
//
15+
// The Java side obtains native handles as long values (e.g. from JNI)
16+
// and passes them to Go:
17+
//
18+
// // Java
19+
// long sensorManagerPtr = nativeGetSensorManager();
20+
// SensorBridge bridge = Sensorbridge.newSensorBridge(sensorManagerPtr);
21+
// String name = bridge.defaultAccelerometerName();
22+
//
23+
// # Go library package (bindable via gomobile bind)
24+
//
25+
// The pattern for a gomobile-bindable Go package:
26+
//
27+
// // Package sensorbridge exposes NDK sensor access to Java/Kotlin.
28+
// // Build with: gomobile bind -target=android ./sensorbridge
29+
// package sensorbbridge
30+
//
31+
// import "github.com/AndroidGoLab/ndk/sensor"
32+
//
33+
// // SensorBridge wraps a sensor.Manager for cross-language use.
34+
// // Exported struct fields and methods with supported types are
35+
// // automatically exposed to Java/Kotlin by gomobile bind.
36+
// type SensorBridge struct {
37+
// mgr *sensor.Manager // unexported: invisible to Java
38+
// }
39+
//
40+
// // NewSensorBridge creates a bridge from a raw ASensorManager handle.
41+
// // The handle is passed as int64 because gomobile bind does not
42+
// // support unsafe.Pointer or uintptr.
43+
// func NewSensorBridge(managerHandle int64) *SensorBridge {
44+
// mgr := sensor.NewManagerFromUintPtr(uintptr(managerHandle))
45+
// return &SensorBridge{mgr: mgr}
46+
// }
47+
//
48+
// // DefaultAccelerometerName returns the name of the default accelerometer.
49+
// func (b *SensorBridge) DefaultAccelerometerName() string {
50+
// s := b.mgr.DefaultSensor(sensor.Accelerometer)
51+
// return s.Name()
52+
// }
53+
//
54+
// // Handle returns the underlying ASensorManager handle for passing
55+
// // back to Java/JNI code.
56+
// func (b *SensorBridge) Handle() int64 {
57+
// return int64(b.mgr.UintPtr())
58+
// }
59+
//
60+
// # Java usage
61+
//
62+
// import sensorbbridge.SensorBridge;
63+
//
64+
// // Obtain the native ASensorManager* from JNI or the NDK.
65+
// long ptr = nativeGetSensorManager();
66+
//
67+
// // Create the Go bridge, passing the handle as a long.
68+
// SensorBridge bridge = Sensorbbridge.newSensorBridge(ptr);
69+
//
70+
// // Call Go methods — gomobile generates Java proxy methods.
71+
// String name = bridge.defaultAccelerometerName();
72+
// Log.d("Sensor", "Accelerometer: " + name);
73+
//
74+
// // Retrieve the handle back (e.g. to pass to another native call).
75+
// long handle = bridge.handle();
76+
//
77+
// # Key rules
78+
//
79+
// - Use int64 (Java long) to transport native pointers across the boundary.
80+
// - Convert int64 <-> uintptr inside Go; never expose uintptr to gomobile.
81+
// - Keep NDK handle fields unexported — gomobile cannot serialize them.
82+
// - Exported methods must use only gomobile-supported types.
83+
// - Close/release NDK resources from Go, not from Java.
84+
//
85+
// This program must run on an Android device.
86+
package main
87+
88+
import (
89+
"fmt"
90+
91+
"github.com/AndroidGoLab/ndk/sensor"
92+
)
93+
94+
func main() {
95+
fmt.Println("=== gomobile bind: sensor bridge ===")
96+
fmt.Println()
97+
98+
// Demonstrate the int64 <-> uintptr <-> NDK handle conversion chain.
99+
//
100+
// In a real app, the int64 comes from Java (JNI native handle).
101+
// Here we show the Go-side conversion pattern.
102+
103+
fmt.Println("Pattern: Java long -> Go int64 -> uintptr -> NDK handle")
104+
fmt.Println()
105+
106+
// Step 1: Receive a handle from Java as int64.
107+
fmt.Println("Step 1: Java passes native handle as long (int64)")
108+
fmt.Println(" Java: long ptr = nativeGetSensorManager();")
109+
fmt.Println(" Java: SensorBridge bridge = Sensorbbridge.newSensorBridge(ptr);")
110+
fmt.Println()
111+
112+
// Step 2: Convert int64 -> uintptr -> NDK type in Go.
113+
fmt.Println("Step 2: Go converts int64 -> uintptr -> sensor.Manager")
114+
fmt.Println(" Go: mgr := sensor.NewManagerFromUintPtr(uintptr(handle))")
115+
fmt.Println()
116+
117+
// Step 3: Use the NDK type normally.
118+
fmt.Println("Step 3: Use NDK types normally in Go")
119+
fmt.Println(" Go: s := mgr.DefaultSensor(sensor.Accelerometer)")
120+
fmt.Println(" Go: name := s.Name()")
121+
fmt.Println()
122+
123+
// Step 4: Return results to Java using supported types.
124+
fmt.Println("Step 4: Return results as gomobile-supported types (string, int64, error)")
125+
fmt.Println(" Go: return s.Name() // string -> Java String")
126+
fmt.Println(" Go: return int64(mgr.UintPtr()) // handle -> Java long")
127+
fmt.Println()
128+
129+
// Show the available sensor type constants that a bridge might expose.
130+
types := []struct {
131+
name string
132+
value sensor.Type
133+
}{
134+
{"Accelerometer", sensor.Accelerometer},
135+
{"Gyroscope", sensor.Gyroscope},
136+
{"MagneticField", sensor.MagneticField},
137+
{"Light", sensor.Light},
138+
{"Proximity", sensor.Proximity},
139+
}
140+
141+
fmt.Println("Sensor type constants (use int32 for gomobile):")
142+
for _, t := range types {
143+
fmt.Printf(" %-25s = %d\n", t.name, int32(t.value))
144+
}
145+
fmt.Println()
146+
147+
// gomobile bind type mapping summary.
148+
fmt.Println("gomobile bind type mapping:")
149+
fmt.Println(" Go int64 <-> Java long (native handle transport)")
150+
fmt.Println(" Go string <-> Java String")
151+
fmt.Println(" Go int32 <-> Java int (sensor type constants)")
152+
fmt.Println(" Go float32 <-> Java float (sensor values)")
153+
fmt.Println(" Go float64 <-> Java double")
154+
fmt.Println(" Go bool <-> Java boolean")
155+
fmt.Println(" Go []byte <-> Java byte[]")
156+
fmt.Println(" Go error <-> Java Exception")
157+
fmt.Println(" Go struct <-> Java class (exported methods only)")
158+
fmt.Println(" Go interface <-> Java interface")
159+
fmt.Println()
160+
161+
fmt.Println("sensor-bridge example complete")
162+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Passing ANativeWindow handles between Java and Go via gomobile bind.
2+
//
3+
// When integrating NDK rendering with a Java/Kotlin Android app,
4+
// the Java side holds the Surface and must pass the native window
5+
// handle to Go. Because gomobile bind does not support unsafe.Pointer
6+
// or uintptr, the handle is transported as int64 (Java long).
7+
//
8+
// # Obtaining ANativeWindow from Java
9+
//
10+
// Java/Kotlin obtains an ANativeWindow* via JNI from an android.view.Surface:
11+
//
12+
// // Java (JNI helper)
13+
// public static native long surfaceToNativeWindow(Surface surface);
14+
//
15+
// // C (JNI implementation)
16+
// JNIEXPORT jlong JNICALL Java_com_example_NativeLib_surfaceToNativeWindow(
17+
// JNIEnv *env, jclass cls, jobject surface) {
18+
// return (jlong)ANativeWindow_fromSurface(env, surface);
19+
// }
20+
//
21+
// # Go library package (bindable)
22+
//
23+
// package renderer
24+
//
25+
// import (
26+
// "github.com/AndroidGoLab/ndk/egl"
27+
// "github.com/AndroidGoLab/ndk/window"
28+
// )
29+
//
30+
// // Renderer manages an EGL context attached to a native window.
31+
// type Renderer struct {
32+
// win *window.Window
33+
// display egl.EGLDisplay
34+
// surface egl.EGLSurface
35+
// ctx egl.EGLContext
36+
// }
37+
//
38+
// // NewRenderer creates a renderer from a native window handle.
39+
// // The windowHandle is an ANativeWindow* cast to int64 by the Java caller.
40+
// func NewRenderer(windowHandle int64) (*Renderer, error) {
41+
// win := window.NewWindowFromUintPtr(uintptr(windowHandle))
42+
// // ... set up EGL display, surface, context using win ...
43+
// return &Renderer{win: win}, nil
44+
// }
45+
//
46+
// // WindowWidth returns the current window width in pixels.
47+
// func (r *Renderer) WindowWidth() int32 {
48+
// // window.Width() returns error; unwrap for gomobile.
49+
// // In production code, handle the error appropriately.
50+
// return 0 // placeholder: win.GetWidth() returns error in current API
51+
// }
52+
//
53+
// // RenderFrame renders a single frame. Called from Java's render loop.
54+
// func (r *Renderer) RenderFrame() error {
55+
// // ... OpenGL ES rendering calls ...
56+
// return nil
57+
// }
58+
//
59+
// // Destroy releases all resources. Must be called when the surface
60+
// // is destroyed to avoid leaking native handles.
61+
// func (r *Renderer) Destroy() {
62+
// // ... tear down EGL ...
63+
// // Do NOT close the window — Java owns the Surface lifecycle.
64+
// }
65+
//
66+
// // WindowHandle returns the raw ANativeWindow* as int64 so Java
67+
// // can pass it to another native library.
68+
// func (r *Renderer) WindowHandle() int64 {
69+
// return int64(r.win.UintPtr())
70+
// }
71+
//
72+
// # Java usage
73+
//
74+
// import renderer.Renderer;
75+
//
76+
// public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
77+
// private Renderer renderer;
78+
//
79+
// @Override
80+
// public void surfaceCreated(SurfaceHolder holder) {
81+
// long windowPtr = surfaceToNativeWindow(holder.getSurface());
82+
// try {
83+
// renderer = Renderer.newRenderer(windowPtr);
84+
// } catch (Exception e) {
85+
// Log.e("Renderer", "init failed", e);
86+
// }
87+
// }
88+
//
89+
// @Override
90+
// public void surfaceDestroyed(SurfaceHolder holder) {
91+
// if (renderer != null) {
92+
// renderer.destroy();
93+
// renderer = null;
94+
// }
95+
// }
96+
// }
97+
//
98+
// # EGL handle round-tripping
99+
//
100+
// EGL types (EGLDisplay, EGLContext, EGLSurface) are unsafe.Pointer aliases.
101+
// Use the egl package's conversion functions:
102+
//
103+
// // Go -> Java: extract EGL handle as int64
104+
// func (r *Renderer) EGLDisplayHandle() int64 {
105+
// return int64(egl.EGLDisplayToUintPtr(r.display))
106+
// }
107+
//
108+
// // Java -> Go: reconstruct EGL handle from int64
109+
// func SetEGLDisplay(handle int64) {
110+
// display := egl.EGLDisplayFromUintPtr(uintptr(handle))
111+
// // use display...
112+
// }
113+
//
114+
// # Ownership rules
115+
//
116+
// - The Java side owns Surface lifecycle (create/destroy).
117+
// - Go must NOT call window.Close() if Java owns the Surface.
118+
// - Go MUST release its own resources (EGL context, buffers) before
119+
// surfaceDestroyed returns.
120+
// - Native handles passed as int64 are raw pointers — no reference
121+
// counting. The caller must ensure the handle remains valid.
122+
//
123+
// This program must run on an Android device.
124+
package main
125+
126+
import (
127+
"fmt"
128+
129+
"github.com/AndroidGoLab/ndk/egl"
130+
"github.com/AndroidGoLab/ndk/window"
131+
)
132+
133+
func main() {
134+
fmt.Println("=== gomobile bind: window interop ===")
135+
fmt.Println()
136+
137+
// Demonstrate the handle conversion chain for window handles.
138+
139+
fmt.Println("Handle flow: Java Surface -> JNI ANativeWindow* -> Go int64 -> window.Window")
140+
fmt.Println()
141+
142+
// Step 1: Java obtains ANativeWindow from Surface via JNI.
143+
fmt.Println("Step 1: Java obtains ANativeWindow* via JNI")
144+
fmt.Println(" Java: long ptr = surfaceToNativeWindow(holder.getSurface());")
145+
fmt.Println(" C: return (jlong)ANativeWindow_fromSurface(env, surface);")
146+
fmt.Println()
147+
148+
// Step 2: Java passes handle to Go as int64.
149+
fmt.Println("Step 2: Java passes handle to Go via gomobile bind")
150+
fmt.Println(" Java: Renderer renderer = Renderer.newRenderer(ptr);")
151+
fmt.Println(" Go: func NewRenderer(windowHandle int64) (*Renderer, error)")
152+
fmt.Println()
153+
154+
// Step 3: Go wraps int64 as window.Window.
155+
fmt.Println("Step 3: Go converts int64 -> window.Window")
156+
fmt.Println(" Go: win := window.NewWindowFromUintPtr(uintptr(windowHandle))")
157+
fmt.Println()
158+
159+
// Step 4: Go returns handle to Java as int64.
160+
fmt.Println("Step 4: Go returns handle to Java as int64")
161+
fmt.Println(" Go: return int64(win.UintPtr())")
162+
fmt.Println(" Java: long handle = renderer.windowHandle();")
163+
fmt.Println()
164+
165+
// Show the window.Window and egl interop API.
166+
fmt.Println("window.Window interop API:")
167+
fmt.Printf(" %-35s %s\n", "window.NewWindowFromUintPtr(uintptr)", "wrap uintptr")
168+
fmt.Printf(" %-35s %s\n", "window.NewWindowFromPointer(ptr)", "wrap unsafe.Pointer")
169+
fmt.Printf(" %-35s %s\n", "win.UintPtr()", "extract as uintptr")
170+
fmt.Printf(" %-35s %s\n", "win.Pointer()", "extract as unsafe.Pointer")
171+
fmt.Println()
172+
173+
// Demonstrate that window.Window and egl.ANativeWindow are separate
174+
// types wrapping the same C type, and handles can be shared.
175+
fmt.Println("Cross-package handle sharing:")
176+
fmt.Println(" // window.Window and egl.ANativeWindow both wrap ANativeWindow*.")
177+
fmt.Println(" // Transfer via uintptr:")
178+
fmt.Println(" eglWin := egl.NewANativeWindowFromUintPtr(win.UintPtr())")
179+
fmt.Println()
180+
181+
// Show EGL type conversion API.
182+
fmt.Println("EGL type interop API:")
183+
eglTypes := []string{
184+
"EGLDisplay", "EGLContext", "EGLSurface",
185+
"EGLConfig", "EGLImage", "EGLSync", "EGLClientBuffer",
186+
}
187+
for _, t := range eglTypes {
188+
fmt.Printf(" egl.%sToUintPtr(h) / egl.%sFromUintPtr(p)\n", t, t)
189+
}
190+
fmt.Println()
191+
192+
// Show gomobile bind pattern for EGL.
193+
fmt.Println("gomobile bind pattern for EGL handles:")
194+
fmt.Println(" // Go: export as int64 for Java")
195+
fmt.Println(" func (r *Renderer) EGLDisplayHandle() int64 {")
196+
fmt.Println(" return int64(egl.EGLDisplayToUintPtr(r.display))")
197+
fmt.Println(" }")
198+
fmt.Println()
199+
200+
// Suppress unused import errors by referencing the packages.
201+
_ = window.NewWindowFromUintPtr
202+
_ = egl.EGLDisplayToUintPtr
203+
204+
fmt.Println("window-interop example complete")
205+
}

0 commit comments

Comments
 (0)