背景
有时我们需要用C语言编写一些工具程序,这些工具程序有些内容可以共享,但不方便用函数进行复用。这时可以考虑用代码自动生成技术。本文用一个例子介绍如何通过模板自动生成c语言程序,并调用编译器生成可执行程序,最后执行可执行程序的完整过程。
实现过程
1,用于生成c代码的数据源,json格式的文件 blockStruct.json
[{"student":{"size":260,"elements":{"name":{"type":"char","len":100,"offset":0},"num":{"type":"int","len":0,"offset":100},"age":{"type":"int","len":0,"offset":104},"addr":{"type":"char","len":150,"offset":108}}}},{"teacher":{"size":356,"elements":{"name":{"type":"char","len":100,"offset":0},"num":{"type":"int","len":0,"offset":100},"addr":{"type":"char","len":250,"offset":104}}}}]
2,用于生成c代码的模板文件:fetchStructInfo.c
//自动生成的c代码,用于获取结构体大小,元素偏移量等信息
#include <string.h>
#include <stdio.h>
#include "<?=$structSrc?>"
FILE* fout;
<?php foreach ($structArray as $key=>$value){ ?>
//输出<?=$key?>结构体信息,此处的type的代码:6,1等,要与做数据转换的程序配合一致才行。
void output<?=$key?>Info(){
struct <?=$key?> o;
fprintf(fout,"{");
<?php $i=0;foreach ($value as $k=>$v){ $tag = $i++?',':''; ?>
fprintf(fout,"<?=$tag?>\"<?=$v[0]?>\":{\"type\":\"<?=$v[1]?>\",\"len\":<?=$v[2]?>,\"offset\":%d}",(char*)(&(o.<?=$v[0]?>))-(char*)(&o));
<?php } ?>
fprintf(fout,"}");
}
<?php } ?>
//将结构体块的大小等信息用json的形式输出到文件
int outputBlockInfo(char* fileName){
if((fout = fopen(fileName, "w"))==NULL)
{
printf("file->%s can not open!\n",fileName);
return 1;
}
//备注:json串的键名加上双引号可保证大多数的json解析器都能认识
fprintf(fout,"[");
<?php $i=0;foreach ($structArray as $key=>$value){ $tag = $i++?',':'';?>
fprintf(fout,"<?=$tag?>{\"<?=$key?>\":{\"size\":%d,\"elements\":",sizeof(struct <?=$key?>));
output<?=$key?>Info();
fprintf(fout,"}}");
<?php } ?>
fprintf(fout,"]");
fclose(fout);
return 0;
}
int main(int argc, char* argv[])
{
//结构体信息文件名
char* infoFileName = "<?=$outputFileName?>";
//输出结构体的大小,元素名称,类型等信息
outputBlockInfo(infoFileName);
return 0;
}
3,用于生成c代码的php脚本文件tempCodeWorker.php
<?php
namespace Ados;
require_once 'const.php';
require_once STRUCT_PARSER.'structwkr_engine.php';
$engine = new StructwkrEngine();
//读取c语言结构体定义文件的内容
$src = file_get_contents('structSrc/blockStruct.h');
//调用引擎的编译功能,获取源文件中定义的结构体的信息
$engine->compile($src);
//获取源语言中定义的结构体信息,包括元素名称,类型,查不包括结构体的实际大小,元素的偏移量
//这类信息通过动态生成一个c代码,执行后取得相应的信息。
$structArray=$engine->structInfo();
//用于替换的模板变量
$structSrc="../structSrc/blockStruct.h";
$outputFileName ="../structSrc/blockStruct.json";
//替换模板文件,输出内容就是要生成的c代码
require_once "template/fetchStructInfo.c";
?>
4.用于生成编译c代码的批处理文件的模板文件:build.inc
rem 编译生成temp.exe
set PATH=%PATH%;<?=$vcPath?>\bin
set PATH=%PATH%;<?=$vcIDEPath?>
cl.exe temp.c /I "<?=$vcPath?>\include" /link "<?=$vcPath?>\lib\libcmt.lib" "<?=$vcPath?>\lib\oldnames.lib" "<?=$sdkLibPath?>\Kernel32.Lib"
temp.exe
5, 用于生成批处理文件的php脚本文件tempBatWorker.php
<?php
//用于替换的模板变量
$vcPath= 'D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC';
$vcIDEPath ='D:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE';
$sdkLibPath ='C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib';
//替换模板文件,输出内容就是要生成的批处理文件
require_once "template/build.inc";
?>
6, 运用重定向技术,将模板替换后的输出内容重定向生成最终所需的C代码,以下php文件命名为fetchStructInfo.php
<?php
//调用tempCodeWorker,将输出重定向到文件,得到所需的c源代码
exec('php tempCodeWorker.php > tempfile/temp.c');
//调用tempBatWorker,将输出重定向到文件,得到所需的批处理脚本
exec('php tempBatWorker.php > tempfile/build.bat');
?>
执行第6步所定义的php脚本,将会得到一个c语言源代码文件和一个用于编译此文件的脚本文件。因此我们最后需要一个主脚本文件,将这些步骤串起来,生成C代码,生成编译所需的批处理文件,执行编译脚本得到可执行文件,运行可执行文件。这个主脚本文件fetchStructInfo.bat内容如下:
rem 调用fetchStructInfo.php 生成临时c代码与用于编译并执行的批处理文件
php fetchStructInfo.php
rem 执行此批处理命令,编译得到可执行文件并执行之,得到结构体信息文件
cd tempfile
build.bat
pause
关于调用cl的补充说明
由于本文中动态生成的c代码很简单,因此想用命令行的方式进行编译。
如果直接就用 cl.exe temp.c
多半会报 cl.exe不是可执行的命令。所以要设置环境变量。
这里有两个办法,
第一种:
执行:“D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\vcvars32.bat”
这个批处理脚本会将vc编译所需的环境配置好。并且这个vcvars32.bat复制到其它目录后再执行效果也一样。不过这个方法有一个缺点,就是设置的内容较多,需要较长的时间,好像要1秒多。
第二种:
如果只是做一个简单的编译,所以还是直接设置环境变量比较好一点,而且是不在整个系统范围内设置,只在单个会话中设置,对其它会话也没有影响。
所以先执行
set PATH=%PATH%;D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin
但是接下来还是会报一些连接错误,所以还需要指定相应的包含路径和连接库
在本人所在电脑进行编译的完整的命令应该如下:
set PATH=%PATH%;D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin
set PATH=%PATH%;D:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE
cl.exe temp.c /I “D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include” /link “D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\lib\libcmt.lib” “D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\lib\oldnames.lib” “C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib\Kernel32.Lib”
这样整个执行下来会很快。
有了以上的知识做基础,就可以采用模板替换的方式生成可以在其它机器上运行的批处理脚本。