mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
273 lines
6.7 KiB
Go
273 lines
6.7 KiB
Go
//go:build windows
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
|
)
|
|
|
|
// dllPlugin wraps a Windows DLL compiled with -buildmode=c-shared.
|
|
//
|
|
// Required exports:
|
|
//
|
|
// NewPlugin(name *C.char, configJSON *C.char) unsafe.Pointer → plugin handle
|
|
// StartPlugin(handle unsafe.Pointer) C.int
|
|
// StopPlugin(handle unsafe.Pointer) C.int
|
|
// DestroyPlugin(handle unsafe.Pointer)
|
|
//
|
|
// Optional exports (tool registration):
|
|
//
|
|
// GetToolDefsJSON(handle unsafe.Pointer) *C.char → JSON array of tool defs
|
|
// InvokeToolJSON(handle unsafe.Pointer, toolName *C.char, argsJSON *C.char) *C.char
|
|
// FreeCString(s *C.char) → free C string from DLL
|
|
// GetStagesJSON(handle unsafe.Pointer) *C.char → JSON array of stage names
|
|
// InvokeStage(handle unsafe.Pointer, stage *C.char, contextJSON *C.char) C.int
|
|
type dllPlugin struct {
|
|
name string
|
|
dll syscall.Handle
|
|
handle uintptr
|
|
sdk *sdk.PluginSDK
|
|
|
|
// cached proc addresses
|
|
newPlugin uintptr
|
|
startPlugin uintptr
|
|
stopPlugin uintptr
|
|
destroyPlugin uintptr
|
|
getTools uintptr
|
|
invokeTool uintptr
|
|
freeCStr uintptr
|
|
getStages uintptr
|
|
invokeStage uintptr
|
|
}
|
|
|
|
func findProc(dll syscall.Handle, name string) uintptr {
|
|
addr, err := syscall.GetProcAddress(dll, name)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func newDLLPlugin(dllPath, name string, config map[string]interface{}) (*dllPlugin, error) {
|
|
dll, err := syscall.LoadLibrary(dllPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LoadLibrary %s: %w", dllPath, err)
|
|
}
|
|
|
|
np := findProc(dll, "NewPlugin")
|
|
if np == 0 {
|
|
_ = syscall.FreeLibrary(dll)
|
|
return nil, fmt.Errorf("dll %s must export NewPlugin", name)
|
|
}
|
|
|
|
return &dllPlugin{
|
|
name: name,
|
|
dll: dll,
|
|
// required
|
|
newPlugin: np,
|
|
startPlugin: findProc(dll, "StartPlugin"),
|
|
stopPlugin: findProc(dll, "StopPlugin"),
|
|
destroyPlugin: findProc(dll, "DestroyPlugin"),
|
|
// optional tool/stage API
|
|
getTools: findProc(dll, "GetToolDefsJSON"),
|
|
invokeTool: findProc(dll, "InvokeToolJSON"),
|
|
freeCStr: findProc(dll, "FreeCString"),
|
|
getStages: findProc(dll, "GetStagesJSON"),
|
|
invokeStage: findProc(dll, "InvokeStage"),
|
|
}, nil
|
|
}
|
|
|
|
func (p *dllPlugin) Name() string { return p.name }
|
|
|
|
func (p *dllPlugin) Start(s *sdk.PluginSDK) error {
|
|
p.sdk = s
|
|
|
|
cfgJSON, _ := json.Marshal(map[string]interface{}{
|
|
"name": p.name,
|
|
"config": s.Settings().Dump(),
|
|
})
|
|
cName := append([]byte(p.name), 0)
|
|
cConfig := append(cfgJSON, 0)
|
|
|
|
ret, _, _ := syscall.SyscallN(
|
|
p.newPlugin,
|
|
uintptr(unsafe.Pointer(&cName[0])),
|
|
uintptr(unsafe.Pointer(&cConfig[0])),
|
|
)
|
|
if ret == 0 {
|
|
_ = syscall.FreeLibrary(p.dll)
|
|
return fmt.Errorf("dll NewPlugin %s returned nil", p.name)
|
|
}
|
|
p.handle = ret
|
|
|
|
if p.startPlugin != 0 {
|
|
syscall.SyscallN(p.startPlugin, p.handle)
|
|
}
|
|
|
|
// discover and register tools from DLL
|
|
if p.getTools != 0 {
|
|
if err := p.registerTools(s); err != nil {
|
|
return fmt.Errorf("dll %s register tools: %w", p.name, err)
|
|
}
|
|
}
|
|
if p.getStages != 0 {
|
|
p.registerStages(s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *dllPlugin) Stop() error {
|
|
if p.stopPlugin != 0 {
|
|
syscall.SyscallN(p.stopPlugin, p.handle)
|
|
}
|
|
if p.destroyPlugin != 0 {
|
|
syscall.SyscallN(p.destroyPlugin, p.handle)
|
|
}
|
|
_ = syscall.FreeLibrary(p.dll)
|
|
return nil
|
|
}
|
|
|
|
// --- tool registration via C ABI ---
|
|
|
|
type dllToolDef struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
|
}
|
|
|
|
func (p *dllPlugin) registerTools(s *sdk.PluginSDK) error {
|
|
ret, _, _ := syscall.SyscallN(p.getTools, p.handle)
|
|
if ret == 0 {
|
|
return nil // no tools
|
|
}
|
|
defsJSON := cStringPtrToString(ret)
|
|
if p.freeCStr != 0 {
|
|
syscall.SyscallN(p.freeCStr, ret)
|
|
}
|
|
|
|
var defs []dllToolDef
|
|
if err := json.Unmarshal([]byte(defsJSON), &defs); err != nil {
|
|
return fmt.Errorf("parse tool defs: %w", err)
|
|
}
|
|
for _, d := range defs {
|
|
if d.Name == "" {
|
|
continue
|
|
}
|
|
toolName := d.Name
|
|
handler := p.makeToolHandler(toolName)
|
|
s.RegisterTool(toolName, sdk.ToolDef{
|
|
Name: toolName,
|
|
Description: d.Description,
|
|
Parameters: d.Parameters,
|
|
Plugin: p.name,
|
|
}, handler)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *dllPlugin) makeToolHandler(toolName string) sdk.ToolHandler {
|
|
return func(args map[string]interface{}) (interface{}, error) {
|
|
if p.invokeTool == 0 {
|
|
return nil, fmt.Errorf("dll %s does not export InvokeToolJSON", p.name)
|
|
}
|
|
argsJSON, _ := json.Marshal(args)
|
|
cToolName := append([]byte(toolName), 0)
|
|
cArgs := append(argsJSON, 0)
|
|
|
|
ret, _, _ := syscall.SyscallN(
|
|
p.invokeTool,
|
|
p.handle,
|
|
uintptr(unsafe.Pointer(&cToolName[0])),
|
|
uintptr(unsafe.Pointer(&cArgs[0])),
|
|
)
|
|
if ret == 0 {
|
|
return nil, fmt.Errorf("dll InvokeToolJSON %s returned nil", toolName)
|
|
}
|
|
resultJSON := cStringPtrToString(ret)
|
|
if p.freeCStr != 0 {
|
|
syscall.SyscallN(p.freeCStr, ret)
|
|
}
|
|
var result map[string]interface{}
|
|
if err := json.Unmarshal([]byte(resultJSON), &result); err != nil {
|
|
return nil, fmt.Errorf("dll tool %s result parse: %w", toolName, err)
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
func (p *dllPlugin) registerStages(s *sdk.PluginSDK) {
|
|
ret, _, _ := syscall.SyscallN(p.getStages, p.handle)
|
|
if ret == 0 {
|
|
return
|
|
}
|
|
stagesJSON := cStringPtrToString(ret)
|
|
if p.freeCStr != 0 {
|
|
syscall.SyscallN(p.freeCStr, ret)
|
|
}
|
|
type stageEntry struct {
|
|
Stage string `json:"stage"`
|
|
}
|
|
var entries []stageEntry
|
|
if err := json.Unmarshal([]byte(stagesJSON), &entries); err != nil {
|
|
return
|
|
}
|
|
for _, e := range entries {
|
|
if e.Stage == "" {
|
|
continue
|
|
}
|
|
stageName := sdk.Stage(e.Stage)
|
|
stage := stageName
|
|
s.RegisterStage(stage, func(sc *sdk.StageContext) error {
|
|
if p.invokeStage == 0 {
|
|
return nil
|
|
}
|
|
ctxJSON, _ := json.Marshal(map[string]interface{}{
|
|
"raw_message": sc.RawMessage,
|
|
"user_id": sc.UserID,
|
|
"phase": string(sc.Phase),
|
|
})
|
|
cStage := append([]byte(stage), 0)
|
|
cCtx := append(ctxJSON, 0)
|
|
syscall.SyscallN(
|
|
p.invokeStage,
|
|
p.handle,
|
|
uintptr(unsafe.Pointer(&cStage[0])),
|
|
uintptr(unsafe.Pointer(&cCtx[0])),
|
|
)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func cStringPtrToString(ptr uintptr) string {
|
|
if ptr == 0 {
|
|
return ""
|
|
}
|
|
var buf []byte
|
|
for i := uintptr(0); ; i++ {
|
|
b := *(*byte)(unsafe.Pointer(ptr + i))
|
|
if b == 0 {
|
|
break
|
|
}
|
|
buf = append(buf, b)
|
|
}
|
|
return string(buf)
|
|
}
|
|
|
|
// tryLoadDLL 尝试从插件目录加载 plugin.dll。
|
|
// 返回 nil,nil 表示目录中没有 plugin.dll。
|
|
func tryLoadDLL(dir, name string, config map[string]interface{}) (sdk.Plugin, error) {
|
|
dllPath := filepath.Join(dir, dllEntry)
|
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return newDLLPlugin(dllPath, name, config)
|
|
}
|