最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Go編寫一個Windows剪貼板監(jiān)控器

 更新時(shí)間:2025年11月18日 09:40:37   作者:Mgx  
這篇文章主要為大家詳細(xì)介紹了如何基于Go編寫一個Windows剪貼板監(jiān)控器,可以在后臺默默監(jiān)聽你的復(fù)制行為,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

你有沒有想過,當(dāng)你按下 Ctrl+C 的那一瞬間,你的剪貼板內(nèi)容其實(shí)可以被"悄悄"記錄下來?別慌,這不是什么黑客教程,而是一次用 Go 語言 和 Windows API 玩轉(zhuǎn)系統(tǒng)底層的趣味實(shí)驗(yàn)!

今天,我們就來手把手實(shí)現(xiàn)一個 剪貼板監(jiān)視器 —— 它會在后臺默默監(jiān)聽你的復(fù)制行為,并把內(nèi)容打印出來(當(dāng)然,也可以做更多事情,比如自動保存、翻譯、甚至發(fā)到郵箱里 )。

為什么要做這個

  • 學(xué)習(xí)目的:理解 Windows 消息機(jī)制、剪貼板架構(gòu)、Unicode/ANSI 編碼處理。
  • 實(shí)用場景:自動化工具、開發(fā)調(diào)試、剪貼板歷史記錄等。
  • 裝 X 需求:在同事面前演示:"看,我一復(fù)制,程序就知道我抄了啥!"

 技術(shù)原理:Windows 剪貼板監(jiān)聽機(jī)制

Windows 提供了一套經(jīng)典的"剪貼板查看器鏈"(Clipboard Viewer Chain)機(jī)制:

  • 你可以把自己的窗口注冊為"剪貼板查看器"。
  • 一旦剪貼板內(nèi)容發(fā)生變化,系統(tǒng)會向鏈中的每個窗口發(fā)送 WM_DRAWCLIPBOARD 消息。
  • 你收到消息后,就可以打開剪貼板、讀取內(nèi)容、然后優(yōu)雅地關(guān)掉它。

聽起來是不是有點(diǎn)像"訂閱-發(fā)布"模式?沒錯!只不過這是 1980 年代的 Windows 版 Pub/Sub。

注意事項(xiàng)

微軟官方已不推薦使用 SetClipboardViewer(推薦用 AddClipboardFormatListener),但為了兼容性和教學(xué)目的,我們?nèi)杂媒?jīng)典方式。

核心挑戰(zhàn):剪貼板數(shù)據(jù)格式

剪貼板可不是只存文本!它支持多種格式:

格式常量含義
CF_TEXTANSI 文本(通常是 GBK 編碼)
CF_UNICODETEXTUTF-16 文本(現(xiàn)代應(yīng)用主流)
CF_HDROP文件拖放(比如從資源管理器復(fù)制文件)
其他自定義格式比如 Word 的富文本、圖片等

所以我們的程序必須:

  • 枚舉所有格式
  • 優(yōu)先讀取 CF_UNICODETEXT
  • 降級處理 CF_TEXT(并轉(zhuǎn)碼為 UTF-8)
  • 識別文件拖放
  • 對未知格式友好提示

代碼實(shí)現(xiàn):Go + Windows API

我們使用 golang.org/x/sys/windows 調(diào)用 Windows API,并配合 unsafe 和 reflect 直接操作內(nèi)存(別怕,有安全兜底)。

下面就是完整源碼(已加詳細(xì)注釋):

package main

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"syscall"
	"unicode/utf16"
	"unsafe"

	"golang.org/x/sys/windows"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

// ===== Win32 API Constants =====
const (
	WM_CREATE        = 0x0001
	WM_DESTROY       = 0x0002
	WM_DRAWCLIPBOARD = 0x0308
	WM_CHANGECBCHAIN = 0x030D
	CF_TEXT          = 1
	CF_BITMAP        = 2
	CF_UNICODETEXT   = 13
	CF_HDROP         = 15
)

// ===== Win32 Structures =====
type WNDCLASSEX struct {
	Size       uint32
	Style      uint32
	WndProc    uintptr
	ClsExtra   int32
	WndExtra   int32
	Instance   syscall.Handle
	Icon       syscall.Handle
	Cursor     syscall.Handle
	Background uintptr
	MenuName   *uint16
	ClassName  *uint16
	IconSm     syscall.Handle
}

type MSG struct {
	HWnd    uintptr
	Message uint32
	WParam  uintptr
	LParam  uintptr
	DwTime  uint32
	PtX     int32
	PtY     int32
}

