在 Visual C++ 或者 Visual Studio 中, 是无法直接使用 BCB 工程编译产生的库文件的. 究其原因, 是由于微软 Visual C++ lib 文件格式与 BCB 工程的 lib 文件格式不同所导致. Lib 文件中存放的是动态链接库的接口信息, 而不会含有任何函数的内部实现细节. 因此, 我们可以直接通过 Dll 文件来反向生成特定格式的 lib 文件, 以便在 VC 和 BCB 中交叉使用各个编译的动态链接库.
以 MTK 平台的多路下载工具 SP_MDT 为例, 演示在 VC++ 中直接使用 BCB 编译的 lib 库的问题及解决方案. 我们以 Eboot 为例, 源代码目录下关于 Eboot 的文件有以下几个:
Eboot 头文件定义, 路径: SP_MDT_SRC\Eboot
Eboot lib 库文件, 路径: SP_MDT_SRC\Lib
Eboot 动态链接库文件, 路径: SP_MDT_SRC\output
我们新建一个 VC++ MFC 工程, 将上述文件全部拷贝到 MFC 工程目录下, 同时在对话框中实现如下代码段:
#include "interface.h"
ANDROID_DL_HANDLE_T handle;
Android_DL_Create(&handle);
此时, 编译工程会报出如下错误:
error LNK2019: 无法解析的外部符号 _Android_DL_Create@4
该符号在函数 "public: void __thiscall CLibDemoDlg::OnBnClickedOk(void)" 中被引用
原因在于我们仅仅包含了相应函数的头文件, 而并没有导入任何的函数实现(如 cpp 源文件或者 lib 库文件). 接下来, 我们尝试直接在 VC++ 中使用 BCB 编译生成的 lib 库文件, 加入以下代码:
#pragma comment(lib, "eboot.lib")
编译时报出如下错误:
eboot.lib : warning LNK4003: 无效的库格式; 已忽略库
显然, VC++ 并不能正确的识别 BCB 所生成的 lib 库文件. 那么如何解决这个问题呢? 一般来说有两种方法:
- 动态加载 dll 动态链接库
最直接的方法是, 既然有头文件, 那么就可以知道各个函数的定义, 而 dll 动态链接库则会将这些公开的函数导出. 因此, 可以直接调用 LoadLibrary 载入动态链接库, 并查找到相应的函数地址, 完成调用. 这种方法简单粗暴, 其好处是写出的源代码无论是在 BCB 还是在 VC++ 平台都能够通用. 但其缺点也很明显, 需要改写头文件, 定义各种各样的函数指针, 如果使用到的导出函数很多, 则工作量较大. - 使用 VC++ 编译工具生成 lib 文件
那么既然原有的工程提供了头文件, 为了最大程度的减少工作量, 可以通过 BCB 生成的 dll 反向输出 VC++ 的 lib 文件, 实现静态加载 dll 动态链接库. 方法如下:
(1) 使用 dumpbin 生成 .def 文件
dumpbin 位于 [VS_DIR]\VC\bin 目录中, 执行如下命令产生 .def 文件:
dumpbin eboot.dll /EXPROTS /OUT:eboot.def
此时, eboot.def 文件内容为:
Dump of file E:\eboot.dll
File Type: DLL
Section contains the following exports for eboot.dll
00000000 characteristics
4E3F5BF7 time date stamp Mon Aug 08 11:45:59 2011
0.00 version
1 ordinal base
101 number of functions
44 number of names
ordinal hint RVA name
49 0 000021F0 Android_ADV_Connect
41 1 00001000 Android_ADV_Create
42 2 00001150 Android_ADV_Destory
47 3 00001B00 Android_ADV_FreeBuffer
46 4 000015D0 Android_ADV_LoadBuffer
51 5 00002A00 Android_ADV_Reboot
48 6 00001B30 Android_ADV_SendBuffer
50 7 00002640 Android_ADV_SendImage
43 8 000011D0 Android_ADV_SetBootArg
44 9 00001340 Android_ADV_SetDownloadArg
45 A 000015A0 Android_ADV_SetRemoteArg
13 B 00002FD0 Android_Boot_As_Download
18 C 00003150 Android_DA_Download
31 D 00003420 Android_DL_Create
32 E 000034E0 Android_DL_Destroy
33 F 00003550 Android_DL_Rom_Load
34 10 00003B20 Android_DL_Rom_UnloadAll
14 11 00003080 Android_Flash_Download
35 12 00003B60 Android_Remote_Download
71 13 00003C30 Android_Secured_Download
15 14 00003330 Android_Set_Lock
19 15 00003240 Android_Write_Trace
9 16 000140B0 CloseActiveSync
101 17 00010940 Eboot_DebugChangePath
4 18 00010910 Eboot_DebugClear
3 19 00010900 Eboot_DebugOff
5 1A 000108F0 Eboot_DebugOn
6 1B 00010920 Eboot_GetDLLInfo
12 1C 000108C0 Eboot_Log
64 1D 000154E0 GetAllDeviceNumber
63 1E 000155F0 GetAllPresentDevicePath
62 1F 000158B0 GetDeviceNumber
65 20 00016400 GetPortsDriverVersion
61 21 000159D0 GetPresentDevicePath
10 22 000140E0 RestartActiveSync
16 23 0000F2D0 SP_BootAsAdvmeta
21 24 0000F680 SP_BootAsAdvmetaByUSB
7 25 0000FA60 SP_BootAsFactoryNormalMode
20 26 0000FDD0 SP_BootAsFactoryNormalModeByUSB
2 27 0000EB70 SP_BootAsMeta
17 28 0000EF00 SP_BootAsMetaByUSB
11 29 00011260 SP_Flash_Direct_Download
1 2A 000111C0 SP_Flash_Download
8 2B 00013EF0 SP_MPDownload
Summary
106000 .data
4000 .rdata
3000 .reloc
1000 .rsrc
18000 .text
(2) 为 .def 文件添加 lib 说明
在 .def 文件开头处加入如下描述:
LIBRARY "eboot"
(3) 整理 .def 中的 EXPORT 信息
删除除 EXPORTS 段以外的其他无用信息, 同时根据头文件将 EXPORTS 段的 ordinal 和 name 字段进行整理:
- 如果是 stdcall 调用, 则整理为 name@param_size @ordinal 的形式; 注意, name 和 param size 之间不能有空格.
- 如果是 cdecl 调用, 则整理为 name@ordinal 的形式;
例如, Android_DL_Create 的定义为:
extern int __stdcall Android_DL_Create(ANDROID_DL_HANDLE_T *p_dl_handle);
可以看到该函数是一个 stdcall 调用, 其参数为 1 个指针, 占用 4 个字节, 因此整理为如下形式:
EXPORTS ...... Android_DL_Create@4 @31
(4) 根据 .def 生成 .lib 文件
在 .def 文件整理完毕之后, 使用 lib.exe 生成 .lib 文件, 执行如下命令:
lib /def:eboot.def /machine:i386 /out:eboot.lib
即可生成 eboot.exp 和 eboot.lib 文件. 将 eboot.lib 文件拷贝到 MFC 工程目录下替换原有的 lib 文件,
并重新编译, 通过. 将 eboot.dll 拷贝到与 exe 相同路径下, 运行正常.