程序员人生 网站导航

TCC研究(2) 把C语言当作脚本,解释执行,并嵌入各类程序

栏目:php教程时间:2015-04-28 08:46:52

Tiny C Compiler(简称TCC, 或Tiny CC)是世界上最小的C语言编译器。

TCC有1个突出的特点:就是可以把C语言当作脚本使用。试用记录以下:


首先,安装好TCC.   

在Windows下, 下载履行程序: tcc-0.9.26-win32-bin.zip。 解压到c: cc, 将c: cc添加到PATH目录中。

测试安装是不是成功,在命令行窗口中打入命令   tcc -v , 看到TCC版本号即是成功


方式1: 以TCC解释履行C语言文本

解释履行,就是不编译,直接运行。

写1段C程序,存盘为 hello.c

#include <stdio.h> int main(int argc, char *argv[]) { int i; printf("Hello, world "); for(i=1; i<argc; i++) printf("argv[%d]=%s ", i, argv[i]); }
在命令行窗口,打入命令   tcc -run hello.c

-run 的意思是立即履行

运行结果:

Hello, world


还可以通过命令行,向程序传入参数

在命令行窗口,打入命令   tcc -run hello.c param1 param2

运行结果(显示有两个传入参数):

Hello, world
argv[1]=param1
argv[2]=param2


方式2: 在Linux下,把C语言程序当作脚本运行,像SH脚本1样

首先,在Linux下安装TCC,我用的版本是Ubuntu 14.04

下载TCC源码,解压

cd ~

wget  http://download.savannah.gnu.org/releases/tinycc/tcc-0.9.26.tar.bz2

tar  xvf  tcc-0.9.26.tar.bz2

则生成1个子目录 tcc-0.9.26,进入目录,编译它

cd tcc-0.9.26

./configure

make

make install

完成后,输入命令  tcc -v .  如显示tcc版本号,表示成功。

用find命令查找1下 tcc 在哪?    find / -name tcc

发现tcc可履行文件安装在  /usr/local/bin

写1段C程序,存盘为 hello.c

#!/usr/local/bin/tcc -run #include <stdio.h> int main(int argc, char *argv[]) { printf("Hello, world "); }
第1行的 #!/usr/local/bin/tcc -run 是告知操作系统,这是1个脚本,解释器是 /usr/local/bin/tcc

修改权限,将hello.c 转为可履行文件 

   chmod +x hello.c

直接运行hello.c

./hello.c

运行结果:

hello,world

感觉不错, .c文件直接当脚本


对照1下, 编译后再履行的情况 ,把hello.c编译1下:    tcc hello.c   ,  将生成 a.out

运行a.out:  ./a.out

运行结果与脚本履行1样

由于有第1行的 #!/usr/local/bin/tcc -run, 用gcc编译hello.c会出错,用tcc编译就没问题


方式3:在程序中嵌入脚本功能,动态调用C语言脚本(这是TCC的精华的地方)

嵌入脚本,就是让你的程序具有脚本功能,而且这个脚本还是C语言的。

tcc目录下的 exampleslibtcc_test.c是1个示范程序。

我觉得示范程序不通用,因而自己编了1个通用的模块,两个文件:   cscript.c, cscript.h

先看 cscript.c,  定义了1个函数  run_script() 

说明1下: libtcc.h 是 tcc提供的1个头文件,在tcc目录下复制过来的

#include <stdlib.h> #include "libtcc.h" //TCC提供的头文件 /* 运行1个脚本, 启动脚本中指定的函数,返回该函数的运行结果值 * 脚本中的启动函数原型必须为: int func(int param) * program是脚本全部内容, function_name是启动函数名称,param是传递给函数的参数 * tcc_path用于指定tcc所在目录,tcc_path设为时NULL表示不指定tcc目录 */ int run_script(char *program, char *function_name, int param, char *tcc_path) { TCCState *s; //TCC编译引擎 int (*func)(int); //1个函数指针, 函数原型为: int func(int param) int result; //运行结果 s = tcc_new(); //初始化TCC编译引擎 if (!s) return ⑴; //初始化失败 //tcc_path指定tcc所在的目录 if (tcc_path!=NULL) tcc_set_lib_path(s, tcc_path); //指明编译结果写入内存,而不是存为文件 tcc_set_output_type(s, TCC_OUTPUT_MEMORY); //如果编译失败,则退出 if (tcc_compile_string(s, program) == ⑴) { tcc_delete(s); return ⑵; } //如果程序重定位失败,退出 if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0) { tcc_delete(s); return ⑶; } //寻觅名为function_name的入口函数 func = tcc_get_symbol(s, function_name); if (!func) { //找不到入口函数,则退出 tcc_delete(s); return ⑷; } //运行入口函数,取得运行结果 result = func(param); //结束TCC编译引擎 tcc_delete(s); return result; }