// ===== Win32 API Procs =====
var (
	user32   = windows.NewLazySystemDLL("user32.dll")
	kernel32 = windows.NewLazySystemDLL("kernel32.dll")
	shell32  = windows.NewLazySystemDLL("shell32.dll")

	procRegisterClassEx      = user32.NewProc("RegisterClassExW")
	procCreateWindowEx       = user32.NewProc("CreateWindowExW")
	procDefWindowProc        = user32.NewProc("DefWindowProcW")
	procOpenClipboard        = user32.NewProc("OpenClipboard")
	procCloseClipboard       = user32.NewProc("CloseClipboard")
	procEnumClipboardFormats = user32.NewProc("EnumClipboardFormats")
	procGetClipboardData     = user32.NewProc("GetClipboardData")
	procSetClipboardViewer   = user32.NewProc("SetClipboardViewer")
	procGetMessage           = user32.NewProc("GetMessageW")
	procTranslateMessage     = user32.NewProc("TranslateMessage")
	procDispatchMessage      = user32.NewProc("DispatchMessageW")
	procShowWindow           = user32.NewProc("ShowWindow")
	procGlobalLock           = kernel32.NewProc("GlobalLock")
	procGlobalUnlock         = kernel32.NewProc("GlobalUnlock")
	procDragQueryFile        = shell32.NewProc("DragQueryFileW")
)

// ===== 窗口過程函數(shù) =====
func windowProc(hwnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) uintptr {
	switch msg {
	case WM_CREATE:
		_, _, _ = procSetClipboardViewer.Call(uintptr(hwnd))

	case WM_DRAWCLIPBOARD:
		if ret, _, _ := procOpenClipboard.Call(uintptr(hwnd)); ret == 0 {
			fmt.Println("? 無法打開剪切板")
			break
		}
		var format uint32 = 0
		var finalText string

		for {
			r1, _, _ := procEnumClipboardFormats.Call(uintptr(format))
			format = uint32(r1)
			if format == 0 {
				break
			}

			hMem, _, _ := procGetClipboardData.Call(uintptr(format))
			if hMem == 0 {
				continue
			}

			switch format {
			case CF_UNICODETEXT:
				text, err := getUnicodeText(hMem)
				if err == nil {
					finalText = text
					goto done
				}
			case CF_TEXT:
				text, err := getAnsiText(hMem)
				if err == nil && finalText == "" {
					finalText = text
				}
			case CF_HDROP:
				finalText = ""
				files, err := getHDropFiles(hMem)
				if err != nil {
					fmt.Println("? 文件列表: 讀取文件失敗:", err)
				} else {
					fmt.Printf("?? 拖放的文件 (%d):\n", len(files))
					for _, f := range files {
						fmt.Println(" -", f)
					}
				}
			default:
				finalText = ""
				fmt.Printf("?? %s\n", getClipboardFormatName(format))
			}
		}
	done:
		if finalText != "" {
			fmt.Printf("?? 文本內(nèi)容:\n%s\n", finalText)
		}
		procCloseClipboard.Call()

	case WM_CHANGECBCHAIN:
		fmt.Println("?? 剪切板查看器鏈已更改")

	default:
		ret, _, _ := procDefWindowProc.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
		return ret
	}
	return 0
}

// ===== 輔助函數(shù) =====
func getAnsiText(hMem uintptr) (string, error) {
	lpLock, _, _ := procGlobalLock.Call(hMem)
	if lpLock == 0 {
		return "", fmt.Errorf("無法鎖定內(nèi)存")
	}
	defer procGlobalUnlock.Call(hMem)

	var size int
	for {
		b := *(*byte)(unsafe.Pointer(lpLock + uintptr(size)))
		if b == 0 {
			break
		}
		size++
	}

	data := make([]byte, size)
	for i := 0; i < size; i++ {
		data[i] = *(*byte)(unsafe.Pointer(lpLock + uintptr(i)))
	}

	decoder := simplifiedchinese.GBK.NewDecoder()
	reader := transform.NewReader(bytes.NewReader(data), decoder)
	utf8Bytes, err := io.ReadAll(reader)
	if err != nil {
		return "", err
	}
	return string(utf8Bytes), nil
}

