书籍对我来说就像良药,总在我伤心难过的时候给我安慰,在我孤独寂寥的时候陪我左右,在我失去前行的动力的时候给我力量。其之于我就像一个家人,我对其敬佩有加,其对我关切备至。之前无意中在Greasyfork中搜索到一个脚本,可以将微信读书中的书籍内容导出成md或者html格式,当时我激动不已,觉得终于可以“书籍自由”了,我可以导出md然后在平板上写写画画,在电脑上记录读书的感想,不必局限于微信读书的条条框框(不过现在觉得其实微信读书社区非常强大,已经无法离开了hh)但是不知为何现在那个插件已经无法实现此功能了,我便怅然有种失落之感,觉得我与这位亲人顿时疏远了不少。为了重拾我的自由,我千方百计在网上寻找,终于找到了GitHub上大佬实现的代码,又经过一个下午的调试,终于成功实现书籍内容导出功能,其间看了许多中文英文的调试指南,又觉得与我今年所培养的debug专业能力非常贴合,于是记录文档如下。

环境

  • Windows11 联想拯救者 2060Ti
  • Python 3.9.8
  • Chrome 126.0.6478.127
  • gtk3-runtime-3.24.29-2021-04-29

踩坑经验

1 安装wereader_exporter

按照 https://github.com/drunkdream/weread-exporter 链接中的教程,安装好所有的依赖库之后执行 python -m weread_exporter -b $book_id -o epub -o pdf,结果报错:

Django:OSError: cannot load library ‘gobject-2.0-0’: error 0x7e.

2 安装gtk3运行库

在网上找到了解答 https://www.cnblogs.com/melloliana/p/16098061.html , 于是我按照教程安装了对应的运行时库,配置好环境变量(系统环境变量,位次设的比较高),并在wereader源代码的exporter.py文件下加上了os.add_dll_directory(r"C:\Program Files\GTK3-Runtime Win64\bin")。这下不报上面的gobject错了,程序也可以运行起来,但是运行的时候会显示Fontconfig error: Cannot load default config file错误,紧接着程序崩溃,只能关闭程序。

3 官网安装weasyPrint

我尝试了print调试法,锁定了出错的位置在import weasyPrint这句话上,那就是说这个第三方库还是有问题,那么问题在哪呢?我找到官网文档 https://doc.courtbouillon.org/weasyprint/latest/first_steps.html#missing-library ,遵照着教程,将weasyPrint重新安装了一遍,包括MSYS2, mingw-w64-x86_64-pango, 然后pip install,但是上面的问题仍然存在。

4 卸载Graphviz,解决冲突

然后我就在网上搜索这个font的问题,找到了最终的解决方案: https://github.com/Kozea/WeasyPrint/issues/1339 。问题在于,python的weasyPrint库依赖很多dll动态链接库,其中有一个链接库是fontconfig.dll,安装gtk的时候应该已经安装好了default config了,但是为什么在程序运行的时候还会出现这个问题呢?通过where指令可以返回类似D:\Program Files\Graphviz\bin\fontconfig.dll的结果,但是我们需要gtk的dll才行。所以我们卸载掉Graphviz之后再运行代码,这次就没有问题了。

思考复盘

其实问题始终围绕着weasyPrint库展开,动态链接库的配置,python如何寻找到对应的dll,这些都是因为认知模糊才出现的问题。

在Windows系统中,使用.dll文件(动态链接库)可以通过几种不同的方式将它们集成到你的程序中。以下是一些常见的方法:

1. 动态加载 .dll

在运行时,程序可以使用Windows API函数(如LoadLibraryGetProcAddress)动态加载和使用.dll文件中的函数。这种方法允许更大的灵活性,因为库可以在程序运行时被加载或卸载。

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
26
27
28
29
#include <windows.h>
#include <iostream>

typedef void (*FunctionType)();

