LuaJIT(OpenResty)调用iconv动态链接库转码

来源:互联网 发布:阿里云服务器介绍 编辑:程序博客网 时间:2024/04/19 20:16
LuaJIT(OpenResty)调用iconv动态链接库转码
一个原来ASP的小项目,我想移植到OpenResty平台上。ASP平台虽然当年简单粗暴,但是现在要保持生命力还是得找个前景比较看好的,于是我相中了OpenResty这个平台。
首先遇到的问题当然是编码问题了……由于是ASP项目从前到后GBK,好歹大部分的提交操作都是AJAX POST,但是AJAX的提交都是UTF-8的,之前是在ASP做了转码操作的,而OpenResty平台使用nginx-iconv-module来转码,但是我看了看,首先OpenResty平台都不默认打包这个模块,其次,貌似不满足要求,我要的不是全部转码,我只需要把AJAX提交的部分请求转码就行……
于是我开始寻找LuaJIT、OpenResty相关的转码库,也许是我不会找,总之没找到……找到一个Lua Iconv,但是基于Lua标准平台的,LuaJIT估计不能用,我连试都没试……我感觉发挥我自己动手丰衣足食的特长的时候到了……
我用EveryThing搜了一下自己的操作系统(windows),发现到处都是iconv.dll,直接随便拿出一个来用就是了嘛……
要调用这个库成功着实费了一些功夫,感觉它的函数定义特别反人类……我花了一晚上的时间来调通三个主要函数……整整一晚上啊……
总结一下它的反人类之处:

1、fromCode和toCode参数顺序是反着的;

2、调用时传入的不是char*,而是char**;

3、传入的size不是size_t,而是size_t *;

4、outSize需要传入最大输出buff,而输出的不是最终输出的字节数,而是最后剩下没用完的字节数……

5、明明是size_t类型,却要返回-1……

6、导出的函数名前面都加了个lib

所以,调了一晚上不能怪我太弱,只能怪敌人太强大……

闲话少说,先上代码,最早调通的iconv.lua是这样的

(注:沿用iconv我的from、to也是反着的)

居然没有lua的编辑器,拿Ruby冒充一下):

local ffi = require("ffi");ffi.cdef[[long libiconv_open(const char* tocode, const char* fromcode);long libiconv(int cd,  char** inbuf, long *inbytesleft, char** outbuf, long *outbytesleft);long libiconv_close(int cd);]];local ICONV = ffi.load("iconv.dll");local iconv = {_cd = nil,BUFFER_LENGTH = 4096,openHandle = function(self,tocode,fromcode)if self._cd ~= nil and self._cd ~= -1 thenerror("please close it first!", 2);return false;endlocal o = {_cd = nil};--setmetatable(o, self)--self.__index = selfsetmetatable(o, {__index = self});if type(tocode) ~= "string" or type(fromcode) ~= "string" thenerror("paramater error,please input string!", 2);return false;endo._cd = ICONV.libiconv_open(tocode,fromcode);if o._cd == -1 theno._cd = nil;return false;endreturn o;end,iconv = function(self,str)local inLen = string.len(str);local insize = ffi.new("long[1]",inLen);local instr = ffi.new("char[?]",inLen+1,str);local inptr = ffi.new("char*[1]",instr);local outstr = ffi.new("char[?]",self.BUFFER_LENGTH+1);local outptr = ffi.new("char*[1]",outstr);local outsize = ffi.new("long[1]",self.BUFFER_LENGTH);local err = ICONV.libiconv(self._cd,inptr,insize,outptr,outsize);if err == -1 thenreturn false,nil;endlocal out = ffi.string(outstr,self.BUFFER_LENGTH - outsize[0]);return true,out;end,closeHandle = function(self)if self._cd == nil or self._cd == -1 thenerror("please open it first!", 2);return false;endICONV.libiconv_close(self._cd);self._cd = nil;end};return iconv;
至于里面的long,32位系统size_t是unsigned int,64位是unsigned long,由于有时候返回-1,为了对比方便,我把unsigned去掉了……大家可以根据自己的平台改改试试……

然后写个testIconv.lua来测试一下~

local Iconv = require("iconv")local togbk = Iconv:openHandle("gbk", "utf-8");if not togbk then print("create handle failed!");return; end;local succ=nil;local value = "我爱汉字~我用UTF-8..."print("before iconv:"..value)succ,value = togbk:iconv(value);togbk:closeHandle();if not succ thenvalue = nil;endprint("after iconv:"..value);
把该文件保存为utf-8格式,然后用命令行执行

