能否将PNG图片打包进资源DLL并配合ImageSearch函数使用?
首先直接回应你的核心疑问:ImageSearch原生并不支持直接读取DLL资源中的PNG文件——它只接受本地文件路径作为搜索目标。不过我们可以通过“提取资源到临时存储”的方式间接实现,同时完全避免在脚本目录留下文件痕迹。下面分方案详细说明:
一、DLL资源打包方案(隐藏图片到DLL)
这个方案的思路是把PNG作为自定义资源打包进DLL,脚本运行时临时提取到系统临时目录(用完即删),再传递给ImageSearch。
步骤1:打包PNG到DLL
用资源编辑器(比如Visual Studio自带的资源编辑器,或者免费的Resource Hacker)创建一个空DLL,添加你的PNG文件作为资源:
- 资源类型设为自定义类型(比如命名为
PNG) - 给资源分配一个唯一ID(比如
101)
步骤2:脚本中提取并调用ImageSearch
以下是AutoHotkey的实现示例(假设你的资源DLL名为resource.dll):
; 从DLL提取PNG资源到临时文件 ExtractPNGFromDLL(dllPath, resourceID, tempFilePath) { ; 加载DLL模块 hModule := DllCall("LoadLibrary", "Str", dllPath, "Ptr") if (!hModule) { MsgBox, 无法加载资源DLL return false } ; 查找指定资源 hResource := DllCall("FindResource", "Ptr", hModule, "Ptr", resourceID, "Str", "PNG", "Ptr") if (!hResource) { DllCall("FreeLibrary", "Ptr", hModule) MsgBox, 无法找到目标PNG资源 return false } ; 获取资源大小并锁定内存 resourceSize := DllCall("SizeofResource", "Ptr", hModule, "Ptr", hResource, "UInt") pResource := DllCall("LoadResource", "Ptr", hModule, "Ptr", hResource, "Ptr") pLocked := DllCall("LockResource", "Ptr", pResource, "Ptr") ; 写入系统临时文件 file := FileOpen(tempFilePath, "wb") file.RawWrite(pLocked, resourceSize) file.Close() ; 释放DLL DllCall("FreeLibrary", "Ptr", hModule) return true } ; 实际调用示例 tempPNG := A_Temp "\temp_search_img.png" ; 用系统临时目录,用户几乎不会注意 if (ExtractPNGFromDLL("resource.dll", 101, tempPNG)) { ; 执行ImageSearch ImageSearch, foundX, foundY, 0, 0, A_ScreenWidth, A_ScreenHeight, %tempPNG% if (ErrorLevel = 0) { MsgBox, 目标图片已找到!位置:%foundX%, %foundY% } ; 用完立即删除临时文件 FileDelete, %tempPNG% }
这个方案的优势是图片被隐藏在DLL中,普通用户很难察觉到其中包含图像资源;临时文件在系统临时目录,用完即删,不会留下痕迹。
二、无需额外DLL的替代方案
如果不想额外分发DLL,以下几种方案同样可以实现“不附带PNG文件”的需求:
1. 将PNG编码为Base64嵌入脚本
把PNG转换成Base64字符串,直接嵌入到脚本代码中,运行时解码到临时文件使用。这种方案不需要任何额外文件,脚本本身就是单文件。
示例代码:
; 替换成你的PNG对应的Base64字符串(可以用在线工具转换) pngBase64 := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" tempPNG := A_Temp "\temp_img.png" ; 解码Base64到临时文件 FileDelete, %tempPNG% ; 获取解码后的字节大小 DllCall("Crypt32.dll\CryptStringToBinary", "Str", pngBase64, "UInt", 0, "UInt", 1, "Ptr", 0, "UIntP", outSize, "Ptr", 0, "Ptr", 0) VarSetCapacity(buffer, outSize, 0) ; 执行解码 DllCall("Crypt32.dll\CryptStringToBinary", "Str", pngBase64, "UInt", 0, "UInt", 1, "Ptr", &buffer, "UIntP", outSize, "Ptr", 0, "Ptr", 0) ; 写入文件 File := FileOpen(tempPNG, "wb") File.RawWrite(&buffer, outSize) File.Close() ; 调用ImageSearch ImageSearch, foundX, foundY, 0, 0, A_ScreenWidth, A_ScreenHeight, %tempPNG% if (ErrorLevel = 0) { MsgBox, 找到图片! } ; 清理临时文件 FileDelete, %tempPNG%
2. 使用支持内存图像的搜索库(无临时文件)
原生ImageSearch依赖文件,但一些第三方AHK库(比如Gdip.ahk)支持直接加载内存中的图像数据进行像素搜索。流程是:
- 从DLL/Base64获取PNG的字节流
- 用Gdip将字节流加载为内存中的图像对象
- 调用Gdip的图像搜索函数进行匹配
这种方案完全不需要生成临时文件,隐蔽性最高,但需要引入第三方库并稍作学习。
3. 编译脚本时嵌入PNG到EXE
如果你使用AutoHotkey的编译工具,可以用FileInstall将PNG嵌入到编译后的EXE中,运行时释放到系统临时目录而不是脚本目录:
; 第三个参数设为1表示覆盖,将PNG安装到系统临时目录 FileInstall, search.png, %A_Temp%\search.png, 1 ; 执行搜索 ImageSearch, foundX, foundY, 0, 0, A_ScreenWidth, A_ScreenHeight, %A_Temp%\search.png ; 用完删除 FileDelete, %A_Temp%\search.png
编译后的EXE是单文件,用户看不到PNG,运行时临时释放的文件也会被立即删除。
总结
- 若追求“图片隐藏在DLL中”,选择DLL资源+临时文件方案;
- 若想要最轻量化的单文件方案,选择Base64嵌入或编译时嵌入PNG;
- 若完全不想生成任何临时文件,选择Gdip内存图像搜索方案。
内容的提问来源于stack exchange,提问作者msmilkshake