再看  cscript.h ,不过声明了1下run_script()这个函数。

#ifndef __CSCRIPT_H__ #define __CSCRIPT_H__ #ifdef __cplusplus extern "C" { #endif /** 运行1个脚本, 启动脚本中指定的函数,返回该函数的运行结果值 * 脚本中的启动函数原型必须为: int func(int param) * programe是脚本程序, function_name是启动函数,param是传递给函数的参数 * tcc_path用于指定tcc所在目录,tcc_path设为NULL时表示不指定tcc目录 */ extern int run_script(char *program, char *function_name, int param, char *tcc_path); #ifdef __cplusplus } #endif #endif

好了, 编1个主程序,使用通用模块中的run_script()函数,每次通过命令行指定脚本文件,读出并运行。

假定主程序名为 cscript.exe

运行命令为:   cscript   <script_file>

主程序以下,存盘为 main.c

#include <stdio.h> #include <stdlib.h> #include "cscript.h" //通用模块头文件 int main(int argc, char *argv[]) { char *program = NULL; //脚本内容 char *function_name = "script_main"; //启动函数名为script_main int param = 888; //传入参数 char *tcc_path = NULL; int file_size; int result; //返回结果 FILE *fp; if (argc<=1) { printf("Usage: cscript <script_file> "); return ⑴; } //第1个命令行参数是脚本文件名,打开它 if ((fp=fopen(argv[1],"rb"))==NULL) { printf("Error open file %s ", argv[1]); return ⑴; } //测出文件长度 fseek(fp, 0L, SEEK_END); file_size = ftell(fp); //申请1个内存用于寄存文件内容 program = (char *)malloc(file_size+1); if (program==NULL) return ⑴; //将文件内容全部读入到 program中 fseek(fp, 0L, SEEK_SET); if (fread(program, file_size, 1, fp)>0) { program[file_size]=0; result = run_script(program, function_name, param, tcc_path);//运行 printf("result = %d ", result); } free(program); fclose(fp); }
这个程序比较简单,不过是从命令行第1个参数指定的文件中,读出内容,当作脚本履行。

脚本中需要指定1个启动函数,这个函数不是main(), 而是 int script_main(int param)

编译主程序(main.c 和 cscript.c), 假定tcc安装在c: cc目录下

tcc -llibtcc -Lc: cc main.c cscript.c -o cscript.exe

-llibtcc 表示链接 libtcc库 (大小写不要写错)

-Lc: cc 指定tcc目录(大小写不要写错)

-o cscript.exe 指定生成exe文件为 cscript.exe

编译成功,没有任何提示。目录下生成 cscript.exe


好了,写1个脚本(内容以下),存盘为 test1.txt

#include <stdio.h> int script_main(int param) { printf("it's in script main, param =%d ", param); return param; }

用刚才生成的 cscript.exe 直接运行 test1.txt

打入命令   cscript  test1.txt

运行结果:

it's in script main, param =888
result = 888

成功!!

这1次,我们不是使用tcc去运行脚本,而是采取刚才自己写的 cscript.exe程序去运行脚本。让自己的程序具有了脚本功能。

从TCC提供的example上看,TCC可以支持标准C, windows API调用,Linux动态库调用等,就是说,用脚本写系统程序、GUI,甚么都行。 

相当于远程分发程序。

如何利用,看想像力了。比如说: 程序从网站下载1个文本,运行,生成1个GUI,产生1个小人在桌面上跑来跑去……

再比如:大数据运算,100台计算机联网,某台机分发1个脚本,各机本地运算,返回结果给主机汇总,就是大数据的map-reduce算法嘛。

心有多远,就有多远

TCC就是强!  




------分隔线----------------------------
------分隔线----------------------------

最新技术推荐