今天我想尝试用 C++ 放点音乐,于是前往bdfs搜索一番,发现了一个看起来十分不错的库——ufmod——尽管年代久远(请记住这个“年代久远”),但是功能简单,配置看起来也不难(大雾)。这个库托管在 sourceforge 上,很快就下好了,一切似乎都非常顺利。
下好了源码包,我从中找到了 ufmod.lib, 看起来甚至不需要我来编译,这真是太棒了。我马上新建了一个文件夹,刚建好源文件,我却犯了难——我找不到库函数的定义,我根本不知道这些函数该如何使用。
为了找到函数的使用方法,我再次bdfs,然而,由于年代久远,网上的资料寥寥无几,我在仅有的几篇资料中注意到了两个宏:XM_FILE和XM_MEMORY。关于这两个宏的故事,我们后面再谈。
bdfs无果,我退而求其次,尝试从ufmod.lib和ufmod.dll等文件中寻找符号表,我从 github 下载了一份 dumpbin 工具,导出了库文件的符号表。这里摘录部分数据如下
ordinal hint RVA name
1 0 000010AA uFMOD_GetRowOrder
2 1 000010A6 uFMOD_GetStats
3 2 000010BA uFMOD_GetTime
4 3 00001100 uFMOD_GetTitle
5 4 00001030 uFMOD_Jump2Pattern
6 5 0000109A uFMOD_Pause
7 6 00001214 uFMOD_PlaySong
8 7 0000109E uFMOD_Resume
9 8 00001081 uFMOD_SetVolume
但是,还有一个关键的问题没有解决——函数的参数呢?
由于这个库是使用汇编编写的,生涩难懂,想通过阅读源码的方法来获得所需的信息对我来说不大可能了。我回到源码包,注意到其中有一份 ufmod.desc,里面有着各个函数的简要描述,包括函数的参数。
然而,这份描述有些令人难以理解:为什么 uFMOD_PlaySong 函数的前两个参数类型为 Any ???
所幸,作者使用了匈牙利表示法来命名参数,在 ChatGPT 的帮助下,我大致猜到了 lpXM 和 fdwSong 含义。但是第二个参数 para 我仍不明白有何含义。
随着bdfs再次失败,我终于看到了源码包中有一个 Examples 文件夹,看到这个文件夹的那一刻,我感觉自己唐完了。
Examples 文件夹里有着非常完善的解释,报错各个参数的含义,和之前提到的 XM_MEMORY 和 XM_FILE 的定义。最关键的是,这份实例里有一份 ufmod.h 头文件,这意味着我无需再操心函数定义的问题了。
胜利仿佛就在眼前,我添加好了链接器参数,开始编译。
不幸的,链接出锅了。uFMOD_PlaySong 没能成功链接到库文件。
随后我又尝试了多种方法,包括改换 ufmod.obj,都没能链接成功
由于链接器抽风,我不得不改换使用动态 DLL 的方式来加载这个库。
经过一番努力,代码再次出锅,GetLastError() 返回 139,为什么会变成这样呢?
一如往常,bdfs没能给出令人满意的回答。许多资料称这是因为加载的 dll 库依赖的其他库不存在。此时,我突发奇想,把编译器换成了32位,奇妙地解决了这个问题。
dll成功加载了,函数地址也成功获取。我的努力看起来没有白费,似乎马上就要成功,然而我的电脑却给我开了个玩笑:程序在播放了第一个音符后就立即停止了。
我尝试了库中所有的函数,都没法使音乐继续播放。使用 DLL 的道路也失败了。
尽管 dll 失败,我仍不死心,我注意源码包中还有一个 oalufmod.dll,OpenAL 成了我最后的希望。
参考了一番示例中的代码,我成功抄出来了一份使用 OpenAL 播放的代码(雾)。
在安装完 OpenAL 的驱动后。我期待着我的程序能够正常播放,结果却更加令人失望,这次连一点声音灰没有了。
于是,最后的希望也破灭了。
我终于是气急败坏了。
由于示例中提供的 obj 文件虽能链接却会使程序还没进入主函数就立刻 RE。抱着破罐子破摔的心态,我下载了一个 fasm 汇编器,直接重新编译了一份 obj 目标文件。但是 waveOut 系列函数却总无法链接。bdfs得到的资料称应在链接器参数中加入 -lwinmm,然而这并没有任何效果。
忽然,我想起了我前段时间安装的 Windows SDK 中有一个 WinMM.Lib。我抱着试一试的想法,把源码包中提供的 vb_winmm.lib 替换为了 WinMM.Lib。
就在此时,奇迹发生了,程序成功链接并播放了音乐。我不禁感叹:远古库害死人呐。
在链接器参数中,请将 WinMM.Lib 放在 ufmod.obj 后面,否则将导致链接失败。