如何基于Tiny C compiler的c脚本混合编程?
如何基于Tiny C compiler的c脚本混合编程?
TCC简要介绍:
TCC 最有趣的特性是可以用 UNIX 系统上常见的 #!/usr/bin/tcc 的方式来执行 ANSI C 语言写就的源程序,省略掉了在命令行上进行编译和链接的步骤,而可以直接运行 C 语言写就的源程序。这样就能做到像任何一种其它的脚本语言比如 Perl 或者是 Python 一样,显著的加快开发步调。可以像编写 Shell 脚本一样的使用 C 语言,随便想一想都觉得是一件奇妙的事情。但是 TCC 还有一些其它的特性呢!
- TCC 的体积非常小,全部源代码打包压缩以后不到 200 K 字节大小,编译后的 tcc 可执行程序不过 80 K 字节大校这意味着我们几乎可以在任何场合使用 TCC 提供给我们的编写 C 语言脚本的能力。这其中当然包括硬盘空间十分紧张的环境,比如嵌入式系统和启动软盘等等。
- 在给 TCC 的源程序中可以使用任何在给 GCC 的源程序中可以使用的动态链接库。TCC 不仅支持标准的 ANSI C 语言,而且也支持 ISO C99 标准和一部分来自于 GCC 的对 C 语言所做的扩展。
- TCC 直接生成经过部分优化的 X86 机器代码。并不需要生成任何虚拟机的二进制代码。据 TCC 作者提供的数据,TCC 的编译速度比 GNU C 编译器在不做任何代码优化工作(gcc -O0)的时候都要快。当然啦,要是让 GCC 做代码优化的话,那么编译速度就更加比不上 TCC 喽。
- Libtcc库能够让用户将TCC作为作为动态代码生成的后端,在libtcc.h中有API的说明,libtcc_test.c是使用libtcc的简单例子。Libtcc包含了这样一种思想,用户可以将程序包含在字符串中,然后直接编译,接下来就可以存取全局符号(函数和变量)。
生成LIBTCC:
由于libtcc是基于linux的,为了能够生成window下的动态连接库,我们先要使用mingw/msys编译,然后用下面的语句生成dll。为了方便msvc使用,还需要再生成lib库用于链接。整个步骤如下:
1)生成DLL
gcc -O2 -shared -Wall -Wl,--export-all-symbols -mpreferred-stack-boundary=2 -march=i386 -falign-functions=0 -fno-strict-aliasing -DTCC_TARGET_PE -DLIBTCC -o libtcc.dll tcc.c
2)生成def
pexports libtcc.dll | sed "s/^_//" > libtcc.def
3)从def生成lib
lib /machine:i386 /def:libtcc.def
由于c语言编程需要头文件和库,我们也需要在发布程序的时候带上include目录和lib目录,然后在主程序中指定该路径。
TCC和主程序的配合:
在我们的程序中,主要把固定的逻辑放在主程序中,而将变化的东西放在tcc脚本中。该程序的基本流程如下:
按照用户的输入参数打开截包,按照指定的类型将每一个数据报合适的数组片断传给用户写的tcc程序,由tcc程序将处理后的数据传给主函数,由主函数进一步处理,比如写入文件中。
注意到我们需要处理的可能是rtp包,也可能是没有rtp头的包,在输出的时候,有些码流也需要加入额外的处理,比如h261和h263需要调用主程序中的函数进行sbit,ebit的拼接;比如h263+的简单处理,比如h264的流头的添加,比如音频的直接输出。所以我们在主程序和tcc程序间不光要传递变量,还需要传递函数。
下面是大概的代码框架和解释。
主程序:
TCCState *s;
创建新的tcc状态
s = tcc_new();
if (!s) {
fprintf(stderr, "Could not create tcc state/n");
exit(1);
}
添加路径:
tcc_add_include_path(s,fullinc);
tcc_add_library_path(s,fulllib);
设置输出方式为内存:
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
编译一个文件到tcc中(其实是读文件到缓冲区,然后调用了tcc_compile_string)
tcc_compile_file(s,fullpath);
将变量和函数符号加入到tcc环境中去,这样在tcc中就可以直接使用这些符号
tcc_add_symbol(s, "encodeflag", (unsigned long)&encodeflag);
tcc_add_symbol(s,"f",(unsigned long )&f);
tcc_add_symbol(s,"Enter_H263Decode",(unsigned long)&Enter_H263Decode);
tcc_add_symbol(s,"writeFile",(unsigned long)&writeFile);
做所有的重定位工作,在tcc_get_symbol之前必须调用
tcc_relocate(s);
获取tcc中的函数和变量符号
tcc_get_symbol(s, &val, "Process");
Process = (pf_Process)val;
tcc_get_symbol(s,&val,"withRtp");
withRtp=(int)(*(int *)val);
tcc_get_symbol(s,&val,"offset");
offset=(int)(*(int *)val);
循环处理每一个数据包
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
。。。
调用tcc中的处理函数
Process(&pkt_data[offset],totallen);
}
清理tcc环境
tcc_delete(s);
TCC程序
#include <stdio.h>
#include <stdlib.h>
int withRtp=1;
int offset=54;
unsigned char zero[2]={0,0};
unsigned char h264head[4]={0,0,0,1};
extern FILE *f;
extern unsigned char encodeflag;
extern int writeFile(unsigned char *buf,int len);
int Process(unsigned char *pt,int len)
{
if(encodeflag==0 ||encodeflag==1)
{/*h261或者h263*/
if((pt[0])&0x7==0)
{
Enter_H263Decode(len,pt,1);
}
else
{
Enter_H263Decode(len,pt,0);
}
}
else if(encodeflag==2)
{/*h263+*/
if((pt[0]==0x04) && (pt[1]==0x00))
{
writeFile(zero,2);
writeFile(&pt[2],len-2);
}
else if((pt[0]==0x00) && (pt[1]==0x00))
{
writeFile(&pt[2],len-2);
}
else
{
printf("not correct 263+ format/n");
}
}
else if(encodeflag==3)
{/*raw data*/
fwrite(pt,1,len,f);
}
else if(encodeflag==4)
{/*mp3*/
if(pt[0]==0xff && pt[1]==0xfb)
{
writeFile(&pt[4],len-4);
}
else
{
printf("not a mp3 header,%02X %02X/n",pt[0],pt[1]);
}
}
else if(encodeflag==5)
{/*h264*/
writeFile(h264head,4);
writeFile(pt,len);
}
else
{
;
}
return 0;
}
从上面的例子可以看出,使用tcc,可以很容易的处理数组,毕竟,采用其他脚本语言的话,比如lua,perl等,要想处理数组,要想容易的进行位运算,还是很麻烦的,而且,c语言毕竟是大多数人熟悉的语言,使用起来很方便。