0

0

使用Go语言调用Windows API获取字体目录

碧海醫心

碧海醫心

发布时间:2025-10-23 12:20:19

|

493人浏览过

|

来源于php中文网

原创

使用go语言调用windows api获取字体目录

本文详细介绍了如何在Go语言中通过调用Windows API `SHGetKnownFolderPath`来准确获取系统字体目录。文章涵盖了`GUID`结构体的定义、`syscall`包的使用、`SHGetKnownFolderPath`和`CoTaskMemFree`函数的实现细节,并提供了完整的Go语言示例代码,帮助开发者正确地进行Windows特殊文件夹路径的查询和内存管理。

简介与核心概念

在Windows系统中,获取特殊文件夹(如字体目录、程序文件目录等)的推荐方式是调用Shell API中的SHGetKnownFolderPath函数。这个函数能够根据一个已知的文件夹ID(KNOWNFOLDERID)返回对应的文件系统路径。由于Go语言的标准库没有直接提供此功能,我们需要借助syscall包来调用底层的Windows API。

SHGetKnownFolderPath函数的C++签名如下:

HRESULT SHGetKnownFolderPath(
  _In_      REFKNOWNFOLDERID rfid,
  _In_      DWORD dwFlags,
  _In_opt_  HANDLE hToken,
  _Out_     PWSTR *ppszPath
);

其中,REFKNOWNFOLDERID是一个GUID结构体,用于标识特定的已知文件夹。ppszPath参数在函数成功返回后会指向一个由CoTaskMemAlloc分配的字符串缓冲区,因此在不再使用该路径时,必须调用CoTaskMemFree函数来释放这块内存,以避免内存泄漏。

立即学习go语言免费学习笔记(深入)”;

Go语言实现细节

为了在Go语言中调用上述API,我们需要进行以下步骤:

1. 定义GUID结构体与FOLDERID_Fonts

KNOWNFOLDERID实际上是一个GUID(全局唯一标识符)。我们需要在Go中定义一个对应的结构体,并提供字体目录的GUID常量。

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

// GUID 结构体定义,对应Windows API中的GUID
type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

// FOLDERID_Fonts 是字体目录的KNOWNFOLDERID
var FOLDERID_Fonts = GUID{0xFD228CB7, 0xAE11, 0x4AE3, [8]byte{0x86, 0x4C, 0x16, 0xF3, 0x91, 0x0A, 0xB8, 0xFE}}

2. 加载DLL并获取函数指针

使用syscall.NewLazyDLL和NewProc来加载Shell32.dll和Ole32.dll,并获取SHGetKnownFolderPath和CoTaskMemFree的函数指针。

302.AI
302.AI

302.AI是一个汇集全球顶级AI的自助服务平台

下载
var (
    modShell32               = syscall.NewLazyDLL("Shell32.dll")
    modOle32                 = syscall.NewLazyDLL("Ole32.dll")
    procSHGetKnownFolderPath = modShell32.NewProc("SHGetKnownFolderPath")
    procCoTaskMemFree        = modOle32.NewProc("CoTaskMemFree")
)

3. 封装SHGetKnownFolderPath函数

在Go中封装SHGetKnownFolderPath,需要将Go类型映射到C类型,并使用syscall.Syscall6进行实际的API调用。unsafe.Pointer在这里是必需的,因为它允许我们将Go的指针类型转换为uintptr,以传递给C函数。

// SHGetKnownFolderPath 封装了Windows API SHGetKnownFolderPath
func SHGetKnownFolderPath(rfid *GUID, dwFlags uint32, hToken syscall.Handle, pszPath *uintptr) (retval error) {
    // Syscall6用于调用带有6个参数的Windows API
    // r0 是HRESULT返回值
    r0, _, _ := syscall.Syscall6(procSHGetKnownFolderPath.Addr(), 4, // 4表示参数数量
        uintptr(unsafe.Pointer(rfid)),       // rfid (REFKNOWNFOLDERID)
        uintptr(dwFlags),                    // dwFlags
        uintptr(hToken),                     // hToken (通常为0)
        uintptr(unsafe.Pointer(pszPath)),    // ppszPath (PWSTR *)
        0, 0)                                // 额外的参数,此处不使用
    if r0 != 0 {
        retval = syscall.Errno(r0)           // 如果返回非0,表示错误
    }
    return
}

4. 封装CoTaskMemFree函数

同样,封装CoTaskMemFree用于释放由SHGetKnownFolderPath分配的内存。

// CoTaskMemFree 封装了Windows API CoTaskMemFree
func CoTaskMemFree(pv uintptr) {
    // Syscall用于调用带有1个参数的Windows API
    syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(pv), 0, 0)
    return
}

