Go多版本共存

场景 工作需要,一般使用的是相对较低的go版本,且较长时间内不会轻易变更;自己尝鲜或参与某个开源项目,又会使用另外的go版本。在不同项目间切换工作,通常需要切换到对应的go版本(虽然go目前是向下兼容的,高版本可以正确编译低版本,但开发人员即便熟知不同版本间的差异,也不能百分百保证不使用到高版本的特性)。 解决方案 网上也有很多方案,尝试后摸索出了比较符合自己风格和习惯的方案。 创建一个存放go不同版本的目录,用于将不同版本下载到该目录统一管理,并以版本号命名 1 mkdir $HOME/gosdk 编写下载脚本download_go.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/bin/bash if test "${1}" == ""; then echo '请传入正确的版本号,如:'${0}' 1.21.1' else # 统一管理目录 gosdk=$HOME/gosdk # 下载 gopkg=go${1}.linux-amd64.tar.gz if ! test -e ${gopkg}; then wget -c https://dl.google.com/go/${gopkg} fi # 删除可能冲突的文件 rm -rf ${gosdk}/go rm -rf ${gosdk}/go${1} # 解压 tar -C ${gosdk}/ -xzf ${gopkg} # 重命名 mv ${gosdk}/go ${gosdk}/go${1} # 删除安装包 rm ${gopkg} # 配置环境变量 go env -w GOPROXY=https://goproxy....

2025-05-26 10:27:45 · 1 分钟 · 慢步道人

Lazarus插件式窗口设计尝试

前言 先前就尝试过使用Lazarus写一个插件式的桌面应用框架,主程序只负责整体的插件管理以及为插件提供页签式的展示容器,具体的功能实现都由插件(动态库)来完成。 但是之前比较零散的几次尝试,多或少有些问题,比较有代表性的如: 无法以模式窗口显示插件内的子窗口 插件内的窗口在任务栏上显示的是和主程序分离的两个程序 动态库无法卸载 使用接口无法释放动态库里的对象 无法跨平台(主要针对windows) 近来又进行了一次系统性的尝试,以前的一些也针对性的研究了下,并找到了目前来看比较合适的解决方案,并将阶段性成果开源了,具体地址见文末。 旧问题解决 模式窗口 这个问题其实早在Lazarus官方的wiki(Form in Dll)上就有解决方案了。 在此基础上也尝试进行一些魔改,过程就不多说了,经验总结如下: DisableFormsCallBack和EnableFormsCallback两个回调必须为普通过程,改为类方法会导致模式窗口失效 TApplicationCallback可以和插件动态库内的管理类合并,做为插件入口的统一管理,甚至做为插件统一接口的实现 视觉上为同一程序 Form in Dll中已经涉及,即在CreateParams中将Params.WndParent赋值为主程序中对应容器的句柄。 卸载/释放 对于动态库的卸载,在Lazarus的dll卸载问题中已有提到,本次直接避免。 对于对象的释放,这前的尝试犯了一个很严重的错误,即未遵守谁创建谁释放这一原则。接口中增加专门的释放函数,用于释放通过接口创建的对象。但,对于接口创建的对象,主程序除了释放外,也不应该(事实上也不能)进行其它操作,否则会抛内存访问异常。 共享内存管理器 如果仅使用基本数据类型的话,这就是个伪命题,但若使用高阶数据类型的话还是会方便很多,毕竟字符串其实并不算是真正的基本类型。 文章在Lazarus中使用ShareMem解决了在Windows平台上共享内存管理器的问题。在QQ群的交流中,群友啊D提出使用GetMemoryManager和SetMemoryManager,目前来看是能解决该问题的。 跨平台一致性 Lazarus本身就是跨平台的,只注意避免使用平台专用的api即可,或者对不同平台的api进行封装。 对于插件式的动态库,exports导出的函数统一使用Name关键字强制命名。同时,对于32位CPU,导出函数统一使用cdecl而不是stdcall进行传参约束;对于64位CPU,导出函数统一使用默认的传参约束,即不使用任何关键字,由编译器管理。 新的问题 基于有限的测试,Linux的gtk2中显示模式窗口时主窗体还能进行最大化、最小化、移动等操作,但不能操作窗体内的元素,这与Windows上的行为表现不太一致 插件动态库创建的窗体,嵌入主程序容器后,并不能像嵌入自身容器内那样可以方便的自适应大小和位置 TODO 解决新发现的问题 主程序页签式容器的实现 主程序插件的管理 开源地址 Github地址:https://github.com/afrusrsc/x-framework Gitee地址:https://gitee.com/afrusrsc/x-framework

2025-05-05 19:06:17 · 1 分钟 · 慢步道人

ANSI转义序列

