【C】【面向Windows开发者】了解Unix跨平台编程:用autoconf判断你需要的头文件和函数在当前平台是否可用!
Windows下用得最多的开发环境大概是(或者说肯定是)VS了,VS虽然不怎么遵循C语言规范,但这个具有智能提示、自动补全、代码上色功能(作为对比的是记事本、edit.com、vi等),内置了调试器,并且具有可视化的工程配置编辑器(作为对比的是各种makefile)的开发工具还是相当受欢迎的,无论是新手还是老司机都能用得很爽(除了那些连安装都不会的新手),因此诞生了一批又一批不熟悉跨平台编程的Windows大牛。跨平台编程,其实严格意义上来说是跨Unix系的跨平台编程,批命令用的是bash而非batch(前一个发音是“拜史”而另一个是“拜尺”),autoconf会根据你需要用到的头文件来帮你生成一个configure脚本(并且这个脚本是bash的,而不是batch的)。运行这个脚本后,它会判断你需要的函数和头文件在不在,然后给你生成一个头文件,config.h(但你需要先写一个config.h.in)以及其它的文件比如makefile(同样根据你的makefile.in来判断)
实际说一下autoconf的操作吧。假定我们的程序名字叫“foo”,并且它会用到以下几个头文件:
stdio.h
fcntl.h
errno.h
ctype.h
signal.h
unistd.h
stdarg.h
malloc.h
memory.h
inttypes.h
sys/socket.h
arpa/inet.h
netinet/ip.h
首先我们需要写configure.ac,当我们写好以后,autoconf就会自动从当前文件夹读取configure.ac,然后根据它的内容给你生成configure脚本。AC_INIT(,)
AC_LANG(C)
AC_PROG_CC_C99
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_CHECK_HEADERS()
AC_TYPE_SIZE_T
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_FILES()
AC_OUTPUT例子中的第一行表示工程的名字叫“foo”,然后“AC_LANG(C)”指定编程语言为C,使用C编译器,“AC_PROG_CC_C99”表示要求必须要有遵循C99规范的C编译器,其实也可以换成“AC_PROG_CC”,也就是只要是C编译器就行。对于C++,用“AC_PROG_CPP”,参考资料见文末。
后面的AC_CHECK_HEADERS是判断指定的头文件是否存在,是的话,会把判断结果写入到config.h里面。
AC_TYPE_SIZE_T则是判断size_t是否存在,如果不存在,它就会自动根据需求给你定义size_t的类型。
最后面的AC_CONFIG_HEADERS是指定config.h这个文件的名字,你可以改成“cfg.h”,这样configure脚本就会自动把判断结果写入到cfg.h里面,而不是之前说的config.h了,此外对应的,你也要写cfg.h.in,而不是config.h.in。
然后autoconf会根据config.h.in的内容生成config.h,以及根据makefile.in生成makefile。
config.h.in的写法是:把你需要的头文件的文件名转换为大写,把句点和斜杠换成下划线“_”,然后前面加个“#undef ”,其它地方则自己补全你需要的东西就行。
按照本文的例子,你应该像这样写:(虽说注释什么的可以不要,但最好写上,给Windows开发者看)#ifndef _FOO_CONFIG_H_
#define _FOO_CONFIG_H_
/* Define as 1 if you have stdio.h. */
#undef HAVE_STDIO_H
/* Define as 1 if you have fcntl.h. */
#undef HAVE_FCNTL_H
/* Define as 1 if you have errno.h. */
#undef HAVE_ERRNO_H
/* Define as 1 if you have ctype.h. */
#undef HAVE_CTYPE_H
/* Define as 1 if you have signal.h. */
#undef HAVE_SIGNAL_H
/* Define as 1 if you have unistd.h. */
#undef HAVE_UNISTD_H
/* Define as 1 if you have stdarg.h. */
#undef HAVE_STDARG_H
/* Define as 1 if you have malloc.h. */
#undef HAVE_MALLOC_H
/* Define as 1 if you have memory.h. */
#undef HAVE_MEMORY_H
/* Define as 1 if you have inttypes.h. */
#undef HAVE_INTTYPES_H
/* Define as 1 if you have sys/socket.h. */
#undef HAVE_SYS_SOCKET_H
/* Define as 1 if you have arpa/inet.h. */
#undef HAVE_ARPA_INET_H
/* Define as 1 if you have netinet/ip.h. */
#undef HAVE_NETINET_IP_H
#endif这样的话,configure会根据它判断的结果,来帮你定义指定的宏,比如它找到了stdio.h,那么它给你生成的config.h里面,就会有#define HAVE_STDIO_H 1
然后我们写makefile.in,用于让configure生成makefile。因为configure需要判断你用的编译器是啥,然后根据你用的编译器来生成对应的makefile脚本。CC=@CC@
LD=@CC@
CCFLAGS=@CCFLAGS@
foo: foo.o
$(LD) -o $@ $^
.PHONY: clean
clean:
rm -f foo *.o其中的CC=@CC@是指定C编译器为configure找到的C编译器,LD是链接器,C编译器也带链接功能。CCFLAGS是C编译器使用的命令行参数,configure会自动帮你配置。
写好这些以后,我们就可以写我们的主程序了。#include"config.h"
#if HAVE_STDIO_H
#include<stdio.h>
#endif
#if HAVE_FCNTL_H
#include<fcntl.h>
#endif
#if HAVE_ERRNO_H
#include<errno.h>
#endif
#if HAVE_CTYPE_H
#include<ctype.h>
#endif
#if HAVE_SIGNAL_H
#include<signal.h>
#endif
#if HAVE_UNISTD_H
#include<unistd.h>
#endif
#if HAVE_STDARG_H
#include<stdarg.h>
#endif
#if HAVE_MALLOC_H
#include<malloc.h>
#endif
#if HAVE_MEMORY_H
#include<memory.h>
#endif
#if HAVE_INTTYPES_H
#include<inttypes.h>
#endif
#if HAVE_SYS_SOCKET_H
#include<sys/socket.h>
#endif
#if HAVE_ARPA_INET_H
#include<arpa/inet.h>
#endif
#if HAVE_NETINET_IP_H
#include<netinet/ip.h>
#endif
int main()
{
printf("Hello World!\n");
return 0;
}但我们在主程序写这么多#if感觉非常变态,这会让代码变得很难读,重要的内容都在后面了,前面占去了太多行。因此常见的是写一个自己的config配置头文件,这边我就写foocfg.h,像下面这样:#ifndef _FOOCFG_H_
#define _FOOCFG_H_
#include"config.h"
#if HAVE_STDIO_H
#include<stdio.h>
#endif
#if HAVE_FCNTL_H
#include<fcntl.h>
#end if
#if HAVE_ERRNO_H
#include<errno.h>
#endif
#if HAVE_CTYPE_H
#include<ctype.h>
#endif
#if HAVE_SIGNAL_H
#include<signal.h>
#endif
#if HAVE_UNISTD_H
#include<unistd.h>
#endif
#if HAVE_STDARG_H
#include<stdarg.h>
#endif
#if HAVE_MALLOC_H
#include<malloc.h>
#endif
#if HAVE_MEMORY_H
#include<memory.h>
#endif
#if HAVE_INTTYPES_H
#include<inttypes.h>
#endif
#if HAVE_SYS_SOCKET_H
#include<sys/socket.h>
#endif
#if HAVE_ARPA_INET_H
#include<arpa/inet.h>
#endif
#if HAVE_NETINET_IP_H
#include<netinet/ip.h>
#endif
#endif然后我们在主程序源码里面包含它就好:#include"foocfg.h"
int main()
{
printf("Hello World!\n");
return 0;
}这样的话,我们这个示例程序就配置好了。
接下来运行autoconf,然后运行configure,最后运行make,就好了。
示例源码附件下载:
对于不看内容直接搓鼠标滚轮到底部的读者朋友们我这里贴心地提醒一句:
我这篇文章描述的是如何让自己的程序能在不同的平台下运行,编译的时候能自动判断所需的头文件是否存在,并且能自动找到合适的解决方案,得到所需的函数的声明和变量以及结构体的定义等。
知道了内容讲的是啥的话,再回到开始看应该就容易看懂了吧,抱歉我不是特别擅长写教程。
参考资料:
autoconf
https://www.gnu.org/software/autoconf/manual/autoconf.html
AC_PROG_CC等有关语言的检查
https://www.gnu.org/software/autoconf/manual/autoconf-2.60/html_node/C-Compiler.html
检查头文件的方法
https://www.gnu.org/software/autoconf/manual/autoconf-2.66/html_node/Configuration-Headers.html
页:
[1]