Skip to content

Commit b9ba4dc

Browse files
mapleafgoclaude
andcommitted
fix(ffi): Singcast 懒初始化 svc 字段,兼容 gomobile 零值构造器
gomobile 生成的 Java Singcast() 构造器创建零值 Go 结构体, 导致 svc 字段为 nil,Init/SetOnEvent 等方法调用时崩溃。 添加 service() 方法做懒初始化,所有方法通过它访问 svc。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent f5df006 commit b9ba4dc

1 file changed

Lines changed: 45 additions & 37 deletions

File tree

ffi/api.go

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,37 +38,45 @@ func New() *Singcast {
3838
return &Singcast{svc: core.NewService()}
3939
}
4040

41+
// service returns the core.Service, initializing lazily for gomobile zero-value compatibility.
42+
func (s *Singcast) service() *core.Service {
43+
if s.svc == nil {
44+
s.svc = core.NewService()
45+
}
46+
return s.svc
47+
}
48+
4149
// Init initializes the core runtime.
4250
// optionsJSON: {"home_dir":"/path","log_max_lines":500,"debug":false,"fix_android_stack":false}
4351
func (s *Singcast) Init(optionsJSON string) error {
44-
return s.svc.Init(optionsJSON)
52+
return s.service().Init(optionsJSON)
4553
}
4654

4755
// StartWithContent starts or restarts the service with raw YAML or JSON config.
4856
func (s *Singcast) StartWithContent(content, ruleSetProxy string) error {
49-
return s.svc.StartWithContent(content, ruleSetProxy)
57+
return s.service().StartWithContent(content, ruleSetProxy)
5058
}
5159

5260
// Stop stops the running service. Safe to call multiple times.
5361
func (s *Singcast) Stop() error {
54-
return s.svc.Stop()
62+
return s.service().Stop()
5563
}
5664

5765
// Destroy releases all resources. The instance cannot be reused.
5866
func (s *Singcast) Destroy() {
59-
s.svc.Destroy()
67+
s.service().Destroy()
6068
}
6169

6270
// --- Platform IO ---
6371

6472
// SetTunFd stores a TUN file descriptor for mobile platforms.
6573
func (s *Singcast) SetTunFd(fd int32) {
66-
s.svc.PlatformIO().SetTunFd(fd)
74+
s.service().PlatformIO().SetTunFd(fd)
6775
}
6876

