//go:build windows package main import ( "fmt" "io" "log" "os" "runtime" "sync" "syscall" "time" "unsafe" ) const version = "v1.0.1" // Win32 常量 const ( WS_OVERLAPPEDWINDOW = 0x00CF0000 WS_CHILD = 0x40000000 WS_VISIBLE = 0x10000000 WS_VSCROLL = 0x00200000 WS_HSCROLL = 0x00100000 WS_EX_CLIENTEDGE = 0x00000200 ES_MULTILINE = 0x0004 ES_READONLY = 0x0800 ES_AUTOVSCROLL = 0x0040 ES_AUTOHSCROLL = 0x0080 WM_DESTROY = 0x0002 WM_SIZE = 0x0005 WM_SETFONT = 0x0030 WM_CTLCOLOREDIT = 0x0133 EM_SETSEL = 0x00B1 EM_REPLACESEL = 0x00C2 SW_SHOW = 5 IDC_ARROW = 32512 CW_USEDEFAULT = ^0x7FFFFFFF FIXED_PITCH = 1 FF_MODERN = 48 DEFAULT_CHARSET = 1 FW_NORMAL = 400 MB_ICONERROR = 0x00000010 ) // 暗色主题 (BGR格式) const ( colorBg = 0x001E1E1E colorFg = 0x00D4D4D4 ) type tWNDCLASSEX struct { CbSize uint32 Style uint32 LpfnWndProc uintptr CbClsExtra int32 CbWndExtra int32 HInstance uintptr HIcon uintptr HCursor uintptr HbrBackground uintptr LpszMenuName *uint16 LpszClassName *uint16 HIconSm uintptr } type tRECT struct { Left int32 Top int32 Right int32 Bottom int32 } type tMSG struct { HWnd uintptr Message uint32 _ uint32 WParam uintptr LParam uintptr Time uint32 PtX int32 PtY int32 } var ( modUser32 = syscall.NewLazyDLL("user32.dll") modKernel32 = syscall.NewLazyDLL("kernel32.dll") modGdi32 = syscall.NewLazyDLL("gdi32.dll") pGetModuleHandleW = modKernel32.NewProc("GetModuleHandleW") pLoadCursorW = modUser32.NewProc("LoadCursorW") pRegisterClassExW = modUser32.NewProc("RegisterClassExW") pCreateWindowExW = modUser32.NewProc("CreateWindowExW") pShowWindow = modUser32.NewProc("ShowWindow") pUpdateWindow = modUser32.NewProc("UpdateWindow") pGetMessageW = modUser32.NewProc("GetMessageW") pTranslateMessage = modUser32.NewProc("TranslateMessage") pDispatchMessageW = modUser32.NewProc("DispatchMessageW") pDefWindowProcW = modUser32.NewProc("DefWindowProcW") pPostQuitMessage = modUser32.NewProc("PostQuitMessage") pSendMessageW = modUser32.NewProc("SendMessageW") pGetClientRect = modUser32.NewProc("GetClientRect") pMoveWindow = modUser32.NewProc("MoveWindow") pMessageBoxW = modUser32.NewProc("MessageBoxW") pCreateFontW = modGdi32.NewProc("CreateFontW") pDeleteObject = modGdi32.NewProc("DeleteObject") pCreateSolidBrush = modGdi32.NewProc("CreateSolidBrush") pSetTextColor = modGdi32.NewProc("SetTextColor") pSetBkColor = modGdi32.NewProc("SetBkColor") ) var ( hWndMain uintptr hWndEdit uintptr hFont uintptr hbrBg uintptr guiReady bool guiMu sync.Mutex pendingLogs []string ) // guiLogWriter 实现 io.Writer,将日志写入编辑控件 type guiLogWriter struct{} func (w *guiLogWriter) Write(p []byte) (n int, err error) { text := string(p) guiMu.Lock() if !guiReady || hWndEdit == 0 { pendingLogs = append(pendingLogs, text) guiMu.Unlock() return len(p), nil } guiMu.Unlock() appendText(text) return len(p), nil } // logFileWriter 实现 io.Writer,将日志写入 log/日期.log 文件 type logFileWriter struct { mu sync.Mutex curDate string curFile *os.File } func (w *logFileWriter) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock() date := time.Now().Format("2006-01-02") if date != w.curDate || w.curFile == nil { if w.curFile != nil { w.curFile.Close() w.curFile = nil } if err := os.MkdirAll("./log", 0755); err != nil { return 0, err } filename := fmt.Sprintf("./log/%s.log", date) f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return 0, err } w.curDate = date w.curFile = f } return w.curFile.Write(p) } // initFileLog 初始化文件日志,返回 io.Writer func initFileLog() io.Writer { w := &logFileWriter{} // 写入一条启动标记 now := time.Now().Format("2006-01-02 15:04:05") w.Write([]byte(fmt.Sprintf("====== %s 日志文件初始化完成 ======\n", now))) return w } func appendText(text string) { if hWndEdit == 0 { return } // 光标移到末尾 pSendMessageW.Call(hWndEdit, EM_SETSEL, ^uintptr(0), ^uintptr(0)) // 插入文本 ptr, _ := syscall.UTF16PtrFromString(text) pSendMessageW.Call(hWndEdit, EM_REPLACESEL, 1, uintptr(unsafe.Pointer(ptr))) } func flushPendingLogs() { guiMu.Lock() logs := pendingLogs pendingLogs = nil guiReady = true guiMu.Unlock() for _, text := range logs { appendText(text) } } func wndProc(hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case WM_SIZE: if hWndEdit != 0 { var rect tRECT pGetClientRect.Call(hWnd, uintptr(unsafe.Pointer(&rect))) pMoveWindow.Call(hWndEdit, 0, 0, uintptr(rect.Right), uintptr(rect.Bottom), 1) } return 0 case WM_CTLCOLOREDIT: pSetTextColor.Call(wParam, colorFg) pSetBkColor.Call(wParam, colorBg) return hbrBg case WM_DESTROY: if hFont != 0 { pDeleteObject.Call(hFont) } if hbrBg != 0 { pDeleteObject.Call(hbrBg) } pPostQuitMessage.Call(0) return 0 } ret, _, _ := pDefWindowProcW.Call(hWnd, uintptr(msg), wParam, lParam) return ret } // runGUI 创建并运行GUI窗口,阻塞直到窗口关闭 func runGUI() { runtime.LockOSThread() defer runtime.UnlockOSThread() hInstance, _, _ := pGetModuleHandleW.Call(0) className, _ := syscall.UTF16PtrFromString("KfzLogWnd") windowTitle, _ := syscall.UTF16PtrFromString("孔网商品定价 " + version + " - 日志") editClass, _ := syscall.UTF16PtrFromString("EDIT") hCursor, _, _ := pLoadCursorW.Call(0, IDC_ARROW) // 创建暗色背景画刷 hbrBg, _, _ = pCreateSolidBrush.Call(colorBg) // 注册窗口类 wc := tWNDCLASSEX{ CbSize: uint32(unsafe.Sizeof(tWNDCLASSEX{})), LpfnWndProc: syscall.NewCallback(wndProc), HInstance: hInstance, HCursor: hCursor, HbrBackground: hbrBg, LpszClassName: className, } pRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc))) // 创建主窗口 hWndMain, _, _ = pCreateWindowExW.Call( 0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowTitle)), WS_OVERLAPPEDWINDOW, 100, 100, 900, 600, 0, 0, hInstance, 0, ) // 创建编辑控件(多行、只读、滚动条) hWndEdit, _, _ = pCreateWindowExW.Call( WS_EX_CLIENTEDGE, uintptr(unsafe.Pointer(editClass)), 0, WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_READONLY|ES_AUTOVSCROLL|ES_AUTOHSCROLL, 0, 0, 0, 0, hWndMain, 0, hInstance, 0, ) // 设置等宽字体 fontName, _ := syscall.UTF16PtrFromString("Consolas") hFont, _, _ = pCreateFontW.Call( 16, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, FIXED_PITCH|FF_MODERN, uintptr(unsafe.Pointer(fontName)), ) pSendMessageW.Call(hWndEdit, WM_SETFONT, hFont, 1) // 刷新缓冲日志 flushPendingLogs() // 显示窗口 pShowWindow.Call(hWndMain, SW_SHOW) pUpdateWindow.Call(hWndMain) // 消息循环 var msg tMSG for { ret, _, _ := pGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0) if ret == 0 { break } pTranslateMessage.Call(uintptr(unsafe.Pointer(&msg))) pDispatchMessageW.Call(uintptr(unsafe.Pointer(&msg))) } } // fatalExit 弹出错误对话框并退出 func fatalExit(format string, v ...interface{}) { msg := fmt.Sprintf(format, v...) log.Print(msg) title, _ := syscall.UTF16PtrFromString("错误") text, _ := syscall.UTF16PtrFromString(msg) pMessageBoxW.Call(0, uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(title)), MB_ICONERROR) os.Exit(1) }