前面我们先是讨论了 Excel 密码保护措施的脆弱性,接着介绍了一个尚未成熟的 VBA 编译工具——VBA Padlock,接下来,我们来讨论我们自己所能实现的对 VBA 代码的保护措施。因为 VBA 代码是以源代码的形式存储在 Excel 文件中的,要实现最高强度的保护,最直接的办法就是将 VBA 源代码编译为机器代码。VBA Padlock 工具就是这个思路,但该工具尚不成熟,而且还需要付费。成本最低的办法就是我们自己将 VBA 源代码转换为某种编译语言,再通过该语言的编译器将其编译为机器代码。一种比较可行的办法就是将 VBA 代码转换为 Delphi 代码。Delphi 有如下几点优势:- Delphi 是一种编译语言,可以编译为机器代码;
- Delphi 支持 OLE Automation,VBA 代码比较容易转换为 Delphi。
现在,我们以《Excel 编程 -- 再谈访问数据库》中的写入/读出 Access 数据库为例,说明将 VBA 代码转换为 Delphi DLL 的过程。访问 Access 数据库的例子中包括了类 AceClient 以及 AceTestModule 中的 AceTest、WriteDataToAccess、ReadDataFromAccess 几个过程。首先,我们在 VS Code 中让 Copilot 做初步转换:Copilot 转换成的 Delphi 代码只能作为雏形,还是需要手工打磨,形成最终版本,如下所示:VBA 中的 AceClient 类转换为 Delphi 的 TAceClient。Delphi 的 Class 可以定义自己的构造函数,如图所示,TAceClient.Create 构造函数带一个参数,ADBFile。所以,TAceClient 类的实例化可以写成:var aClient: TAceClient;begin aClient := TAceClient('MyDb.accdb'); // ...end;
同样,AceTestModule 也是先通过 Copilot 进行初步转换,再手工打磨成为能够使用的最终版本:在这个文件头部,我们看到,AceTest 通过 exports 指令声明为 DLL 中向外呈现的接口,而 WriteDataToAccess 和 ReadDataFromAccess 都只是由 AceTest 调用的内部过程,外部不可见。function AceTest(const sheet: PWideChar; const db: PWideChar): Boolean; stdcall;
与 VBA 中的 Sub AceTest 不同,改写后的 AceTest 变成了一个函数,且该函数的调用规范遵循 stdcall,这是为了符合 Windows API 的规范。该函数接受两个参数:一个需要操作的工作表名称,一个需要生成的 Access 数据库文件名。仔细查看,其实 Delphi 的代码与 VBA 的代码相似度很高,这也使得 VBA 代码到 Delphi 代码的转换比较容易(相对于转换为 C/C++ 而言)。AceOperation.dll 的源代码包含两个模块:AceClient.pas 和 AceTestModule.pas,向外呈现的接口只有一个:AceTest 函数。为了适应不同环境,可以将编译后的 64 位 DLL 命名为 AceOperation64.dll,而将 32 位的 DLL 命名为 AceOperation32.dll。这是我们的 Excel 数据集。我们的任务是,将这个数据集写入 Access 数据库,然后再读出 Access 数据库中的数据,写入同一个工作表的右侧。首先声明 DLL 中的接口函数 AceTest,然后在 TestIt 过程中调用这个函数 AceTest 来完成我们的任务。此时,除以上几行代码之外,VBAProject 中不再包含其他的 VBA 代码。最后结果如下:此图与《Excel 编程 -- 再谈访问数据库》中的结果完全一样。说明我们的转换是成功的。很多人对于现在还在谈论 Delphi 似乎不以为然,认为 Delphi 已经是明日黄花。其实不然,从我们今天的例子看,Delphi 至少可以作为 VBA 源代码的“保护神”而存在!😄注:为了节省篇幅,Delphi 代码只显示了一部分。有需要 Delphi 代码的朋友,可以私信索取。