func getUnicodeText(hMem uintptr) (string, error) {
	lpLock, _, _ := procGlobalLock.Call(hMem)
	if lpLock == 0 {
		return "", fmt.Errorf("無法鎖定內(nèi)存")
	}
	defer procGlobalUnlock.Call(hMem)

	var length int
	for {
		w := *(*uint16)(unsafe.Pointer(lpLock + uintptr(length*2)))
		if w == 0 {
			break
		}
		length++
	}

	sliceHeader := reflect.SliceHeader{
		Data: lpLock,
		Len:  length,
		Cap:  length,
	}
	utf16Data := *(*[]uint16)(unsafe.Pointer(&sliceHeader))

	runes := utf16.Decode(utf16Data)
	return string(runes), nil
}

func getHDropFiles(hMem uintptr) ([]string, error) {
	lpLock, _, _ := procGlobalLock.Call(hMem)
	if lpLock == 0 {
		return nil, fmt.Errorf("無法鎖定HDROP內(nèi)存")
	}
	defer procGlobalUnlock.Call(hMem)

	count, _, _ := procDragQueryFile.Call(lpLock, 0xFFFFFFFF, 0, 0)
	if count == 0 {
		return nil, fmt.Errorf("HDROP中未找到文件")
	}

	files := make([]string, count)
	for i := uintptr(0); i < count; i++ {
		var buf [260]uint16
		_, _, _ = procDragQueryFile.Call(lpLock, i, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
		files[i] = syscall.UTF16ToString(buf[:])
	}
	return files, nil
}

func getClipboardFormatName(format uint32) string {
	if format <= 0xC000 {
		switch format {
		case CF_TEXT:
			return "CF_TEXT (ANSI文本)"
		case CF_UNICODETEXT:
			return "CF_UNICODETEXT"
		case CF_BITMAP:
			return "CF_BITMAP (位圖)"
		case CF_HDROP:
			return "CF_HDROP (文件列表)"
		default:
			return "標(biāo)準(zhǔn)格式"
		}
	}
	buf := make([]uint16, 256)
	r1, _, _ := user32.NewProc("GetClipboardFormatNameW").Call(
		uintptr(format),
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(len(buf)),
	)
	if r1 == 0 {
		return fmt.Sprintf("未知自定義格式 (%d)", format)
	}
	return "自定義格式: " + syscall.UTF16ToString(buf)
}

func init() {
	if err := shell32.Load(); err != nil {
		panic("無法加載shell32.dll: " + err.Error())
	}
}

func main() {
	className := syscall.StringToUTF16Ptr("ClipboardMonitorClass")

	var wc WNDCLASSEX
	wc.Size = uint32(unsafe.Sizeof(wc))
	wc.WndProc = syscall.NewCallback(windowProc)
	wc.Instance = 0
	wc.ClassName = className
	wc.Style = 0x0002 // CS_HREDRAW

	ret, _, err := procRegisterClassEx.Call(uintptr(unsafe.Pointer(&wc)))
	if ret == 0 {
		panic("注冊窗口類失敗: " + err.Error())
	}

	hwnd, _, err := procCreateWindowEx.Call(
		0,
		uintptr(unsafe.Pointer(className)),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("剪切板監(jiān)視器"))),
		0,
		0, 0, 100, 100,
		0, 0, 0, 0,
	)
	if hwnd == 0 {
		panic("創(chuàng)建窗口失敗: " + err.Error())
	}

	procShowWindow.Call(hwnd, 0) // 隱藏窗口

	fmt.Println("?? 剪貼板監(jiān)視器已啟動。現(xiàn)在可以復(fù)制內(nèi)容進(jìn)行測試!")

	var msg MSG
	for {
		ret, _, _ := procGetMessage.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0)
		if ret <= 0 {
			break
		}
		procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
		procDispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
	}
}

如何運(yùn)行

安裝依賴

go mod init clipboard-monitor
go get golang.org/x/sys/windows
go get golang.org/x/text/encoding/simplifiedchinese

運(yùn)行程序

保存為 main.go,然后:

go run main.go

測試效果

復(fù)制一段文字、一張圖片、或幾個文件試試! 你會看到類似這樣的輸出:

?? 剪貼板監(jiān)視器已啟動。現(xiàn)在可以復(fù)制內(nèi)容進(jìn)行測試!
?? 文本內(nèi)容:
Hello, 剪貼板!
?? 拖放的文件 (2):
 - C:\Users\Alice\Pictures\cat.jpg
 - C:\Users\Alice\Documents\report.pdf
?? CF_BITMAP (位圖)

安全提醒

  • 本程序僅用于學(xué)習(xí)和本地調(diào)試。
  • 實(shí)際產(chǎn)品中應(yīng)避免濫用剪貼板監(jiān)控(涉及隱私!)。
  • 在企業(yè)環(huán)境中,此類行為可能違反安全策略。