起因 近来,翻看gin源码时,无意间看到了green = "\033[97;42m"这种不明所以的代码,遂充满疑惑和好奇,于是就搜索探究了一番,这才知道这叫ANSI转义序列。 ANSI转义序列 简单说,就是一种标准化的终端控制序列,用于设置文本样式、颜色和背景等。先放个代码吧,以免不知所云。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import "fmt" func main() { for i := 0; i <= 10; i++ { fmt.Printf("这是\033[%[1]dm<文本属性>%2[1]d\033[0m\n", i) } s := []int{30, 31, 32, 33, 34, 35, 36, 37} for _, v := range s { fmt.Printf("这是\033[%[1]dm<16色标准前景色>%2[1]d\033[0m\t\033[%[2]dm<16色亮前景色>%2[2]d\033[0m\n", v, v+60) fmt.Printf("这是\033[%[1]dm<16色标准背景色>%2[1]d\033[0m\t\033[%[2]dm<16色亮背景色>%2[2]d\033[0m\n", v+10, v+70) } for i := 0; i < 256; i++ { fmt....

2025-04-17 20:28:24 · 2 分钟 · 慢步道人

Go命令行加进度条

背景 文件去重功能做好了,但当文件比较多或文件比较大的时候,耗时也会比较久,想加个进度条来直观显示处理进度。 简单尝试了下github.com/schollz/progressbar这个库,完全能满足目前的需求。 使用 安装 1 go get -u github.com/schollz/progressbar/v3 # 注意带版本v3 一般使用 1 2 3 4 5 6 bar := progressbar.Default(n, "描述") defer bar.Close() i := 0; i < n; i++ { bar.Add(1) // 工作代码 } n为总数,当n>0时,显示的是常规的进度条;当n=-1时,显示一个计数的进度。 还有其它比较细的控制及其它场景的使用,等有空了再细细研究下。

2025-03-21 21:38:23 · 1 分钟 · 慢步道人

用Go写一个文件去重工具

背景 想自己做这个功能,主要是因为Duplicate Cleaner这个商业软件只有几天的试用时间,而且文件去重这个逻辑也非常简单。 graph TD a[获取文件清单及大小] --> b[按大小分组] --> c[排除只有一个文件的组] --> d[计算文件Hash值] --> e[按Hash值分组] --> f[排除只有一个文件的组] --> g[选择需要删除的文件] --> h[删除] 问题 计算文件Hash值,使用了hash.Hash接口,自然也用到了goroutine来缩短耗时,但是在测试的时候发现功能不太好用,时好时坏,准确说是有时能获取到重复列表,有时不能。 一点点排查,并且把代码段发给DeepSeek,最终确定是因为hash.Hash不是并发安全的。 修复方法很简单,只要在goroutine内实例化即可。修改之后达到了预期。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func calcHashs(files []*FileInfo, hashName string) { g := sync.WaitGroup{} for _, file := range files { g.Add(1) go func(f *FileInfo) { defer g.Done() h := newHash(hashName) hashValue, err := calcHash(f....

2025-03-20 16:19:57 · 1 分钟 · 慢步道人

Gin功能列表

gin版本v1.10.0 包函数 方法 含义 说明 BasicAuth 创建一个HTTP基本认证(Basic HTTP Authorization)的中间件 底层调用BasicAuthForRealm BasicAuthForProxy 创建一个HTTP代理基本认证(Basic HTTP Proxy - Authorization)的中间件 BasicAuthForRealm 创建一个HTTP基本认证(Basic HTTP Authorization)的中间件 Bind 创建一个用于将请求中的数据绑定到指定的接口对象上的中间件 CreateTestContext 创建一个干净的Engine实例和一个与之关联的上下文对象用于测试 CreateTestContextOnly 在已有的Engine实例基础上创建一个独立的上下文用于测试 CustomRecovery 根据自定义的处理函数创建一个恢复中间件 底层调用CustomRecoveryWithWriter CustomRecoveryWithWriter 创建一个自定义的恢复中间件 Default 返回一个默认的Engine实例 默认含Logger和Recovery两个中间件 Dir 返回一个http.FileSystem接口的实现,该实现可被http.FileServer使用 DisableBindValidation 关闭默认的验证器 DisableConsoleColor 禁用控制台的颜色输出 EnableJsonDecoderDisallowUnknownFields 开启JSON解码器的DisallowUnknownFields功能 即遇到未知字段时就报错 EnableJsonDecoderUseNumber 开启JSON解码器的UseNumber功能 即将数字解码为json.Number类型,而不是float64,以在需要时精确地转换为整数或浮点数,从而避免精度丢失的问题 ErrorLogger 创建一个能够处理任意类型的错误的中间件 底层调用ErrorLoggerT ErrorLoggerT 创建一个能够处理指定类型的错误的中间件 ForceConsoleColor 强制在控制台输出带有颜色的内容 IsDebugging 判断当前框架是否处于调试模式 Logger 使用默认配置来创建一个日志中间件 底层调用LoggerWithConfig LoggerWithConfig 根据传入的配置来创建一个日志中间件 LoggerWithFormatter 根据指定的格式来创建一个日志中间件 底层调用LoggerWithConfig LoggerWithWriter 根据指定的输出目标创建一个日志中间件 底层调用LoggerWithConfig Mode 返回当前Gin框架的运行模式 有debug、release和test三种模式 New 返回一个全新的、没有任何中间件的Engine实例 Recovery 使用默认配置创建一个恢复中间件 底层调用RecoveryWithWriter RecoveryWithWriter 根据指定输出目标创建一个恢复中间件 底层调用CustomRecoveryWithWriter SetMode 设置gin框架的运行模式 WrapF 将标准的http....

2025-02-23 19:48:17 · 3 分钟 · 慢步道人

Lazarus在Linux上使用自定义动态库

前置知识 Windows上叫动态链接库,通常以*.dll形式命名;Linux上叫共享库,通常以lib*.so形式命名。(此处统一叫动态库) Windows上dll路径的一般搜索顺序为:当前目录->系统目录(如:C:\Windows\System32、C:\Windows\SysWOW64)->Windows目录(如C:\Windows)->PATH环境变量指定的目录。 Linux上so路径的一般搜索顺序为:编译时使用-rpath指定的路径->LD_LIBRARY_PATH环境变量指定的路径->系统默认库路径(如/lib、/usr/lib等)->/etc/ld.so.conf和/etc/ld.so.conf.d/目录中配置的路径。 Lazarus/Delphi调用动态库有两种形式:静态调用和动态调用。 静态调用:主程序启动时加载,若动态库不存在或不匹配,则主程序抛异常并中止;主程序退出时卸载。 动态调用:主程序在需要时可随时加载,不需要时可随时卸载;动态库发生异常时,一般不会导致主程序中止。 使用动态库 动态库 动态库本身不需要特殊设置,正常编译、构建即可。(以名为dll的动态库为例,实际文件名为libdll.so) 主程序 编译时 静态调用方式 主程序直接编译会报错:Warning: linker:/usr/bin/ld: cannot find -ldll: No such file or directory,意思是链接器找不到名为dll的动态库文件。 解决方案 打开Project Options->Compiler Options->Compilation and Linking,勾选Pass options to linker with "-k", delimiter is space,并在下方填入-L’动态库所在路径’,保存即可正常编译。 动态调用方式 主程序直接编译即可。 运行时 静态调用方式 此时编译后的程序并不能正常运行,会报错error while loading shared libraries: libdll.so: cannot open shared object file: No such file or directory,意思是加载动态库时找不到名为libdll.so的动态库文件。 这是前面提到的路径搜索顺序导致的,可将动态库放入相应的搜索路径下,也可使用-rpath指定路径。对于自定的动态库,建议使用-rpath指定路径,最好指定的路径为当前路径(与Windows保持一致)。 解决方案 打开Project Options->Compiler Options->Compilation and Linking,勾选Pass options to linker with "-k", delimiter is space,并在下方填入-rpath='$ORIGIN'(与之前的-L’动态库所在路径’之间要添加一个空格),保存并重新编译,即可正常运行。...

2025-02-20 21:51:24 · 1 分钟 · 慢步道人

Lazarus为应用添加版本信息

需求背景 在开发过程中,我们经常需要给应用添加版本信息,以便于追踪和管理应用的版本。 对于windows平台,直接在Project->Project Options...对话框中的Project Options->Version Info内进行设置即可。 但是,对于非windows平台,该方案便不可行。 而且,该方案还存在一个明显的问题:版本信息需要手动维护,未能与版本控制系统进行有效关联,很可能出现应用与源码不一致的情况。 解决思路 不使用Version Info功能,但取其把版本信息编译到可执行文件中的思想。 借鉴vscode的关于方案,版本信息中包含版本号和提交ID即可使应用和源码进行关联。 只要解决了在构建时动态获取版本信息的问题,便可以解决该问题。 解决方案 经多种尝试,最终确定的方案如下: 在源码中添加ver.inc文件,用于存储版本信息,在关于界面等进行展示。 编写脚本,获取版本信息并写入ver.inc文件。 打开Project->Project Options...对话框,在Compiler Options->Compiler Commands->Execute before中添加脚本的路径。 正常构建应用即可。 如要跨平台,可分别编写对应平台的脚本并创建对应的Build mode,在Build modes中选择对应的模式,然后配置对应的脚本,正常构建即可。 附脚本 以使用git为例: windows 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @echo off ::获取版本号 git describe --tags >nul 2>nul if errorlevel 1 ( set VER_NO=0.0 ) else ( for /f "delims=" %%a in ('git describe --tags') do set VER_NO=%%a ) ::获取提交id git rev-parse HEAD >nul 2>nul if errorlevel 1 ( set COMMIT_ID=0000000000000000000000000000000000000000 ) else ( for /f "delims=" %%b in ('git rev-parse HEAD') do set COMMIT_ID=%%b ) ::写入文件 echo const > ver....

2024-11-12 20:08:32 · 1 分钟 · 慢步道人

Go path包

path包 path包仅适用于处理由正斜杠/分隔的路径,例如URL。 不能处理带有盘符或反斜杠\的Windows路径。 包函数 方法 含义 说明 Base 返回路径的最后一个元素 会先移除路径末尾的斜杠,空路径返回.,纯斜杠返回/ Clean 返回与输入路径等效的最短路径名 Dir 返回路径的目录部分 Ext 返回路径中以.分隔的文件扩展名 无.则返回空字符串 IsAbs 判断路径是否为绝对路径 只有以/开头的才是绝对路径 Join 将路径元素连接成路径 Match 判断路径是否匹配模式 Split 将路径拆分为目录和文件 filepath包 处理方式与目标操作系统定义的文件路径相兼容。 包函数 方法 含义 说明 Abs 返回绝对路径 Base 返回路径的最后一个元素 会先移除路径末尾的斜杠,空路径返回.,纯斜杠返回/ Clean 返回与输入路径等效的最短路径名 Dir 返回路径的目录部分 EvalSymlinks 返回路径中的符号链接所指向的真实路径 Ext 返回路径中以.分隔的文件扩展名 无.则返回空字符串 FromSlash 将路径中/的斜杠替换为特定操作系统的分隔符字符 linux中的\不会被替换 ToSlash 将路径中特定操作系统的分隔符字符替换为/ linux中的\不会被替换 Glob 返回与模式匹配的所有路径 IsAbs 判断路径是否为绝对路径 只有以/开头的才是绝对路径 IsLocal 判断路径是否为本地路径,即是否在当前路径下 仅词法分析,不考虑文件系统 Join 将路径元素连接成路径 Localize 将一个以/分隔的路径转换为一个操作系统路径 Match 判断路径是否匹配模式 Rel 返回从basepath到targpath的相对路径 Split 将路径拆分为目录和文件 SplitList 将一个包含多个以特定操作系统路径分隔符(如:或;)分隔的路径字符串拆分成单个的路径元素 分隔符取决于当前操作系统 VolumeName 返回路径的卷名 适用于Windows平台 Walk 遍历路径下的所有目录和文件并对其调用fn WalkDir 遍历路径下的所有目录和文件并对其调用fn 比Walk更优更高效

2024-11-03 22:12:48 · 1 分钟 · 慢步道人

Go sync包

Mutex 无需显式初始化,直接声明变量即可使用。 互斥锁,同一时刻只能有一个协程持有锁,不分读写。 方法 含义 说明 Lock 加锁 Unlock 解锁 未加锁时调用会导致panic TryLock 尝试加锁 立即返回,成功返回true,失败返回false RWMutex 无需显式初始化,直接声明变量即可使用。 读写锁,同一时刻可以有多个协程持有读锁,但是只能有一个协程持有写锁。 有写锁时,其他协程无法获取读锁和写锁。 有读锁时,其他协程可以获取读锁,但是无法获取写锁。 方法 含义 说明 RLock 加读锁 RUnlock 解读锁 未加读锁时调用会导致panic TryRLock 尝试加读锁 立即返回,成功返回true,失败返回false Lock 加写锁 Unlock 解写锁 未加写锁时调用会导致panic TryLock 尝试加写锁 立即返回,成功返回true,失败返回false Map 无需显式初始化,直接声明变量即可使用。 方法 含义 说明 Store 存储键值对 Load 加载键对应的值 Delete 删除键对应的值 Clear 清除所有键值对 Swap 交换指定键对应的旧值和新值,并返回旧值和操作之前键是否存在 LoadAndDelete 加载键对应的值并删除键值对 LoadOrStore 加载键对应的值,如果键不存在则存储键值对 CompareAndDelete 比较键对应的值是否等于指定值,如果相等则删除键值对 CompareAndSwap 比较键对应的值是否等于指定值,如果相等则替换键对应的值 Range 遍历键值对 传入函数返回false时停止遍历 WaitGroup 无需显式初始化,直接声明变量即可使用。 方法 含义 说明 Add 添加计数器 启动协程前调用,传入协程数量 Done 减少计数器 协程执行完毕后调用 Wait 等待计数器变为0 主协程调用,等待所有协程执行完毕 Once 无需显式初始化,直接声明变量即可使用。 方法 含义 说明 Do 执行函数 传入函数,只会执行一次 Pool 需显式初始化,为New指定创建对象的函数。...

2024-10-30 22:45:48 · 1 分钟 · 慢步道人