如何读懂CPLEX的C++Project配置说明文件

先随便聊几句。很多人问我: Dr. Du ,你为什么喜欢用 MATLAB+YALMIP 来调 CPLEX Gurobi ?其中的原因很有很多: (1) 我写好的模型可能需要同时在不同的 Solvers CPLEX, Gurobi 等)上测试来比较不同 Solvers 的性能,用 YALMIP 几乎不用改代码就可以做到; (2) MATLAB 里处理数据方便。有人追问:那 YALMIP 管理模型的 overhead 成本很高,你受的了么?实际情况是:一般情况下受得了;实在受不了的时候,我也受着,但在统计计算时间时,我只算 solver 的时间,不算 YALMIP 的时间。继续追问: MATLAB+YALMIP 在写模型的时候是不是更快、更方便?这也是其中一个原因吧?答:总得来说,其实 CPLEX Gurobi 提供高级语言编程接口(比如 C++, Java )已经很方便了,在写模型模方面比 formulation tools (AMPL, AIMMS, YALMIP ) 稍微麻烦一点,但基本也差不多。好吧,我承认我用 MATLAB+YALMIP 的另外一个原因:就是 C++ 写的代码,每次 build compile+link )程序之前,总要在 Visual Studio 里面对 project properties 做半天配置,有时候中间某个配置不小心弄错了,报一堆错误,还得回过头对这些配置仔细核对。

但话说回来,真正的企业部署,很多时候还是要用到 C++ 这些高级语言;另外,你要用 CPLEX 的一些最新的功能, MATLAB 接口的支持往往比较差,所以,这时候还要回到 C++ Java 这些语言的接口上。对于 C++ 开发者来说,就绕不开 Project properties 的配置。整个配置过程的说明,见 CPLEX 文件夹(我的电脑是 C:\Program Files\IBM\ILOG\CPLEX_Studio1271\cplex\ ,为叙述方便,我们这里记这个文件夹为 )下的 c_cpp.html 这个文件。按照这个过程配置 project properties ,就不会出错。但很多小朋友反应:虽然能按葫芦画瓢地完成配置过程,很多地方不甚理解,若遇到报出的编译、链接错误,也是一头雾水。这篇博文就是帮大家彻底读懂这个文件,并在遇到编译、链接错误的时候不再惊慌,减少些恐惧感。

1. 静态链接库与动态链接库

当我们拿到一个第三方的函数库,希望直接调用里面的函数,这个第三方函数库往往通过静态链接库( static link library ,后缀名 .lib )和 / 或动态链接库( dynamic link library, DLL )的形式提供。

若是静态库,当我们的程序调用其函数时,在程序的编译、链接阶段,编译器和链接器会把这个函数的代码从第三方库中完全 copy 过来,加入我们的代码,那么生成的 exe 文件自然就包含了这些代码(的可执行形式)。一种更节省空间的形式是:在生成 exe 文件时并不把第三方的库函数代码包含进去,只有在 run 这个 exe 文件时( runtime )时,才把库函数(可执行的机器码) load 到内存中,共 exe 文件调用,这样就可以更节省空间,这就是动态链接库 (DLL ,文件后缀名 .dll) 。静态库和动态库在工业界应用的都很多,很多函数库(如 C++ 标准函数库)通过静态库和动态库两种方式提供。