結(jié)語

通過幾十行 Go 代碼,我們撬動了 Windows 底層的消息系統(tǒng),實(shí)現(xiàn)了對剪貼板的"溫柔窺探"。這不僅是一次技術(shù)實(shí)踐,更是一次穿越回 Win32 時(shí)代的浪漫冒險(xiǎn)。

下次當(dāng)你復(fù)制密碼時(shí),記得看看控制臺——說不定你的程序正在偷笑 。

以上就是基于Go編寫一個Windows剪貼板監(jiān)控器的詳細(xì)內(nèi)容,更多關(guān)于Go Windows剪貼板監(jiān)控器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang微服務(wù)框架Kratos實(shí)現(xiàn)Kafka消息隊(duì)列的方法

    Golang微服務(wù)框架Kratos實(shí)現(xiàn)Kafka消息隊(duì)列的方法

    消息隊(duì)列是大型分布式系統(tǒng)不可缺少的中間件,也是高并發(fā)系統(tǒng)的基石中間件,所以掌握好消息隊(duì)列MQ就變得極其重要,在本文當(dāng)中,您將了解到:什么是消息隊(duì)列?什么是Kafka?怎樣在微服務(wù)框架Kratos當(dāng)中應(yīng)用Kafka進(jìn)行業(yè)務(wù)開發(fā),需要的朋友可以參考下
    2023-09-09
  • go?mode?tidy出現(xiàn)報(bào)錯go:?warning:?“all“?matched?no?packages的解決方法

    go?mode?tidy出現(xiàn)報(bào)錯go:?warning:?“all“?matched?no?package

    使用go的時(shí)候我們一般都會使用go?mode管理,下面這篇文章主要給大家介紹了關(guān)于go?mode?tidy出現(xiàn)報(bào)錯go:?warning:?“all“?matched?no?packages的解決方法,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • Golang語言學(xué)習(xí)拿捏Go反射示例教程

    Golang語言學(xué)習(xí)拿捏Go反射示例教程

    這篇文章主要為大家介紹了Golang語言中Go反射示例的教程,教你拿捏Go反射,再也不用被Go反射折磨,有需要的朋友可以共同學(xué)習(xí)參考下
    2021-11-11
  • Golang 空結(jié)構(gòu)體特性與用法詳解

    Golang 空結(jié)構(gòu)體特性與用法詳解

    本文主要介紹了Golang 空結(jié)構(gòu)體特性與用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-03-03
  • Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)詳解

    Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)詳解

    最近項(xiàng)目中需要用到鑒權(quán)機(jī)制,golang中jwt可以用,這篇文章主要給大家介紹了關(guān)于Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • axios?gin的GET和POST請求實(shí)現(xiàn)示例

    axios?gin的GET和POST請求實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了axios?gin的GET和POST請求實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Go實(shí)現(xiàn)基于RSA加密算法的接口鑒權(quán)

    Go實(shí)現(xiàn)基于RSA加密算法的接口鑒權(quán)

    這篇文章主要介紹了Go實(shí)現(xiàn)基于RSA加密算法的接口鑒權(quán),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-06-06
  • 深入理解Golang的單元測試和性能測試

    深入理解Golang的單元測試和性能測試

    Go語言提供了強(qiáng)大的測試工具,下面這篇文章主要給大家介紹了關(guān)于Golang單元測試和性能測試的相關(guān)資料,文中通過示例代碼給大家詳細(xì)介紹了單元測試和性能測試的相關(guān)內(nèi)容,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-08-08
  • golang http請求封裝代碼

    golang http請求封裝代碼

    這篇文章主要介紹了golang http請求封裝代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • go-cache的基本使用場景示例解析

    go-cache的基本使用場景示例解析

    這篇文章主要為大家介紹了go-cache的基本使用場景示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04

最新評論

平顶山市| 宝应县| 五原县| 通渭县| 友谊县| 邓州市| 承德市| 威海市| 怀宁县| 宣化县| 黎川县| 南召县| 阳春市| 南皮县| 平度市| 新干县| 利辛县| 开原市| 青冈县| 中阳县| 股票| 额尔古纳市| 乌海市| 丰县| 乌兰察布市| 利辛县| 乌拉特前旗| 武陟县| 台中市| 格尔木市| 汾阳市| 浦县| 吉隆县| 乡宁县| 礼泉县| 文水县| 民县| 莲花县| 花莲市| 岑溪市| 石柱|