5. 创建FontFolder辅助函数

为了更方便地获取字体目录,可以创建一个FontFolder函数来整合上述逻辑,并处理路径转换和内存释放。

// FontFolder 获取Windows字体目录的路径
func FontFolder() (string, error) {
    var path uintptr // 用于接收路径的指针
    // 调用SHGetKnownFolderPath获取字体目录路径
    err := SHGetKnownFolderPath(&FOLDERID_Fonts, 0, 0, &path)
    if err != nil {
        return "", err
    }
    // 确保在函数退出时释放内存
    defer CoTaskMemFree(path)
    // 将UTF-16编码的路径转换为Go的string类型
    folder := syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(path))[:])
    return folder, nil
}

这里使用了defer CoTaskMemFree(path)来确保即使在UTF16ToString转换失败或后续操作出错时,内存也能被正确释放,这是良好的实践。

完整示例代码

将以上所有部分组合起来,构成一个完整的Go程序:

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

// GUID 结构体定义,对应Windows API中的GUID
type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

// FOLDERID_Fonts 是字体目录的KNOWNFOLDERID
var FOLDERID_Fonts = GUID{0xFD228CB7, 0xAE11, 0x4AE3, [8]byte{0x86, 0x4C, 0x16, 0xF3, 0x91, 0x0A, 0xB8, 0xFE}}

var (
    modShell32               = syscall.NewLazyDLL("Shell32.dll")
    modOle32                 = syscall.NewLazyDLL("Ole32.dll")
    procSHGetKnownFolderPath = modShell32.NewProc("SHGetKnownFolderPath")
    procCoTaskMemFree        = modOle32.NewProc("CoTaskMemFree")
)

// SHGetKnownFolderPath 封装了Windows API SHGetKnownFolderPath
func SHGetKnownFolderPath(rfid *GUID, dwFlags uint32, hToken syscall.Handle, pszPath *uintptr) (retval error) {
    r0, _, _ := syscall.Syscall6(procSHGetKnownFolderPath.Addr(), 4,
        uintptr(unsafe.Pointer(rfid)),
        uintptr(dwFlags),
        uintptr(hToken),
        uintptr(unsafe.Pointer(pszPath)),
        0, 0)
    if r0 != 0 {
        retval = syscall.Errno(r0)
    }
    return
}

// CoTaskMemFree 封装了Windows API CoTaskMemFree
func CoTaskMemFree(pv uintptr) {
    syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(pv), 0, 0)
    return
}

// FontFolder 获取Windows字体目录的路径
func FontFolder() (string, error) {
    var path uintptr
    err := SHGetKnownFolderPath(&FOLDERID_Fonts, 0, 0, &path)
    if err != nil {
        return "", err
    }
    defer CoTaskMemFree(path)
    folder := syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(path))[:])
    return folder, nil
}

func main() {
    folder, err := FontFolder()
    if err != nil {
        fmt.Println("获取字体目录失败:", err)
        return
    }
    fmt.Println("字体目录:", folder)
}

运行上述代码,在Windows系统上通常会得到类似以下输出:

字体目录: C:\Windows\Fonts

注意事项与总结

  • unsafe包的使用: 调用Windows API通常需要使用unsafe.Pointer进行类型转换,这绕过了Go的类型安全检查。在使用时务必小心,确保类型转换的正确性,避免内存损坏。
  • 内存管理: SHGetKnownFolderPath返回的路径字符串是由CoTaskMemAlloc分配的,因此必须使用CoTaskMemFree来释放。使用defer关键字可以确保在函数退出时无论发生什么情况,内存都能被正确释放。
  • 错误处理: 始终检查SHGetKnownFolderPath的返回值,syscall.Errno(r0)可以将API返回的错误码转换为Go的error类型。
  • 平台限制: 此方法仅适用于Windows操作系统。在跨平台应用中,需要针对不同操作系统提供不同的实现。
  • 扩展性: 掌握了这种调用Windows API的方法,您可以举一反三,通过查找其他KNOWNFOLDERID的值来获取其他系统特殊文件夹的路径。

通过本文的详细讲解和示例代码,您应该已经掌握了如何在Go语言中通过syscall包调用Windows API SHGetKnownFolderPath来准确获取系统字体目录。这种方法不仅专业可靠,而且为Go语言开发者在Windows平台上进行更深层次的系统交互提供了强大的工具

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1437

2023.10.24

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

184

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

268

2023.10.25

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

179

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

273

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

251

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

121

2025.08.07

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

251

2023.08.03

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

150

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.2万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号