下面我们在详细地说明一下 DLL 。当我们的 exe 文件在运行时( runtime ),需要把第三方库函数( .dll )加载进内存,这里具体又分为两种方式。第一种方式:在我们的程序编译链接生成 exe 文件时,在 exe 文件中建立了一个 Import Address Table (IAT) ,这个 IAT 中每一条 record 都记录了 .dll 库中被调用到的一个函数,但只是函数的名字等信息,也就说 build 好的 exe 文件中的 IAT 记录了被调用到的库函数的所有信息;在 exe 文件运行开始时,程序会把 .dll 库函数文件中的所有函数一股脑载入内存,然后, IAT 这个表格中的每一条函数信息被具体的函数指针所代替(指向内存中具体的位置),这样程序就知道去内存中什么位置找到具体的函数并调用了;这里要指出的时,在第一步( build 生成 exe 文件时)建立 IAT 这个表格时,不是直接去跟 .dll 文件打交道(用不着跟 .dll 打交道,因为这只是建立 IAT 表格而不是实际调用里面的函数),而是跟 .dll 文件的一个辅助文件打交道,这个辅助文件后缀名也是 .lib (我们成为 DLL import libraray DLL 的“导入库”),但是与前面提到的静态库 .lib 是完全不一样的;有人问:这个 .lib 导入库文件怎么来的?其实 .dll 文件在生成时的时候,就一并产生它的辅助文件( .lib 导入库文件)。比如 C++ 标注库在以 DLL 形式提供时(当然也以静态库形式同时提供),其 DLL 文件时 ucrtbase.dll ,也提供了其辅助的导入库文件 ucrt.lib ,这个 .lib 文件只是配合 ucrtbase.dll 的,方便用户在 build 产生 exe 文件时建立 IAT 表。这里我们介绍的第一种 DLL 的加载方式就此搁笔,让我们称其为“静态加载”。现在看第二种加载方式——动态加载,这种方式只需要 .dll 文件,不需要其辅助的 .lib 导入库文件:在这种方式下,用户的应用程序中会显示地通过 LoadLibrary() GetProcAddress() FreeLibrary() 等函数与 .dll 文件中的函数进行沟通,需要调哪个库函数,就把其 load 进来,调用,然后从内从中在释放出去;这样在 exe 文件执行之初,就不需要载入 .dll 库中的所有函数,而是执行到 LoadLibrary() GetProcAddress() 这些语句时,才真正载入内存,执行到 FreeLibrary() 部分时会把该函数在从内存中释放掉。与第一种方式(静态加载)相比,这种方式显然对内存的使用更经济,但其致命缺陷是用户的应用程序中要自己通过 LoadLibrary() GetProcAddress() FreeLibrary() 等函数与 .dll 文件打交道,使用起来极其麻烦,所以实际中较少使用这种方式,这也是为什么“标准 C++ 库”、 ”CPLEX 在提供 .dll 文件时,也会提供其辅助的 .lib 导入库文件(对了 .lib 文件还有其对应的 .h 文件),方便用户通过“静态加载”的方式调用。

总结一下:第三方库函数往往通过静态链接库( SLL, .lib 文件及对应的 .h 文件)和 / 或动态链接库( DLL )的方式提供;为方便 DLL 方式调用,不但提供了 .dll 文件,还提供了辅助性的导入库文件 (.lib 文件及对应的 .h 文件 )

2. C++ 库与 C/C++ Run-time (CRT) ibrary

首先,用 C++ 语言,就要用到 C++ 标准库,简单地说(不严格,别拍砖), C++ Run-time Library 就是 C++ 标准库,是微软对 C++ 标准(如 ISO C99 standard library )的封装。这样,电脑上安装了 C++ Run-time Library ,就可以调用 C++ 标准库中的函数了,比如对 iostream string 的操作。这个 C++ Run-time Library ,也称为“ Universal CRT (UCRT) ”。微软通过静态库( libucrt.lib )和动态库( ucrtbase.dll 及导入库 ucrt.lib )两种方式提供,为方便开发者 debug 的需要,静态库和动态库都有其 debug 版本:比如静态的 libucrtd.lib 和动态的 ucrtbased.dll (导入库 ucrtd.lib )。这里注意的是, libucrtd.lib 的文件名,比 libucrt.lib 多了一个字母“ d ”,表示的是 debug ,很多第三方的库函数,无论动态库还是静态库,都会提供 debug 版本,对应的库文件名上也习惯加一个字母 ”d” ,大家知道就好了,后面我们省去对 debug 版本的讨论,只用普通版本( release 版本)来讨论。另外指出, C++ 标准库 UCRT 实际上以被微软作为 Window 10 的一部分放入 Windows 10 SDK 了,所以是你的操作系统的一部分,这些 .lib 文件(如静态库 libucrt.lib, 导入库 ucrt.lib )可以在 C:\Program Files (x86)\Windows Kits\10\lib\... 里找到 , .dll 文件(如与 ucrt.lib 关联的 ucrtbase.dll )可以在 C:\Windows\System32 C:\Windows\SysWOW64\ 等地方找到。