luajit testIconv.lua

或者写个bat耍帅的来运行一下:

@echo offecho -----Test iconv-----luajit iconvTest.luapause
执行结果:

before iconv:鎴戠埍姹夊瓧~鎴戠敤UTF-8...after iconv:我爱汉字~我用UTF-8...

事已至此,当天晚上就这么结束了……

第二天起来感觉好别扭……为啥open后面非要跟个close,多别扭啊……我看lua Iconv的示例里面不用close啊……

然后看它的源码,发现它用了带__gc函数的元表,然而我不记得lua table有这个元表函数啊……

查了一下lua文档,发现只有C语言中定义的C Type有这个元表函数……感觉是不是应该放弃?

当然不是……我感觉它能行我肯定也能行……我又去查了luajit文档,主要是ffi的函数,到最后终于找到了方法……

ctype = ffi.metatype(ct, metatable)cdata = ffi.gc(cdata, finalizer)
这两个函数很关键,一个是给c type的复杂类型(结构体、共用体等)添加元表的,其中包括__gc元表函数;另一个是给C Data添加析构函数的,我仿佛看到了救星
写一段代码试一下呗~

local ffi = require("ffi");local a = ffi.new("int[1]",1);ffi.gc(a,(function(self)print(self[0]);print("I'll over~goodbye~");end));print("wait a while~");print("let's go die~");
输出结果:
wait a while~let's go die~1I'll over~goodbye~
看来效果拔群啊~

于是我今天晚上又花了一晚上的时间改写了上面的iconv.lua

local ffi = require("ffi");ffi.cdef[[long libiconv_open(const char* tocode, const char* fromcode);long libiconv(long cd,  char** inbuf, long *inbytesleft, char** outbuf, long *outbytesleft);long libiconv_close(long cd);]];local ICONV = ffi.load("iconv.dll");local iconv = {_cd = nil,BUFFER_LENGTH = 4096,openHandle = function(self,tocode,fromcode)if type(tocode) ~= "string" or type(fromcode) ~= "string" thenerror("paramater error,please input string!", 2);return false;endlocal o = nil;if self._cd ~= nil thenif self._cd[0] ~= -1 thenerror("please close it first!", 2);return false;endo = self;elseo = {_cd = ffi.new("long[1]",-1)};setmetatable(o, {__index = self});endo._cd[0] = ICONV.libiconv_open(tocode,fromcode);ffi.gc(o._cd,o.__gc);if o._cd[0] == -1 thenreturn false;endreturn o;end,iconv = function(self,str)local inLen = string.len(str);local insize = ffi.new("long[1]",inLen);local instr = ffi.new("char[?]",inLen+1,str);local inptr = ffi.new("char*[1]",instr);local outstr = ffi.new("char[?]",self.BUFFER_LENGTH+1);local outptr = ffi.new("char*[1]",outstr);local outsize = ffi.new("long[1]",self.BUFFER_LENGTH);local err = ICONV.libiconv(self._cd[0],inptr,insize,outptr,outsize);if err == -1 thenreturn false,nil;endlocal out = ffi.string(outstr,self.BUFFER_LENGTH - outsize[0]);return true,out;end,__gc = function(_cd)--print("gc running!");if _cd ~= nil and _cd[0] ~= -1 thenICONV.libiconv_close(_cd[0]);endend,closeHandle = function(self)if self._cd == nil or self._cd[0] == -1 thenerror("please open it first!", 2);return false;endICONV.libiconv_close(self._cd[0]);self._cd[0] = -1;end};return iconv;
于是上面的testIconv.lua变成这样了:
local Iconv = require("iconv")local togbk = Iconv:openHandle("gbk", "utf-8");if not togbk then print("create handle failed!");return; end;local succ=nil;local value = "我爱汉字~我用UTF-8..."print("before iconv:"..value)succ,value = togbk:iconv(value);--togbk:closeHandle();togbk = nil;--当没有引用时自动释放,避免了内存泄漏,当然也可以手动调用togbk:closeHandle()if not succ thenvalue = nil;endprint("after iconv:"..tostring(value));
这种情况下不用各种担心open之后必须要close什么的,也不用非得强迫症似的非要置nil,一切都是那么自然……写C语言或者java语言等等的程絮媛们估计都烦透了拖家带口的open函数……现在才体会到lua这种脚本语言的漂亮之处~

0 0
原创粉丝点击