[我的另外一个博客地址:http://www.ncmacker.cc,那边排版会比较好点。]
公司的打印机最近新添加一个打印图片的功能,具体是用户在后台设置他的logo,服务器接收到图片文件,转换单色的bmp文件(热敏打印机),再由php将单色bmp图片转换成bin文件,里面包含了图片的头信息和数据。做这么个小功能还花了我不少时间,走了很多弯路,各位看官请容我一一讲述吧。
思路1:使用PHP扩展用c语言来完成这个操作
最开始的时候,认为这个操作php语言无法胜任,于是选用了php extension。可是作为菜鸟的我,何曾玩过这么高大上的技能?没办法了,只好现学现卖了,最开始我以为是只要用c语言将转换的功能实现了,然后封装下就可以给php调用,做到后面才发现很多c语言的函数都不能直接使用,要么是被阉割掉了,要么就是被zend重写了,只有遵循zend_api才能够做出一个有用的php扩展。网上搜索了很多关于php extension的资料,发现中文资料还是相对较少。虽然有那么几个稍微系统点的资料:
PHP扩展开发及内核应用
这本书的是翻译的一本国外一个大牛的扩展开发的书籍,翻译的还是比较有水准的,这个翻译项目的发起人也是大牛,其中最为知名的应该是鸟哥,如果连鸟哥都不知道的话,你这php程序员估计也是白当了。
还有极客学院也有一份这本书,与上面介绍的差不多。还有这本深入理解php内核:Thinking In PHP Int ernals也不错。还有一些国外牛人整理的一些资料,先不再这里罗嗦,待会放在结尾当福利发送哈。
我的php extension第一步是先搭建环境,我也把搭建环境的步骤也讲下吧,这样也方便其他需要使用到扩展的童鞋。
1.下载php的完整版源码,我的是php-5.4.41版本的,因为公司的服务器就是使用的5.4.41的,不然版本不对,扩展就算开发好了,后面也是无法添加到php里的(可以兼容老版本的,但是开发版本不能比服务器版本高)。
2.将下载好的php源码解压放在某个地方,我的是放在主文件夹里(就是ubuntu的个人文件夹里)。
然后用cd命令跳转到php-5.4.41/ext目录下:
cd /home/pchangl/php-5.4.41/ext
然后在主文件夹下创建一个extension_def的目录(这个目录不是必须的,只是我为了方便管理.def的文件才建立的),在这个文件夹下建立一个叫做pchangl.def的文件(def的文件是扩展方法的签名)。
这个时候建立好了def的文件,我们就开始使用php/ext目录下的ext_skel小工具为我们的扩展生成框架。
root@pchangl:/home/pchangl/php-5.4.41/ext #ext_skel --extname=pchangl --proto=/home/pchangl/php-extension/pchangl.def
这个时候就给我们生成了扩展的框架:
这个时候我们的框架已经生成了,我们先把config.m4文件修改下,删除掉第16,17,18行前的dnl注释
这个时候我们就可以修改pchangl.c的代码了,这个文件里的代码就是我们自己的程序代码。
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h" #include "php_pchangl.h" static int le_pchangl; const zend_function_entry pchangl_functions[] = { PHP_FE(confirm_pchangl_compiled, NULL) /* For testing, remove later. */ PHP_FE_END /* Must be the last line in pchangl_functions[] */ }; zend_module_entry pchangl_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "pchangl", pchangl_functions, PHP_MINIT(pchangl), PHP_MSHUTDOWN(pchangl), PHP_RINIT(pchangl), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(pchangl), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(pchangl), #if ZEND_MODULE_API_NO >= 20010901 PHP_PCHANGL_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_PCHANGL ZEND_GET_MODULE(pchangl) #endif PHP_MINIT_FUNCTION(pchangl) { return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(pchangl) { return SUCCESS; } PHP_RINIT_FUNCTION(pchangl) { return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(pchangl) { return SUCCESS; } PHP_MINFO_FUNCTION(pchangl) { php_info_print_table_start(); php_info_print_table_header(2, "pchangl support", "enabled"); php_info_print_table_end(); } PHP_FUNCTION(confirm_pchangl_compiled) { char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "pchangl", arg); RETURN_STRINGL(strg, len, 0); }
上面的这个代码是ext_skel工具生成的框架,你写的扩展也要按照他的标准来写,由于代码长度的原因,我删除掉了一部分生成的注释,如果想看的话,你可以自己编写个框架看看注视,下面我就讲解下几个比较重要的部分吧。
PHP_MINIT_FUNCTION(pchangl)
{
return SUCCESS; }
这个方法随着php在apache或者nginx中启动而诞生内存的时候,就会执行,可以把这个方法当成初始化的方法吧。
PHP_RINIT_FUNCTION(pchangl)
{
return SUCCESS; }
这个方法会在每一个页面请求到来的时候执行。
PHP_RSHUTDOWN_FUNCTION(pchangl)
{
return SUCCESS; }
RINIT_FUNCTION方法执行玩后,请求也处理的差不多了,这个时候就会进入这个方法,启动回收程序来收拾烂摊子。
PHP_MSHUTDOWN_FUNCTION(pchangl)
{
return SUCCESS; }
前面该启动的也启动了,该结束的也结束了,现在该apache老人家休息的时候,当apache通知php自己要stop的时候,就会进入这个方法。给扩展下最后通碟,如果有未了的心愿,就放在这个方法里,这可是最后的机会哦
PHP_FUNCTION(function name)
{ //实现 }
这个方法就是我们扩展方法的主体了。
pchangl.c这个文件就介绍到这里了,其他的内容就到我推荐的几个资料中查询吧,都有的。我也就不一一讲解了。
介绍完了环境的搭建,我就开始写自己的扩展,我们的bmp转bin的c代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
//调试开关
#define PRINTF_DEBUG 0
//四个字节对齐 进1制处理 #define WIDTHBYTES(bits) (((bits)+31)/32*4) //每一行的像素宽度必须是4的倍数,否则补0补齐。 /*定义BYTE为无符号一个字节的类型*/ typedef unsigned char BYTE; /*定义WORD为无符号两个字节的类型*/ typedef unsigned short WORD; /*定义DWORD为无符号四个字节的类型*/ typedef unsigned long DWORD; #pragma pack(1) /*位图文件头*/ typedef struct BMP_FILE_HEADER { WORD bType; /* 文件标识符 */ DWORD bSize; /* 文件的大小 */ WORD bReserved1; /* 保留值,必须设置为0 */ WORD bReserved2; /* 保留值,必须设置为0 */ DWORD bOffset; /* 文件头的最后到图像数据位开始的偏移量 */ } BMPFILEHEADER; /*位图信息头*/ typedef struct BMP_INFO { DWORD bInfoSize; /* 信息头的大小 */ DWORD bWidth; /* 图像的宽度 */ DWORD bHeight; /* 图像的高度 */ WORD bPlanes; /* 图像的位面数 */ WORD bBitCount; /* 每个像素的位数 */ DWORD bCompression; /* 压缩类型 */ DWORD bmpImageSize; /* 图像的大小,以字节为单位 */ DWORD bXPelsPerMeter; /* 水平分辨率 */ DWORD bYPelsPerMeter; /* 垂直分辨率 */ DWORD bClrUsed; /* 使用的色彩数 */ DWORD bClrImportant; /* 重要的颜色数 */ } BMPINF; typedef struct tagRGBQUAD { BYTE rgbBlue; //蓝色的亮度(值范围为0-255) BYTE rgbGreen; //绿色的亮度(值范围为0-255) BYTE rgbRed; //红色的亮度(值范围为0-255) BYTE rgbReserved; //保留,必须为0 } RGBQUAD; #pragma pack() int main() { FILE *fpin = NULL; FILE *fpout = NULL; BMPFILEHEADER fileHeader = { 0, 0 }; BMPINF infoHeader = { 0, 0 }; RGBQUAD imgHeader[256 * 4] = { 0, 0 }; DWORD offset = 0, width = 0, height = 0, bitCount = 0; DWORD l_width = 0, bmp_width = 0, bmp_height = 0; DWORD i = 0, j = 0, m = 0; BYTE *bmpDataTmp = NULL; BYTE *bmpPixelTmp = NULL; BYTE bmp_start[8] = "ICONTOP"; BYTE bmp_end[8]