目录
1.2 修改#include "*.c" 为 #include "*.inl" 2
第1章 VC++
本章将以gsl-1.16为例,说明如何使用VC++编译GSL。首先是解压gsl-1.16.tar.gz,这里假定解压至G:\gsl-1.16\src,其目录结构如下图所示:
图1.1
1.1 修改行结束符
GSL源代码的行结束符是回车(0DH),在Windows下最好更换为回车、换行(0DH、0AH)。当然,不修改行结束符并不影响GSL的编译。
Windows资源管理器里,进入G:\gsl-1.16\src搜索*.c和*.h(图1.2所示),然后把这些文件拖放到程序vcHelper的"编码与换行"页面(图1.3所示)。vcHelper的下载方法:进入网盘 http://pan.baidu.com/s/1gd7XDkf 再进入 public\Tools\vcHelper 下载压缩包。
图1.2
图1.3
上图中的"G:\gsl-1.16\src\blas\blas.c""G:\gsl-1.16\src\blas\gsl_blas.h"……表示这些文件被改写。
1.2 修改#include "*.c" 为 #include "*.inl"
GSL里有一些*.c文件比较特殊:编译器并不直接编译它,而是某些c文件(宿主文件)嵌入了其内容,当编译宿主文件时这类文件才发生作用。
举例说明,G:\gsl-1.16\block\block.c的部分内容如下:
#define BASE_DOUBLE #include "templates_on.h" #include "block_source.c" #include "templates_off.h" #undef BASE_DOUBLE
#define BASE_FLOAT #include "templates_on.h" #include "block_source.c" #include "templates_off.h" #undef BASE_FLOAT |
可以看到:block_source.c被多次嵌入到block.c里。编译器并不需要直接编译block_source.c,只是在编译block.c时block_source.c才发挥作用。
GSL这么做的目的是为了节省人工编写代码的工作量,上述代码的用意为:针对double编译一次block_source.c,再针对float编译一次block_source.c……这个非常类似于C++的模板。
现在的问题是:对于一个并不想深入研究GSL代码的程序员而言,他根本无法区分block_source.c和block.c:同样的*.c文件,如何确定哪个文件应该编译,哪个文件不应该编译?解决方法就是重命名:把#include "block_source.c"修改为#include "block_source.inl",同时重命名block_source.c为block_source.inl。
运行VS2008,打开"查找和替换"对话框,并按下图进行配置,然后单击"查找全部"按钮。
图1.4
查找结果如下表所示。
查找全部 "include*.c", 大小写匹配, 通配符, 子文件夹, …… G:\gsl-1.16\src\block\block.c(7):#include "block_source.c" G:\gsl-1.16\src\block\block.c(13):#include "block_source.c" …… …… …… …… …… …… G:\gsl-1.16\src\vector\view.c(174):#include "view_source.c" 匹配行: 956 匹配文件: 161 合计搜索文件: 1344 |
通过上表可知共有956个匹配项,手动修改的话工作量会比较大,而且容易出错。因此,可以通过编写程序来实现修改,具体而言就是:替换G:\gsl-1.16\src\block\block.c里的第7行代码#include "block_source.c"为#include "block_source.inl",同时重命名G:\gsl-1.16\src\block\block_source.c为block_source.inl。上表中的956项需要逐一进行修改。
运行vcHelper程序,然后把上表内容复制到"#include"*.c""页面的文本框内,最后单击"应用"按钮,完成相应的更改工作。如下图所示:
图1.5
1.3 重命名重复的 *.c 文件
GSL的源代码文件(*.c)存在同名的现象,如下图所示:在G:\gsl-1.16\src目录查找*.c,将会发现有多个inline.c文件。
图1.6
VC++编译时不允许源代码文件重名,因此需要将这些重名的文件改名。手工改名工作量较大,且容易出错,可以编写程序去完成此项工作。
vcHelper程序已经具有此项功能,具体操作为:将上图所有的*.c文件拖放至下图所示的主界面即可。
图1.7
上图的G:\gsl-1.16\src\cdf\beta.c==>beta_cdf.c表示将G:\gsl-1.16\src\cdf\beta.c更名为beta_cdf.c。
在图1.6中的几个inline.c文件被重命名为如下文件:
inline_combination.c inline_complex.c inline_interpolation.c inline_multiset.c inline_permutation.c inline_qrng.c inline_rng.c |
inline_combination.c表示它在combination目录,原名为inline.c。如果inline_combination.c这个名称也已经存在,则会重命名inline.c为inline_combination#.c(#号是从1开始编号的整数)。
1.4 声明文件与实现文件分离
GSL接口函数的声明文件为gsl_*.h,它们与实现代码(*.c)混在一起。本节将把声明文件和实现文件分隔开来,具体操作如下:
1、在G:\gsl-1.16目录下新建inc目录;
2、搜索G:\gsl-1.16\src目录下的gsl_*.h,把这些文件移至G:\gsl-1.16\inc;
3、在G:\gsl-1.16\src目录下复制、粘贴config.h.in文件,然后重命名为config.h。可以使用vcHelper程序修改config.h的行结束符。
1.5 修改#include "*.h" 为相对路径
声明文件与实现文件分离后,以前的代码#include <gsl/gsl_math.h>可能就无法正常编译了,为此需要修改#include <gsl/gsl_math.h>为#include "../../inc/gsl_math.h",即包含文件时采用相对路径。
使用"VC编程助手"程序完成此项工作。程序运行界面如下图所示:
图1.8
上图中,首先单击"扫描"按钮。扫描后会显示"扫描1345/1752",它表示G:\gsl-1.16里共有1752个文件。其中1345个文件的扩展名为(.c;.cc;.cpp;.cxx;.h;.hh;.hpp;.hxx;.inc;.inl),这些文件就是准备修改的文件。单击"自动修改"按钮即可完成#include语句的修改。
下面是src\complex\math.c文件在执行"VC编程助手"程序前后的变化。
#include <gsl/gsl_math.h> #include <gsl/gsl_complex.h> #include <gsl/gsl_complex_math.h> |
#include "../../inc/gsl_math.h" #include "../../inc/gsl_complex.h" #include "../../inc/gsl_complex_math.h" |
1.6 新建VC项目
1.6.1 目录结构
新建四个VC项目。其目录结构如下图所示:
图1.9
make-dll 用于生成GSL动态库
make-libS 用于生成GSL静态库(C运行时库为单线程)
make-libT 用于生成GSL静态库(C运行时库为多线程)
make-libD 用于生成GSL静态库(C运行时库为多线程DLL)
每个make-???目录下又有若干子目录,如下图所示。make-dll目录下有vc6、vc2002、vc2003……它们分别表示使用VC++6.0、VC++.NET、VC++2003……编译GSL。
图1.10
编译生成的成果文件存放在图1.9所示的bin目录下,如下图所示。vc6-win32-RA表示编译器是vc6,平台是win32、RA表示Release Ansi版本。vc2008-Pocket PC 2003 (ARMV4)-RU表示编译器是vc2008,平台是Pocket PC 2003 (ARMV4),RU表示Release Unicode版本。
图1.11
bin\vc6-win32-RA目录下的文件如下图所示:
GSL.dll、GSL.lib 动态库及其导入库
GSLS.lib 静态库(C运行时库为单线程)
GSLT.lib 静态库(C运行时库为多线程)
GSLD.lib 静态库(C运行时库为多线程DLL)
图1.12
1.6.2 添加源文件
把G:\gsl-1.16目录下一千多个文件(*.c;*.h;*.inl)添加到VC项目里,这是一项艰巨的工程。这里使用"VC 编程助手"来添加,如下图所示:
图1.13
"生成文件树"里的文本框输入G:\gsl-1.16\src,然后单击"生成文件树"按钮,即可生成文件树。上图可以看到:生成的文件树里有1126个文件。
拖动G:\gsl-1.16\make-dll、G:\gsl-1.16\make-libS、G:\gsl-1.16\make-libT、G:\gsl-1.16\make-libD目录到"替换文件树"里的文本框,则"VC 编程助手"会自动找到这些目录里的VC项目文件(*.dsp;*.vcproj;*.vcxproj)。单击"替换文件树"按钮,则"VC 编程助手"会把VC项目文件里的文件树替换为生成的文件树。如下图所示:
图1.14
注意:有些*.c文件是测试代码(内含main函数),这些文件需要从VC项目里移除。具体如下:
1、test*.c需要移除;
2、doc目录需要移除;
3、gsl-histogram.c、gsl-randist.c需要移除。
1.7 预编译头文件
config.h文件非常重要,它里面有一些非常重要的宏。*.c文件为了获得这些宏定义,几乎都包含了config.h。
使用预编译头文件的目的有两个:一是确保所有的*.c文件都能包含config.h。事实上complex\inline.c、combination\inline.c、multiset\inline.c因为没有包含config.h,编译时就会有些问题;二是提高编译的速度。
1.7.1 创建文件
在G:\gsl-1.16\src下新建文件StdAfx.c,其内容如下:
#include "StdAfx.h" |
在G:\gsl-1.16\src下新建文件StdAfx.h,其内容如下:
#pragma once
#define _CRT_NON_CONFORMING_SWPRINTFS #define _CRT_SECURE_NO_WARNINGS
#if defined(_LIB) //编译生成静态库 #define GSLLib #else //编译生成动态库 #define GSLLib __declspec(dllexport) #endif
//是否使用内联函数 #define USE_INLINE 1
#include <MATH.H> #include <STDLIB.H> #include "config.h" |
1.7.2 配置VC项目
把这两个文件增加到VC项目里,然后配置项目属性:
图1.15
接着配置StdAfx.c的属性:
图1.16
也就是说:StdAfx.c用来生成预编译头文件(*.pch),其它的*.c文件使用这个预编译头文件。
1.7.3 修改*.c文件
现在替换*.c文件里的#include*config.h为#include "StdAfx.h。使用VS2008的"在文件中替换"功能,界面如下:
图1.17
上图中,单击"全部替换"按钮,完成替换工作。
还需要手工完成如下工作:
1、complex\inline.c、combination\inline.c、multiset\inline.c、utils\placeholder.c,G:\gsl-1.16\src\cblas目录下的*.c,需要将#include "StdAfx.h"增加至第一行;
2、G:\gsl-1.16\src\cdf\binomial_cdf.c里有两个#include "StdAfx.h",请删除第二个。
1.7.4 修改*.inl文件
现在替换*.inl文件里的#include*config.h"为空。使用VS2008的"在文件中替换"功能。
图1.18
1.8 修改文件
1.8.1 gsl_types.h
修改inc\gsl_types.h,使其内容如下
#ifndef __GSL_TYPES_H__ #define __GSL_TYPES_H__ #ifndef GSL_VAR #define GSL_VAR extern GSLLib #endif #endif |
1.8.2 gsl_inline.h
修改inc\gsl_inline.h,使其内容如下
#ifndef __GSL_INLINE_H__ #define __GSL_INLINE_H__ #ifndef COMPILE_INLINE_STATIC #ifdef HAVE_INLINE //启用内联 #if defined(USE_INLINE) && defined(_LIB) //编译生成 GSL 静态库 #define INLINE_DECL static inline #define INLINE_FUN static inline #else #define INLINE_DECL GSLLib inline #define INLINE_FUN GSLLib inline #endif #else //禁用内联 #define INLINE_DECL GSLLib #define INLINE_FUN GSLLib #endif #define GSL_RANGE_COND(x) (x) #endif #endif /* __GSL_INLINE_H__ */ |
1.8.3 build.h
修改src\build.h,使其内容如下
#pragma once #undef INLINE_DECL #undef INLINE_FUN #ifdef COMPILE_INLINE_STATIC #undef __GSL_INLINE_H__ #define __GSL_INLINE_H__ //防止再次包含gsl_inline.h #undef GSL_RANGE_CHECK #define GSL_RANGE_CHECK 1 #undef GSL_RANGE_COND #define GSL_RANGE_COND(x) (gsl_check_range && (x)) #ifdef HAVE_INLINE //使用内联 #if defined(USE_INLINE) && defined(_LIB) //编译生成 GSL 静态库 #define INLINE_DECL #define INLINE_FUN #else #define INLINE_DECL GSLLib inline #define INLINE_FUN GSLLib inline #endif #else //不使用内联 #define INLINE_DECL GSLLib #define INLINE_FUN GSLLib #define HAVE_INLINE #endif #endif |
1.8.4 config.h
请修改src\config.h
1、删除下面四段代码
#undef HAVE_C99_INLINE |
#undef HAVE_INLINE |
#undef HIDE_INLINE_STATIC |
#ifndef __cplusplus #undef inline #endif |
2、将下面一段代码增加到config.h的最顶端
#pragma once
#if USE_INLINE //启用内联 #define HAVE_C99_INLINE #define HAVE_INLINE #define HIDE_INLINE_STATIC #ifndef __cplusplus #define inline __inline #endif #else //禁用内联 #undef HAVE_C99_INLINE #undef HAVE_INLINE #undef HIDE_INLINE_STATIC #ifndef __cplusplus #define inline #endif #endif |
3、修改代码
修改前 | 修改后 |
#undef HAVE_DECL_FREXP | #define HAVE_DECL_FREXP 1 |
#undef HAVE_DECL_HYPOT | #define HAVE_DECL_HYPOT 1 |
#undef HAVE_DECL_LDEXP | #define HAVE_DECL_LDEXP 1 |
不修改这些代码,则编译时会出错。如:#undef HAVE_DECL_HYPOT时,编译sys\fcmp.c就会出错。
fcmp.c的主要代码如下:
#include "../config.h" #include "../../../inc/gsl_sys.h" #include <math.h> |
在config.h里,有如下代码。此时,hypot被映射为gsl_hypot。
#if !HAVE_DECL_HYPOT #define hypot gsl_hypot #endif |
接下来会处理math.h里的如下代码
_CRTIMP double __cdecl hypot(double, double); |
因为hypot是一个宏,结果就变成了如下代码:
_CRTIMP double __cdecl gsl_hypot(double, double); |
上面是函数gsl_hypot的声明,它与gsl_sys.h里的gsl_hypot函数声明(下表所示)冲突。导致程序编译失败。
double gsl_hypot (const double x, const double y); |
1.8.5 bspline\bspline.h
将#pragma once增加为G:\gsl-1.16\src\bspline\bspline.h的第一行。否则编译时会出错。
1.9 模块定义文件
生成GSL动态库时需要导出一些变量和函数,为此需要模块定义文件。请在G:\gsl-1.16\src目录新建文件GSL.def。然后输入如下内容
EXPORTS gsl_check_range DATA gsl_interp_akima DATA …… GSL_MAX_DBL GSL_MAX_INT GSL_MIN_DBL GSL_MIN_INT cblas_caxpy cblas_ccopy …… |
说明:
1、gsl_check_range、gsl_interp_akima……是导出的变量;
2、GSL_*、cblas_*、gsl_*这些都是导出函数。
v16.0的GSL有145个导出变量,4199个导出函数。如何快速获取这些变量和函数的名称?
运行vc2010,新建一个项目,把gsl_*.h添加到该项目。在Class View下,可以查看"Global Functions and Variables",如下图所示。可以把这些函数和变量复制出来。
图1.19
复制出来的名称以空格分隔,可以使用Word把空格替换为" DATA^p"(导出变量,^p表示回车换行)或"^p"(导出函数,^p表示回车换行)。注意:请禁用Word的行首字母大写功能。
下图是在Word 2003 里,替换所有的空格为" DATA^p"
图1.20
替换结果如下图所示:
图1.21
这样的内容,是可以直接复制粘贴到def文件里的。
注意:导出的名称可能会有重复的。可使用"VC编程助手"的"排序合并"功能进行排序并剔除重复项,如下图所示:
图1.22
使用VC++6.0编译生成的GSL.dll,其导出符号如下图所示:
图1.23
类似"??_C@_0DA@IJFJ@g?3?2?"这样的符号,是什么呢?这些是内联函数里的字符串。下面的代码可以获得??_C@_0DA代表的字符串:
HMODULE hMod = GetModuleHandle(_T("GSL")); FARPROC pfn = GetProcAddress(hMod,"??_C@_0DA"); const char* s = (const char*)pfn; |
使用vc2008编译生成GSL动态库时,不再导出这些内联函数里的字符串。
1.10 编译GSL
GSL的接口有两类:接口变量和接口函数。
gsl_version就是一个接口变量,客户端程序可以直接使用此变量。
接口函数分为两类,一类是普通的接口函数,另一类是准内联接口函数。什么是准内联函数呢?就是它可以内联也可以不内联。
1.10.1 接口变量
接口变量的声明一般在头文件里,如:gsl_version的声明就在gsl_version.h里
GSL_VAR const char * gsl_version; |
接口变量的初始化一般在*.c文件里,如:gsl_version的初始化就在gsl_version.c里
const char * gsl_version = GSL_VERSION; |
这里要注意宏GSL_VAR。在gsl_types.h里,它被定义为extern GSLLib
当生成GSL动态库时,GSLLib是__declspec(dllexport),此时gsl_version的声明就是:
extern __declspec(dllexport) const char * gsl_version; |
__declspec(dllexport)把变量gsl_version给导出了。还有一种办法导出接口变量,那就是在模块定义文件(*.DEF)里增加如下语句:
gsl_version DATA |
当生成GSL静态库时,GSLLib是空,此时gsl_version的声明就是:
extern const char * gsl_version; |
也就是说生成GSL静态库时,接口变量就是全局变量。
客户端程序连接GSL动态库时,接口变量的声明如下:
extern __declspec(dllimport) const char * gsl_version; |
客户端程序连接GSL静态库时,接口变量的声明如下:
extern const char * gsl_version; |
1.10.2 普通接口函数
普通接口函数的声明一般在头文件里,如:函数gsl_complex_polar的声明就在gsl_complex_math.h里。
gsl_complex gsl_complex_polar(double r, double theta); |
普通接口函数的实现一般在*.c文件里,如:complex\math.c 里有函数gsl_complex_polar的实现代码:
gsl_complex gsl_complex_polar (double r, double theta) {/* return z = r exp(i theta) */ gsl_complex z; GSL_SET_COMPLEX (&z, r * cos (theta), r * sin (theta)); return z; } |
当生成GSL动态库时,在模块定义文件(GSL.def)里增加如下语句即可导出gsl_complex_polar函数。
gsl_complex_polar |
其实更好的做法是修改gsl_complex_polar的声明、实现代码如下
GSLLib gsl_complex gsl_complex_polar(double r, double theta); |
GSLLib gsl_complex gsl_complex_polar (double r, double theta) {/* return z = r exp(i theta) */ gsl_complex z; GSL_SET_COMPLEX (&z, r * cos (theta), r * sin (theta)); return z; } |
当生成GSL动态库时,GSLLib是__declspec(dllexport)。当生成GSL静态库时,GSLLib是空。当客户程序连接GSL动态库时,GSLLib是__declspec(dllimport)(这点尤为重要,根据MSDN文档介绍这样会提高调用导出函数的效率)。当客户程序连接GSL静态库时,GSLLib是空。
1.10.3 准内联接口函数
准内联接口函数的声明及内联代码一般在头文件里,如:gsl_complex_math.h里有gsl_complex_rect的声明和内联代码,如下所示。
INLINE_DECL gsl_complex gsl_complex_rect (double x, double y); |
#ifdef HAVE_INLINE INLINE_FUN gsl_complex gsl_complex_rect (double x, double y) {/* return z = x + i y */ gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } #endif |
准内联接口函数的外部代码一般在*.c文件里,如:complex\inline.c的文件内容:
#define COMPILE_INLINE_STATIC #include "../build.h" #include "../../inc/gsl_complex_math.h" |
通过COMPILE_INLINE_STATIC和build.h,可把gsl_complex_math.h里的内联代码变成外部代码。
1.10.3.1 启用内联
在StdAfx.h里,修改宏USE_INLINE的定义即可控制准内联函数是否内联:
#define USE_INLINE 1 |
将USE_INLINE定义为1,那么宏 HAVE_INLINE 将被定义,如下所示(config.h里的代码):
#if USE_INLINE //启用内联 #define HAVE_C99_INLINE #define HAVE_INLINE #define HIDE_INLINE_STATIC #ifndef __cplusplus #define inline __inline #endif #else //禁用内联 #undef HAVE_C99_INLINE #undef HAVE_INLINE #undef HIDE_INLINE_STATIC #ifndef __cplusplus #define inline #endif #endif |
接下来查看gsl_inline.h里的宏定义
#ifdef HAVE_INLINE //启用内联 #if defined(USE_INLINE) && defined(_LIB) //编译生成 GSL 静态库 #define INLINE_DECL static inline #define INLINE_FUN static inline #else #define INLINE_DECL GSLLib inline #define INLINE_FUN GSLLib inline #endif #else //禁用内联 |
有了INLINE_DECL和INLINE_FUN的定义,就可展开gsl_complex_math.h里gsl_complex_rect的声明和内联代码。
一、编译生成GSL静态库
gsl_complex_math.h里gsl_complex_rect的声明和内联代码如下:
static __inline gsl_complex gsl_complex_rect(double x, double y); |
static __inline gsl_complex gsl_complex_rect(double x, double y) { gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } |
complex\inline.c里gsl_complex_rect的声明和实现代码又是什么情况呢?下面是complex\inline.c的文件内容:
#define COMPILE_INLINE_STATIC #include "../build.h" #include "../../inc/gsl_complex_math.h" |
关键在于build.h,其重要代码如下:
#ifdef HAVE_INLINE //使用内联 #if defined(USE_INLINE) && defined(_LIB) //编译生成 GSL 静态库 #define INLINE_DECL #define INLINE_FUN #else #define INLINE_DECL GSLLib inline #define INLINE_FUN GSLLib inline #endif #else //不使用内联 |
也就是说在complex\inline.c里,gsl_complex_rect的声明、实现代码如下。此时的gsl_complex_rect是一个外部函数。
gsl_complex gsl_complex_rect(double x, double y); |
gsl_complex gsl_complex_rect(double x, double y) { gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } |
编译GSL时,需要分两种情况讨论:
1、内联功能被启用时,如:编译Release版GSL静态库。此时gsl_complex_rect有两个版本:外部版本和内联版本。外部版本就是complex\inline.c里的版本;内联版本就是被编译器展开的版本。
2、内联功能被禁用时,如:编译Debug版GSL静态库。此时gsl_complex_rect也有两个版本:外部版本和外联版本。外部版本就是complex\inline.c里的版本;外联版本就是gsl_complex_rect函数没有被内联展开,而是编译成了函数。为防止外联版本与外部版本名称冲突,在__inline前增加了static关键字。
也就是说,不论gsl_complex_rect是否内联,GSL静态库中一定会有gsl_complex_rect的外部版本。这是为客户程序调用而准备的。
二、编译生成GSL动态库或客户程序调用GSL库时
gsl_complex_math.h里gsl_complex_rect的声明和内联代码如下。注意:complex\inline.c里也有一份同样的代码。
GSLLib __inline gsl_complex gsl_complex_rect(double x, double y); |
GSLLib __inline gsl_complex gsl_complex_rect(double x, double y) { gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } |
注意上面的GSLLib:
1、当编译GSL生成动态库时,GSLLib是__declspec(dllexport);
2、当客户程序调用GSL动态库时,GSLLib为__declspec(dllimport);
3、当客户程序调用GSL静态库时,GSLLib为空。此时,以Release版编译客户程序(内联被启用),客户程序对gsl_complex_rect的调用将使用内联版本;以Debug版编译客户程序(内联被禁用),客户程序对gsl_complex_rect的调用将使用外联版本,这时连接的其实是GSL静态库里的外部版本。
1.10.3.2 禁用内联
将USE_INLINE定义为0,那么宏 HAVE_INLINE 就不会被定义。
gsl_complex_math.h里gsl_complex_rect的声明代码如下:
GSLLib gsl_complex gsl_complex_rect(double x, double y); |
gsl_complex_math.h里gsl_complex_rect的内联代码被禁用了。
complex\inline.c里,gsl_complex_rect的声明、实现代码如下:
GSLLib gsl_complex gsl_complex_rect(double x, double y); |
GSLLib gsl_complex gsl_complex_rect(double x, double y) { gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } |
也就是说:禁用内联后,准内联接口函数就变成了普通接口函数了。
还有一点需要说明:#include "../build.h"之后再#include "../../inc/gsl_complex_math.h",则gsl_complex_math.h里的#include "gsl_inline.h"将失效。因为build.h里定义了#define __GSL_INLINE_H__,防止再次包含gsl_inline.h,破坏INLINE_DECL和INLINE_FUN的定义。其实更好的作法是在gsl_inline.h里增加对宏COMPILE_INLINE_STATIC是否定义的判断,如下所示:
#ifndef COMPILE_INLINE_STATIC #ifdef HAVE_INLINE //启用内联 #if defined(USE_INLINE) && defined(_LIB) //编译生成 GSL 静态库 #define INLINE_DECL static inline #define INLINE_FUN static inline #else #define INLINE_DECL GSLLib inline #define INLINE_FUN GSLLib inline #endif #else //禁用内联 #define INLINE_DECL GSLLib #define INLINE_FUN GSLLib #endif #define GSL_RANGE_COND(x) (x) #endif |
1.11 客户程序调用
为方便客户程序调用GSL库,请在G:\gsl-1.16\inc下创建如下文件:_Inc.h、_Inc.inl、_IncInline.h、_IncInline.inl、_Static.h、_Static.inl、_StaticInline.h、_StaticInline.inl、inc.h。
1.11.1 调用GSL动态库,启用内联
客户程序应该包含如下两个文件:
#include "G:/gsl-1.16/inc/_IncInline.h" #include "G:/gsl-1.16/inc/_IncInline.inl" |
_IncInline.h文件内容如下:
#pragma once #define GSLLib __declspec(dllimport)
#define HAVE_C99_INLINE #define HAVE_INLINE #define HIDE_INLINE_STATIC #ifndef __cplusplus #define inline __inline #endif
#include "inc.h"
#undef HAVE_C99_INLINE #undef HAVE_INLINE #undef HIDE_INLINE_STATIC #ifndef __cplusplus #undef inline #endif |
inc.h文件内容如下(其实就是包含所有的gsl_*.h):
#pragma once #include "gsl_blas.h" ... ... ... #include "gsl_wavelet2d.h" |
_IncInline.inl文件内容如下,其实质就是自动连接GSL.lib。注意:编译客户端程序时必须设置GSL.lib的目录。
#pragma comment(lib,"GSL.lib") |
1.11.2 调用GSL动态库,禁用内联
客户程序应该包含如下两个文件:
#include "G:/gsl-1.16/inc/_Inc.h" #include "G:/gsl-1.16/inc/_Inc.inl" |
_Inc.h文件内容如下:
#pragma once #define GSLLib __declspec(dllimport) #include "inc.h" |
_Inc.inl文件内容如下:
#pragma comment(lib,"GSL.lib") |
1.11.3 调用GSL静态库,启用内联
客户程序应该包含如下两个文件:
#include "G:/gsl-1.16/inc/_StaticInline.h" #include "G:/gsl-1.16/inc/_StaticInline.inl" |
_StaticInline.h文件内容如下:
#pragma once #define GSLLib
#define HAVE_C99_INLINE #define HAVE_INLINE #define HIDE_INLINE_STATIC #ifndef __cplusplus #define inline __inline #endif
#include "inc.h"
#undef HAVE_C99_INLINE #undef HAVE_INLINE #undef HIDE_INLINE_STATIC #ifndef __cplusplus #undef inline #endif |
在#include "inc.h"之前,启用了内联功能,在#include "inc.h"之后取消了inline的宏定义。
_StaticInline.inl文件内容如下,其实质就是根据客户端程序使用的C函数库,自动选择相应的GSL?.lib。注意:编译客户端程序时必须设置GSL?.lib的目录。
#ifdef _MT #ifdef _DLL //使用多线程 DLL 版的 C 函数库 #pragma comment(lib,"GSLD.lib") #else //使用多线程版的 C 函数库 #pragma comment(lib,"GSLT.lib") #endif #else //使用单线程版的 C 函数库 #pragma comment(lib,"GSLS.lib") #endif |
1.11.4 调用GSL静态库,禁用内联
客户程序应该包含如下两个文件:
#include "G:/gsl-1.16/inc/_Static.h" #include "G:/gsl-1.16/inc/_Static.inl" |
_Static.h文件内容如下:
#pragma once #define GSLLib #include "inc.h" |
_Static.inl文件内容与_StaticInline.inl完全相同。
1.12 移植到Windows CE
1.12.1 关于DEBUG
编译PocketPC 2003 Debug 版代码时,将会出现编译错误。原因就是宏DEBUG被定义了。
以ode-initval2\msbdf.c为例进行说明。msbdf_update函数里有如下代码:
if (*nJ == 0 || *nJ > MSBDF_JAC_WAIT || (t == failt && (gammarel < c || hratio < 1.0))) { #ifdef DEBUG printf ("-- evaluate jacobian\n"); #endif int s = GSL_ODEIV_JA_EVAL (sys, t, y, dfdy->data, dfdt); |
因为定义了宏DEBUG,因此变量 s 在初始化前的代码 printf("……")被启用。在VC++的C编译器里,变量的声明必须在执行语句之前。此时,就会出现编译错误。解决方法有两个:一是改代码;二是编译生成Release版。
VC++编译Win32 Debug版GSL时,将定义宏_DEBUG,而不是定义宏DEBUG,因此VC++可以正常编译Win32 Debug版GSL。
1.12.2 关于errno.h
Windows CE的C函数集合不是很完备,缺少一些C函数及头文件,如:缺少errno.h为此需要修改gsl_errno.h和ntuple\ntuple.c中的#include <errno.h>,修改后的代码如下:
#ifndef _WIN32_WCE #include <errno.h> #endif |
也就是编译生成Windows CE代码时,将不再包含errno.h。
1.12.3 其它
增加如下代码至config.h的最后
#ifdef _WIN32_WCE #define hypot _hypot __inline void abort(void) { } __inline char*getenv(const char *varname) { return varname; } #endif |
说明:
1、Windows CE平台没有hypot函数,相应的函数是_hypot;
2、Windows CE平台未实现函数abort和getenv,上述代码只能保证编译通过。