|
|
@@ -54,7 +54,14 @@ ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063
|
|
|
|
|
|
advapi32 = ctypes.windll.advapi32 if hasattr(ctypes, 'windll') else None
|
|
|
|
|
|
-
|
|
|
+# ★★2026-09-19 远端实逮 (服务器 sc query guanlan 永远 START_PENDING, 组件却在跑):
|
|
|
+# ctypes **不声明 restype 时按 c_int 处理返回值**, 而 `RegisterServiceCtrlHandlerExW` 返回的是
|
|
|
+# **HANDLE(指针)** ⇒ 64 位 Python 上被截成 32 位, 拿到的是无效句柄; 于是后面每一次
|
|
|
+# `SetServiceStatus` 都失败(ERROR_INVALID_HANDLE), SCM 收不到任何状态 ⇒ 它停在"默认等待"
|
|
|
+# (CHECKPOINT=0 / WAIT_HINT=2000), 服务永远显示 START_PENDING 且 NOT_STOPPABLE ——
|
|
|
+# 表现是"服务在跑但 `net stop guanlan` 停不掉、状态栏看着像没起完"。
|
|
|
+# 修法: 显式声明 argtypes/restype (句柄按 c_void_p 传), 并把 SetServiceStatus 的失败**报出来**
|
|
|
+# (失败不静默: 写进 logs/service.log)。
|
|
|
class SERVICE_STATUS(ctypes.Structure):
|
|
|
_fields_ = [('dwServiceType', ctypes.c_ulong), ('dwCurrentState', ctypes.c_ulong),
|
|
|
('dwControlsAccepted', ctypes.c_ulong), ('dwWin32ExitCode', ctypes.c_ulong),
|
|
|
@@ -69,16 +76,32 @@ class SERVICE_TABLE_ENTRY(ctypes.Structure):
|
|
|
HANDLER = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p)
|
|
|
MAIN = ctypes.WINFUNCTYPE(None, ctypes.c_ulong, ctypes.POINTER(ctypes.c_wchar_p))
|
|
|
|
|
|
+if advapi32 is not None:
|
|
|
+ advapi32.RegisterServiceCtrlHandlerExW.restype = ctypes.c_void_p
|
|
|
+ advapi32.RegisterServiceCtrlHandlerExW.argtypes = [ctypes.c_wchar_p, HANDLER, ctypes.c_void_p]
|
|
|
+ advapi32.SetServiceStatus.restype = ctypes.c_int
|
|
|
+ advapi32.SetServiceStatus.argtypes = [ctypes.c_void_p, ctypes.POINTER(SERVICE_STATUS)]
|
|
|
+ advapi32.StartServiceCtrlDispatcherW.restype = ctypes.c_int
|
|
|
+ # ★argtypes 别写成 POINTER(SERVICE_TABLE_ENTRY) 却传 `byref(数组)` —— ctypes 会直接抛
|
|
|
+ # ArgumentError("expected LP_SERVICE_TABLE_ENTRY instance instead of pointer to
|
|
|
+ # SERVICE_TABLE_ENTRY_Array_2"), 于是服务**根本起不来**(sc start 回 1053)。
|
|
|
+ # 2026-09-19 我自己先踩了这个坑(远端实逮), 现在按"数组 cast 成指针"传。
|
|
|
+ advapi32.StartServiceCtrlDispatcherW.argtypes = [ctypes.POINTER(SERVICE_TABLE_ENTRY)]
|
|
|
+
|
|
|
_status_handle = None
|
|
|
_stop_evt = threading.Event()
|
|
|
|
|
|
|
|
|
def _report(state: int, hint: int = 0, cp: int = 0) -> None:
|
|
|
if not _status_handle:
|
|
|
+ SW.log(f'[{V.SERVICE_NAME}] 没有服务状态句柄 —— 状态 {state} 报不上去 (非 SCM 启动时属正常)', 'ERROR')
|
|
|
return
|
|
|
st = SERVICE_STATUS(SERVICE_WIN32_OWN_PROCESS, state,
|
|
|
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, NO_ERROR, 0, cp, hint)
|
|
|
- advapi32.SetServiceStatus(_status_handle, ctypes.byref(st))
|
|
|
+ if not advapi32.SetServiceStatus(_status_handle, ctypes.byref(st)):
|
|
|
+ err = ctypes.windll.kernel32.GetLastError()
|
|
|
+ SW.log(f'[{V.SERVICE_NAME}] SetServiceStatus(state={state}) 失败, WinError {err} '
|
|
|
+ f'—— SCM 会看不到本服务状态 (服务状态栏显示异常/停不掉时先查这条)', 'ERROR')
|
|
|
|
|
|
|
|
|
def _handler(control, event_type, event_data, context):
|
|
|
@@ -98,10 +121,13 @@ def _service_main(argc, argv) -> None:
|
|
|
if not _status_handle:
|
|
|
return
|
|
|
_report(SERVICE_START_PENDING, 30000, 1)
|
|
|
+ # ★先把 RUNNING 报给 SCM, 再干重活 (2026-09-19 远端实逮: 原来先 SW.log 再起线程最后才报 RUNNING,
|
|
|
+ # 而 `sc start` 那次在日志落盘上卡住 ⇒ SCM 等不到 RUNNING, 30 s 后直接 1053 "服务未及时响应")
|
|
|
+ # ⇒ 控制协议就绪 = 服务就是 RUNNING; 组件起没起由守护线程负责, 看 /healthz 与 logs/service.log。
|
|
|
+ _report(SERVICE_RUNNING)
|
|
|
SW.log(f'服务已由 SCM 拉起({V.SERVICE_DISPLAY})')
|
|
|
t = threading.Thread(target=_body, name='guanlan-supervisor', daemon=True)
|
|
|
t.start()
|
|
|
- _report(SERVICE_RUNNING)
|
|
|
_stop_evt.wait()
|
|
|
SW.stop()
|
|
|
_report(SERVICE_STOPPED)
|
|
|
@@ -150,7 +176,7 @@ def main() -> int:
|
|
|
table[0].lpServiceProc = ctypes.cast(_service_main, ctypes.c_void_p)
|
|
|
table[1].lpServiceName = None
|
|
|
table[1].lpServiceProc = None
|
|
|
- ok = advapi32.StartServiceCtrlDispatcherW(ctypes.byref(table))
|
|
|
+ ok = advapi32.StartServiceCtrlDispatcherW(ctypes.cast(table, ctypes.POINTER(SERVICE_TABLE_ENTRY)))
|
|
|
if not ok:
|
|
|
err = ctypes.windll.kernel32.GetLastError()
|
|
|
if err in (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, 0):
|