目录
第1章编译 2
1.1 简介 2
1.2 下载 3
1.3 解决方案 4
1.4 创建项目 5
1.5 复制文件树 6
1.6 不使用预编译头文件 8
1.7 包含目录 9
1.8 定义宏 10
1.9 编译前事件 11
1.10 修改 obj 的位置 13
1.11 编译yasm 14
1.12 编译汇编代码 15
1.12.1 Custom Build 15
1.12.2 .rules文件 17
1.12.3 .targets文件 20
1.13 替换long long 23
1.14 strnlen函数 23
第1章编译
1.1 简介
MPIR 是一个开源的多精度整数和有理数计算库,基于 GMP 库开发。它有什么用呢?
如下图所示,笔者在推导缓和曲线弦长随弧长变化的函数时,得到了一个递推公式。
图1.1
递推公式如下:
可见的计算只涉及到分数(有理数)的四则运算,是比较简单的。只是随着的增大,运算量越来越大,手工计算会越来越困难。
上述公式可以编程实现。C/C++的基本数据类型中,使用double表示是比较恰当的。double是有误差的,而且的计算误差会积累,随着的增大的计算误差会越来越大。
借助MPIR的有理数计算功能,可以解决上述问题。的前几项见下表:
n | 系数 |
0 | 1 |
1 | -1/90 |
2 | 1/22680 |
3 | -79/2043241200 |
4 | 2633/12504636144000 |
5 | 1215749/1646485441080480000 |
6 | 48316241/15905049360837436800000 |
7 | 193921367/49059847710292202784000000 |
8 | -73267589502073/927478383356982505319631360000000 |
MPIR是从GMP移植而来的。因为GMP主要是针对于Unix、Linux操作系统的,使用VC++编译比较困难。MPIR的主要工作其实就是把GMP移植到Windows操作系统,使得VC++能够编译。
1.2 下载
浏览网址http://www.mpir.org/,下载mpir-2.7.0.zip,然后解压到W:\mpir\v2.7.0\mpir。目录结构如下图所示:
图1.2
1.3 解决方案
mpir 2.7.0自带了四个Visual Studio 解决方案,如下所示:
W:\mpir\v2.7.0\mpir\build.vc10\mpir.sln 使用vc2010打开
W:\mpir\v2.7.0\mpir\build.vc11\mpir.sln 使用vc2012打开
W:\mpir\v2.7.0\mpir\build.vc12\mpir.sln 使用vc2013打开
W:\mpir\v2.7.0\mpir\build.vc14\mpir.sln 使用vc2015打开
使用vc2010打开mpir.sln后,可以看到有七个项目,如下图所示:
图1.3
这七个项目的区别如下:
dll_mpir_gc 生成动态库,不使用汇编代码
dll_mpir_p3 生成动态库,含32位汇编代码(奔腾3)
dll_mpir_core2 生成动态库,含64位汇编代码(酷睿2)
lib_mpir_cxx 生成静态库,不使用汇编代码
lib_mpir_gc 生成静态库,不使用汇编代码
lib_mpir_p3 生成静态库,含32位汇编代码(奔腾3)
lib_mpir_core2 生成静态库,含64位汇编代码(酷睿2)
mpir有两套API:C API和C++ API。前者可用于C和C++,后者只能用于C++。lib_mpir_gc、lib_mpir_p3、lib_mpir_core2只含有C API;lib_mpir_cxx只含有C++ API;dll_mpir_gc、dll_mpir_p3、dll_mpir_core2既有C API又有C++ API。
从兼容性考虑,建议不要使用汇编代码。只有在对速度要求特别高的情况下,需要考虑使用汇编代码。
1.4 创建项目
为了使用vc6~vc2008编译mpir 2.7.0,需要创建项目。
笔者在W:\mpir\v2.7.0\vc目录下创建了11个目录,如下图所示。
make-dll-c 将创建动态库,只使用c语言
make-dll-asm32 将创建动态库,使用32位汇编代码
make-dll-asm64 将创建动态库,使用64位汇编代码
make-libS-* 将创建静态库,C运行时库为单线程
make-libT-* 将创建静态库,C运行时库为多线程
make-libD-* 将创建静态库,C运行时库为多线程DLL
图1.4
每个目录下,又有多个子目录,如下图所示。如:使用vc6编译请进入vc6目录。
图1.5
1.5 复制文件树
W:\mpir\v2.7.0\mpir\build.vc10\mpir.sln里每个项目都有文件树。如下图所示。现在要把这些项目的文件树复制到上一节新建的项目里。
图1.6
可以手工添加,只不过五百多个文件添加起来还是很繁琐的。可以使用vcHelper来完成此项繁琐的工作。如下图所示,vcHelper把W:\mpir\v2.7.0\mpir\build.vc10\dll_mpir_gc\dll_mpir_gc.vcxproj里的文件树复制到W:\mpir\v2.7.0\vc\make-dll-c\vc6\mpir.dsp。
图1.7
需要复制的文件树如下表所示:
从 W:\mpir\v2.7.0\mpir\build.vc10\dll_mpir_gc\dll_mpir_gc.vcxproj 复制到 W:\mpir\v2.7.0\vc\make-dll-c\vc6\mpir.dsp W:\mpir\v2.7.0\vc\make-libD-c\vc6\mpirD.dsp W:\mpir\v2.7.0\vc\make-libS-c\vc6\mpirS.dsp W:\mpir\v2.7.0\vc\make-libT-c\vc6\mpirT.dsp |
从 W:\mpir\v2.7.0\mpir\build.vc10\dll_mpir_p3\dll_mpir_p3.vcxproj 复制到 W:\mpir\v2.7.0\vc\make-dll-asm32\vc6\mpir.dsp W:\mpir\v2.7.0\vc\make-libD-asm32\vc6\mpirD.dsp W:\mpir\v2.7.0\vc\make-libS-asm32\vc6\mpirS.dsp W:\mpir\v2.7.0\vc\make-libT-asm32\vc6\mpirT.dsp |
从 W:\mpir\v2.7.0\mpir\build.vc10\dll_mpir_core2\dll_mpir_core2.vcxproj 复制到 W:\mpir\v2.7.0\vc\make-dll-asm64\vc2008\mpir.dsp W:\mpir\v2.7.0\vc\make-libD-asm64\vc2008\mpirD.dsp W:\mpir\v2.7.0\vc\make-libS-asm64\vc2008\mpirS.dsp W:\mpir\v2.7.0\vc\make-libT-asm64\vc2008\mpirT.dsp |
vcHelper的下载:访问http://pan.baidu.com/s/1gd7XDkf 然后进入public\Tools\vcHelper下载压缩包。
1.6 不使用预编译头文件
设置VC++,编译时不使用预编译头文件。
VC++6.0的设置如下图所示:
图1.8
VC++.NET的设置如下图所示:
图1.9
1.7 包含目录
编译.c文件时,需要文件W:\mpir\v2.7.0\mpir\mpir.h。该文件相对于VC++6.0项目文件(W:\mpir\v2.7.0\vc\make-dll-c\vc6\mpir.dsp)的相对路径为../../../mpir
VC++6.0的具体配置如下:
图1.10
VC++.NET的配置如下
图1.11
1.8 定义宏
需要定义宏:HAVE_CONFIG_H,不过它似乎没有什么实质作用。
编译生成动态库,还需要定义宏MSC_BUILD_DLL。它用来导出mpir的API函数。
VC++6.0的配置如下:
图1.12
VC++.NET的配置如下。vc2002~vc2008不能选择All Configuration,vc2010后可以选择All Configuration了。
图1.13
1.9 编译前事件
编译前,需要执行"编译前事件"。下图是VC++.NET的配置:
图1.14
上图表示编译前将执行两条命令:
"cd ..\..\..\mpir\build.vc10"表示设置..\..\..\mpir\build.vc10为当前目录。这是相对于W:\mpir\v2.7.0\vc\make-dll-c\vc6\mpir.dsp的目录,换成绝对目录就是W:\mpir\v2.7.0\mpir\build.vc10
"prebuild gc Win32"表示运行当前目录下的prebuild.bat文件,也就是运行W:\mpir\v2.7.0\mpir\build.vc10\prebuild.bat。gc和Win32是传递给prebuild.bat的两个参数。第一个参数有三个选项:gc、p3、core2,分别表示不使用汇编代码、使用32位汇编代码、使用64位汇编代码。第二个参数有两个选项:Win32、x64,分别表示32位、64位平台。
VC++6.0不支持"编译前事件",可以设置gmp-h.in文件的Custom Build,如下图所示:
图1.15
编译时,只要gmp-h.in是第一个被编译的,那么"cd ..\..\..\mpir\build.vc10"和"prebuild gc Win32"就会被执行,相当于"编译前事件"。
prebuild.bat的内容有些复杂,笔者并没有完全理解,只知道"prebuild.bat gc Win32"执行后将在W:\mpir\v2.7.0\mpir目录下生成五个文件:config.h、gmp.h、gmp-mparam.h、longlong.h、mpir.h。这些都是编译时需要的文件。
1.10 修改 obj 的位置
mpf、mpn、mpq、mpz下有很多重名的.c文件,编译时它们将生成重名的.obj文件。这些重名的.obj文件不能放在同一目录下,因此需要修改mpf、mpn、mpq、mpz这四个目录下所有.c文件生成的.obj文件的存放目录。
对于VC++6.0,设置如下图所示。mpf目录下的.c文件生成的.obj文件,原存放目录为 Temp\DU,现存放目录为Temp\DU\mpf。
图1.16
对于VC++.NET,设置如下图所示。mpf目录下的.c文件生成的.obj文件,原存放目录为$(IntDir)\,现存放目录为$(IntDir)\mpf\。注意:$(IntDir)\mpf\最后的\一定不能少,因为$(IntDir)\mpf会被当做文件,而$(IntDir)\mpf\才会被当做目录。
图1.17
根据上面两张图的配置,再修改mpn、mpq、mpz目录下.c文件的配置。
1.11 编译yasm
不需要编译汇编代码的,请跳过本节。
编译汇编代码需要yasm。mpir 2.7.0自带了yasm源代码,如下所示:
W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc9\yasm.sln 使用vc2008编译
W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc10\yasm.sln 使用vc2010编译
如果需要最新版本的yasm,可到如下网址下载:
http://www.tortall.net/projects/yasm/
http://yasm.tortall.net/
使用vc2010打开W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc10\yasm.sln。选择Win32 Release,然后鼠标右键单击"vsyasm"在弹出菜单中单击【Rebuild】。如下图所示。
编译时将会执行Python脚本文件W:\mpir\v2.7.0\mpir\yasm\modules\arch\x86\gen_x86_insn.py。笔者不清楚该脚本做了哪些工作,经测试发现不执行该脚本也不会影响最终exe文件的生成。
图1.18
编译生成的文件是W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc10\Win32\Release\vsyasm.exe。VC++可以调用该程序编译汇编代码。
1.12 编译汇编代码
不需要编译汇编代码的,请跳过本节。
VC++调用yasm编译汇编代码有三种方法:Custom Build、.rules文件、.targets文件。
1.12.1 Custom Build
Custom Build的优点是适用面广,vc6~vc2015都能使用;缺点是修改命令行参数稍显麻烦。
下图是mpn\yasm\*.asm的Custom Build配置:
图1.19
Outputs是编译*.asm后的输出文件,连接时会用到此文件。上图的设置为"$(IntDir)\$(InputName).obj"其中$(IntDir)是编译时的临时目录,$(InputName)是编译.asm文件名,如:编译add_n.asm时,$(InputName)就是add_n。
Commands是vsyasm.exe的命令行,具体如下:
..\..\..\mpir\yasm\Mkfiles\vc10\Win32\Release\vsyasm.exe -Xvc -f Win32 -g cv8 -d "DLL" -o "$(IntDir)\\" -rnasm -pnasm $(InputPath) |
..\..\..\mpir\yasm\Mkfiles\vc10\Win32\Release\vsyasm.exe是W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc10\Win32\Release\vsyasm.exe相对于W:\mpir\v2.7.0\vc\make-dll-asm32\vc6\mpir.dsp的相对路径。
-Xvc 是错误信息显示格式。对于gcc编译器可设置为 -Xgcc 或 -Xgnu。
-f Win32 表明是32位平台,对于64位平台可指定为-f x64
-g cv8 用来生成调试信息,这种调试信息不能被 vc6 识别,所以使用vc6编译.asm文件时,请去除该选项。
-o "$(IntDir)\\"表示将在目录$(IntDir)里生成.obj文件。注意$(IntDir)后面的两个反斜杠一个都不能少。
$(InputPath)表示输入文件,如:add_n.asm、addmul_1.asm……
这里需要说明一下vc6里Custom Build的编译顺序。因为vc6不支持"编译前事件",可使用Custom Build代替"编译前事件",此时编译顺序就显得非常重要了。笔者经实验得到以下规律(并不能确保它一定正确):
1、Custom Build比.c或.cpp的编译要早;
2、在IDE里先后设置文件A、B的Custom Build,则编译时的顺序为逆序,即先编译B再编译A;
3、初次打开.dsp文件,编译顺序是文件在.dsp里的相反顺序。如:.dsp文件里从上到下依次有A、B需要Custom Build,那么编译时先编译B再编译A。
在图1.15中,gmp-h.in在.dsp文件里是最后一个文件,因此Custom Build时它总是第一个被编译。这样,就保证了它扮演"编译前事件"的角色。
1.12.2 .rules文件
vc2005和vc2008可以使用. rules文件。使用vc2008打开W:\mpir\v2.7.0\vc\make-dll-asm32\vc2008\mpir.sln,鼠标右键单击mpir项目,弹出菜单中单击【Custom Build Rules...】菜单项
图1.20
弹出如下界面,请单击"Find Existing..."按钮。
图1.21
请载入W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc9\yasm.rules文件,如下图所示:
图1.22
下图所示界面,请单击"否"按钮。
图1.23
下图所示界面内,请勾选"Yasm",然后单击"OK"按钮。
图1.24
此时查看mpir项目属性,可以看到配置里多了一项"Yasm Assembler",可以在这里对Yasm的命令行参数进行配置。如下图所示:
图1.25
再查看某个.asm文件的属性,可以看到它被自动关联到Yasm Assembler,也就是说编译.asm文件时将使用Yasm Assembler来编译。如下图所示:
图1.26
1.12.3 .targets文件
从vc2010开始. rules文件被.targets文件代替。使用vc2010打开W:\mpir\v2.7.0\vc\make-dll-asm32\vc2010\mpir.sln,鼠标右键单击mpir项目,弹出菜单中单击【Build Customization...】菜单项
图1.27
同样是单击"Find Existing..."按钮
图1.28
请载入W:\mpir\v2.7.0\mpir\yasm\Mkfiles\vc10\vsyasm.targets文件,如下图所示:
图1.29
下图所示界面,请单击"否"按钮。
图1.30
下图所示界面内,请勾选"vsyasm",然后单击"OK"按钮。
图1.31
注意:Yasm Assembler不会自动关联*.asm,需要将*.asm的Item Type属性更改为Yasm Assembler。
图1.32
1.13 替换long long
VC++6.0不支持long long,可用__int64替换它。具体如下:
1、替换 gmp-impl.h 里的 long long int 为 __int64;
修改后的两行代码如下:
typedef __int64 DItype; typedef unsigned __int64 UDItype; |
2、替换printf\doprnt.c、printf\repl-vsnprintf.c、scanf\doscan.c里的long long 为 __int64。
1.14 strnlen函数
vc6、vc2002、vc2003不支持strnlen函数,所以需要修改代码。具体就是修改printf\repl-vsnprintf.c
修改前 | 修改后 |
#if ! HAVE_STRNLEN static size_t strnlen (const char *s, size_t n) { size_t i; for (i = 0; i < n; i++) if (s[i] == '\0') break; return i; } #endif | #if _MSC_VER <= 1310 static size_t strnlen(const char*str ,size_t num) { size_t n = 0; if(str && num) { while(str[n]) { if(++n >= num) { break; } } } return n; } #endif//#if _MSC_VER <= 1310 |
vc6、vc2002、vc2003的_MSC_VER分别为1200、1300、1310。从vc2005开始,VC++实现了strnlen函数。