好了,到现在我们知道,系统中有了前面的 CRT (或说 UCRT )文件,我们就可以调用标准的 C++ 函数库了。但我们在 Visual Studio 里面用的是 Visual C++ (VC) VC 函数库实际上是标准 C++ 库的扩展:在异常处理、 Debug run-time check 和其它功能方面都作了扩展,使我们用 C++ 更爽。你在安装 Visual Studio 2015 (VS2015) 时,相应的 VC 函数库会被安装:静态库的名字是 libvcruntime.lib ,动态库包括 vcruntime140.dll VS2015 用的是 VC14.0 版本)极其导入库 vcruntime.lib 。这里谈到的 .lib 文件可以在 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib\ 找到, .dll 文件在可以在 C:\Windows\System32 C:\Windows\SysWOW64\ 等地方找到。

为了支持 C++ 标注库和 VC 库的运行(启动、数据初始化、多线程控制、结束)等,有必要还有一个辅助性的库,这个库只提供静态库,没有 DLL 库;但其有几个版本: libcmt.lib支持的静态的CRT 库(简写 /MT ); msvcrt.lib支持的是DLL 形式的 CRT vcruntime 库(简写为 /MD ),他们都有 debug 版本,及 libcmtd.lib (简写 /MTd )和 msvcrtd.lib(简写/MDd )。现在基本上不常用 libcmt.lib(/MT )和 libcmtd.lib /MTd )了,常用 msvcrt.lib(/MD )和 msvcrtd.lib(/MDd )。这也是为什么 IBM 发布的 c_cpp.html 这个说明文件中建议,你的程序是 release 版本,就要链接 msvcrt.lib ,如果是 debug 版,就要链接 msvcrtd.lib ,这个文件无非就是 C++ 标准库 (ucrt) VC (vcruntime) 的协助支持性库,也就是说链接上 msvcrt 这个库了,就可以跟 C++ 标准库和 VC 库通信了。 msvcrt 这个库 ( msvcrtd.lib) 可以在 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib\ 找到,可以看出,也是在装 VS2015 时装上去的。

总结一下:我们有一个标准 C++ 库( ucrt ),还有一个 VC 库( vcruntime ),它们的运行需要一个支持性的库 msvcrt 。其中 ucrt vcruntime 有静态库和动态库,而 msvcrt 现在只有静态库(以前的 VC 版本可能既有静态库又有动态库)。 ucrt Windows 10 系统自带的, vcruntime msvcrt 是装 VS2015 时装上去的。许多软件公司在发布自己软件(如电影播放软件等)时,因为自己的软件需要 vc 的支持,为担心用户的电脑上没有装 vcruntime 等库,也会一并打包到发布的软件中。

3. CPLEX 相关的库: cplex library concert technology library

IBM 提供的关于 cplex 的库(这里只说 CPLEX Optimizer ,不说 CP Optimizer )有两个: cplex library ,用于求解;为方面建模,还提供了 concert technology library ,有了 concert ,你在程序里写目标函数、变量和约束条件时才比较爽。为方便下面讨论,记 cplex 的安装目录(我的电脑上是 C:\Program Files\IBM\ILOG\CPLEX_Studio1271\cplex\ )为 , concert 的安装目录(我的电脑上是 C:\Program Files\IBM\ILOG\CPLEX_Studio1271\concert\ )为

Concert 库是提供静态库,没有 DLL 库,其 release 版本可以在 \lib\x64_windows_vs2015\stat_mda\找到,debug 版可以在 \lib\x64_windows_vs2015\stat_mdd\找到。

Cplex 库相反只提供了 DLL 库, .dll 文件可以在 \bin\x64_win64\ 找到,其导入库 .lib 文件可以在 \lib\x64_windows_vs2015\sta_mda\ 找到。

到此为止,你再看 IBM 发布的这个 c_cpp.html ,就十分明了了,整个过程无非就是告诉编译器和链接器我们所依赖的 cplex library concert library 在什么位置,已经我们具体依赖哪些文件( .lib 文件)。你也再也不会为 VS 报出的各种 link errors 手足无措了。

That’s the end of this long article! Cheers!

后记:写这篇文章前,我逐渐意识到一个问题:我的年龄逐渐增大,不像读博士时一动不动能坐一天了,这里提醒我自己,改变工作习惯:觉得累的时候,停下所有“不能停下的活(哪怕是在讲课)”去休息;午饭最好和别人一起吃,这是一个放松休息的时间,午饭后在办公室外的地方休息和娱乐一下,然后再开始下午的工作。做一个“不断改变”的人!最后祝所有的科研工作者身体健康!

我来评几句
登录后评论

已发表评论数()