6977
// SetSocketProtector registers a socket protector for VPN bypass.
7078
func (s *Singcast) SetSocketProtector(p SocketProtector) {
71-
s.svc.PlatformIO().SetSocketProtector(func(fd int32) bool {
79+
s.service().PlatformIO().SetSocketProtector(func(fd int32) bool {
7280
if p != nil {
7381
return p.Protect(fd)
7482
}
@@ -78,64 +86,64 @@ func (s *Singcast) SetSocketProtector(p SocketProtector) {
7886

7987
// SetInterfacesJSON provides network interface data from the mobile platform.
8088
func (s *Singcast) SetInterfacesJSON(json string) {
81-
s.svc.PlatformIO().SetInterfacesJSON(json)
89+
s.service().PlatformIO().SetInterfacesJSON(json)
8290
}
8391

8492
// UpdateDefaultInterface reports the current default network interface.
8593
func (s *Singcast) UpdateDefaultInterface(name string, index int64, expensive bool) {
86-
s.svc.PlatformIO().UpdateDefaultInterface(name, index, expensive)
94+
s.service().PlatformIO().UpdateDefaultInterface(name, index, expensive)
8795
}
8896

8997
// SetIncludeAllNetworks sets whether the VPN configuration uses includeAllNetworks (iOS).
9098
func (s *Singcast) SetIncludeAllNetworks(v bool) {
91-
s.svc.PlatformIO().SetIncludeAllNetworks(v)
99+
s.service().PlatformIO().SetIncludeAllNetworks(v)
92100
}
93101

94102
// SetWIFIState reports the current WiFi SSID and BSSID from the mobile platform.
95103
func (s *Singcast) SetWIFIState(ssid, bssid string) {
96-
s.svc.PlatformIO().SetWIFIState(ssid, bssid)
104+
s.service().PlatformIO().SetWIFIState(ssid, bssid)
97105
}
98106

99107
// QueryTunOptions returns TUN configuration as JSON for mobile consumers.
100108
// Call after the service has started (OpenTun has been invoked by sing-box).
101109
func (s *Singcast) QueryTunOptions() string {
102-
return s.svc.PlatformIO().QueryTunOptions()
110+
return s.service().PlatformIO().QueryTunOptions()
103111
}
104112

105113
// --- Logging ---
106114

107115
// SetLogLevel sets the minimum log level (2=Error, 3=Warn, 4=Info, 5=Debug, 6=Trace).
108-
func (s *Singcast) SetLogLevel(level int32) { s.svc.SetLogLevel(level) }
116+
func (s *Singcast) SetLogLevel(level int32) { s.service().SetLogLevel(level) }
109117

110118
// SetError pushes an error message to connected clients.
111-
func (s *Singcast) SetError(message string) { s.svc.SetError(message) }
119+
func (s *Singcast) SetError(message string) { s.service().SetError(message) }
112120

113121
// --- Pause / Wake / Network ---
114122

115123
// Pause suspends network activity. On iOS, auto-wakes after 1 minute.
116-
func (s *Singcast) Pause() { s.svc.Pause() }
124+
func (s *Singcast) Pause() { s.service().Pause() }
117125

118126
// Wake resumes network activity after Pause.
119-
func (s *Singcast) Wake() { s.svc.Wake() }
127+
func (s *Singcast) Wake() { s.service().Wake() }
120128

121129
// ResetNetwork resets all connections, DNS cache, and forces outbounds to reconnect.
122-
func (s *Singcast) ResetNetwork() { s.svc.ResetNetwork() }
130+
func (s *Singcast) ResetNetwork() { s.service().ResetNetwork() }
123131

124132
// --- Config ---
125133

126134
// ReloadConfig reloads the service with new configuration.
127135
func (s *Singcast) ReloadConfig(content, ruleSetProxy string) error {
128-
return s.svc.ReloadConfig(content, ruleSetProxy)
136+
return s.service().ReloadConfig(content, ruleSetProxy)
129137
}
130138

131139
// ReloadTUN restarts the TUN interface without changing configuration.
132-
func (s *Singcast) ReloadTUN() error { return s.svc.ReloadTUN() }
140+
func (s *Singcast) ReloadTUN() error { return s.service().ReloadTUN() }
133141

134142
// SetOverridePackages updates the include/exclude package lists for VPN split tunneling.
135143
// The service restarts with the current config and new package overrides.
136144
// Pass empty string to clear all overrides.
137145
func (s *Singcast) SetOverridePackages(overrideJSON string) error {
138-
return s.svc.SetOverridePackages(overrideJSON)
146+
return s.service().SetOverridePackages(overrideJSON)
139147
}
140148

141149
// CheckConfig validates a config string (Clash YAML or sing-box JSON).
@@ -145,25 +153,25 @@ func (s *Singcast) CheckConfig(content string) error { return core.CheckConfig(c
145153

146154
// SelectProxy selects a proxy in the given group.
147155
func (s *Singcast) SelectProxy(group, tag string) error {
148-
return s.svc.SelectOutbound(group, tag)
156+
return s.service().SelectOutbound(group, tag)
149157
}
150158

151159
// TestDelay runs a URL test for the given outbound tag.
152-
func (s *Singcast) TestDelay(name string) error { return s.svc.URLTest(name) }
160+
func (s *Singcast) TestDelay(name string) error { return s.service().URLTest(name) }
153161

154162
// SetMode sets the clash routing mode (rule, global, or direct).
155-
func (s *Singcast) SetMode(mode string) error { return s.svc.SetClashMode(mode) }
163+
func (s *Singcast) SetMode(mode string) error { return s.service().SetClashMode(mode) }
156164

157165
// SetGroupExpand sets the UI expand state for a proxy group.
158166
func (s *Singcast) SetGroupExpand(group string, expand bool) error {
159-
return s.svc.SetGroupExpand(group, expand)
167+
return s.service().SetGroupExpand(group, expand)
160168
}
161169

162170
// --- Queries ---
163171

164172
// QueryProxies returns cached proxy groups as JSON.
165173
func (s *Singcast) QueryProxies() string {
166-
h := s.svc.Handler()
174+
h := s.service().Handler()
167175
if h == nil {
168176
return "[]"
169177
}
@@ -172,7 +180,7 @@ func (s *Singcast) QueryProxies() string {
172180

173181
// QueryTraffic returns cached traffic stats as JSON.
174182
func (s *Singcast) QueryTraffic() string {
175-
h := s.svc.Handler()
183+
h := s.service().Handler()
176184
if h == nil {
177185
return "{}"
178186
}
@@ -182,49 +190,49 @@ func (s *Singcast) QueryTraffic() string {
182190
// QueryLogs returns combined sing-box and core internal logs as JSON.
183191
// If clear is true, the log buffer is cleared after querying.
184192
func (s *Singcast) QueryLogs(clear bool) string {
185-
result := s.svc.QueryLogs()
193+
result := s.service().QueryLogs()
186194
if clear {
187-
_ = s.svc.ClearLogs()
195+
_ = s.service().ClearLogs()
188196
}
189197
return result
190198
}
191199

192200
// QueryConnections returns cached connections as JSON.
193201
func (s *Singcast) QueryConnections() string {
194-
h := s.svc.Handler()
202+
h := s.service().Handler()
195203
if h == nil {
196204
return "[]"
197205
}
198206
return h.GetCachedConnectionsJSON()
199207
}
200208

201209
// CloseConnection closes a connection by ID.
202-
func (s *Singcast) CloseConnection(id string) error { return s.svc.CloseConnection(id) }
210+
func (s *Singcast) CloseConnection(id string) error { return s.service().CloseConnection(id) }
203211

204212
// CloseAllConnections closes all active connections.
205-
func (s *Singcast) CloseAllConnections() error { return s.svc.CloseConnections() }
213+
func (s *Singcast) CloseAllConnections() error { return s.service().CloseConnections() }
206214

207215
// --- Platform Queries ---
208216

209217
// NeedWIFIState reports whether the current config requires WIFI state monitoring.
210-
func (s *Singcast) NeedWIFIState() bool { return s.svc.NeedWIFIState() }
218+
func (s *Singcast) NeedWIFIState() bool { return s.service().NeedWIFIState() }
211219

212220
// NeedFindProcess reports whether the current config requires process finding.
213-
func (s *Singcast) NeedFindProcess() bool { return s.svc.NeedFindProcess() }
221+
func (s *Singcast) NeedFindProcess() bool { return s.service().NeedFindProcess() }
214222

215223
// UpdateWIFIState triggers the platform to report current WIFI state.
216-
func (s *Singcast) UpdateWIFIState() { s.svc.UpdateWIFIState() }
224+
func (s *Singcast) UpdateWIFIState() { s.service().UpdateWIFIState() }
217225

218226
// WriteMessage writes a log message to the core at the given level.
219227
func (s *Singcast) WriteMessage(level int32, message string) {
220-
s.svc.WriteMessage(level, message)
228+
s.service().WriteMessage(level, message)
221229
}
222230

223231
// FlushSystemDNS attempts to flush the system DNS cache.
224-
func (s *Singcast) FlushSystemDNS() { s.svc.FlushSystemDNS() }
232+
func (s *Singcast) FlushSystemDNS() { s.service().FlushSystemDNS() }
225233

226234
// QueryMemoryStats returns current Go runtime memory statistics as JSON.
227-
func (s *Singcast) QueryMemoryStats() string { return s.svc.QueryMemoryStats() }
235+
func (s *Singcast) QueryMemoryStats() string { return s.service().QueryMemoryStats() }
228236

229237
// --- Memory ---
230238

@@ -241,7 +249,7 @@ func (s *Singcast) Version() string { return core.VersionJSON() }
241249

242250
// SetOnEvent registers an event handler. Call after Init.
243251
func (s *Singcast) SetOnEvent(handler EventHandler) {
244-
s.svc.SetOnEvent(func(eventType int32, jsonPayload string) {
252+
s.service().SetOnEvent(func(eventType int32, jsonPayload string) {
245253
if handler != nil {
246254
handler.OnEvent(eventType, jsonPayload)
247255
}

0 commit comments

Comments
 (0)