int main() {
// 加载 DLL
HMODULE hModule = LoadLibrary(TEXT("example.dll"));
if (hModule == NULL) {
std::cerr << "加载DLL失败!" << std::endl;
return 1;
}

// 获取函数地址
FunctionType function = (FunctionType)GetProcAddress(hModule, "exampleFunction");
if (function == NULL) {
std::cerr << "获取函数地址失败!" << std::endl;
FreeLibrary(hModule);
return 1;
}

// 调用函数
function();

// 释放 DLL
FreeLibrary(hModule);

return 0;
}

2. 静态链接 .dll

在编译时,程序可以静态链接到一个导入库(.lib文件),这个库提供了对.dll文件中导出的函数的引用。在链接时,链接器会使用导入库中的信息来解析对.dll文件中函数的调用。

1
2
3
4
5
6
7
// example.cpp
#include "example.h" // DLL 导出的头文件

int main() {
exampleFunction();
return 0;
}

在构建过程中,你需要将导入库(example.lib)链接到你的程序中:

1
cl /EHsc example.cpp example.lib

3. 配置运行时环境

无论是静态链接还是动态加载,你都需要确保.dll文件在程序运行时可以被找到。常见的方式包括:

  • .dll文件放在与可执行文件相同的目录中。
  • .dll文件路径添加到系统的环境变量PATH中。
  • .dll文件放在系统目录中(如C:\Windows\System32)。

Python 中使用 .dll

在Python中,许多第三方库(如WeasyPrint)使用ctypescffi库来加载和调用.dll文件中的函数。例如:

1
2
3
4
5
6
7
import ctypes

# 加载 DLL
example_dll = ctypes.CDLL("example.dll")

# 调用函数
result = example_dll.exampleFunction()

发些牢骚

出现上述问题的原因在于,当ctypes调用CDLL函数寻找对应的dll文件时,找到的是Graphviz错误版本的dll,由此才会导致程序崩溃,本质其实是和找CUDA_HOME一个道理,我们既配置了os.add_dll_directory,想让程序去对应的文件夹下寻找dll,但是环境变量已经配置了,而环境变量的优先级又高于library,所以才会报错,掌握了底层原理,排查起来就快了。

所谓“不患无位,患所以立”,不发愁没有职位,只发愁没有任职的本领,最近身边的人都在实习,越来越多的人都找到实习了,很多时候比较焦虑,觉得是不是自己的能力的问题,但其实我真正应该担心的是有没有和心中想要的职位匹配的本领,前段时间面试了一个AI Research Intern,但是我对大模型并不了解,所以没能得到这个职位,很多人找的实习只是为了让自己有钱拿,有实习经历可以写,但是所找的职位更多的还是拧螺丝的重复工作,或者并不是自己真正想去的公司。

我一直是个理想主义的“笨人“,我知道是应试教育之下的复习,但我还是力求能通读全书,建立系统性的思维再考试,而不是只针对考点;对于一些“没用”的科目比如美学培养,选修课等,我却觉得非常有趣,于是很认真的学;机器智能方向需要很多数学知识,我便买了Intro to Linear Algebra从头开始看书看视频学习,我其实怀抱的就是能全方面的了解某个领域,然后再加以应用的心,但我现在觉得这样的心态有些不务实,一方面是我在理科学习上天赋欠缺,没有深入的逻辑思维能力,另一方面我并没有掌握关联学习,反馈学习的刻意练习技巧,想一口吃成个胖子,期待着如果能这样的话势必会比别人更优秀,基础更牢,我觉得不然。

但是这样的学习方式是值得认可的,这样的想法在实用主义的时代必然是有价值的(人总要相信自己所走的路),我坚信世间的许多道理,他们的底层逻辑是互通的,是可以通过深入底层的关联学习触类旁通的,这也是我的初衷所在,我相信有一天当我度过了学习的平台期,量变转为质变之后,我就能更加心安理得地追寻那梦寐以求的自由了。