mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat: restructure plugin system, add Lua plugin support, update docs
This commit is contained in:
272
internal/plugin/dynamic_dll_windows.go
Normal file
272
internal/plugin/dynamic_dll_windows.go
Normal file
@ -0,0 +1,272 @@
|
||||
//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)
|
||||
}
|
||||
Reference in New Issue
Block a user