#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`估计不能用,我连试都没试……我感觉发挥我自己动手丰衣足食的特长的时候到了……(**后来明确是可以用的,LuaJIT调用Lua库请参考[这篇文章](../LuaJIT_C_Extend_LuaSQL_complier_connect_Access/detail.html "这篇文章")**) 我用`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`是这样的: ```Lua 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 then error("please close it first!", 2); return false; end local o = {_cd = nil}; --setmetatable(o, self) --self.__index = self setmetatable(o, {__index = self}); if type(tocode) ~= "string" or type(fromcode) ~= "string" then error("paramater error,please input string!", 2); return false; end o._cd = ICONV.libiconv_open(tocode,fromcode); if o._cd == -1 then o._cd = nil; return false; end return 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 then return false,nil; end local out = ffi.string(outstr,self.BUFFER_LENGTH - outsize[0]); return true,out; end, closeHandle = function(self) if self._cd == nil or self._cd == -1 then error("please open it first!", 2); return false; end ICONV.libiconv_close(self._cd); self._cd = nil; end }; return iconv; ``` 至于里面的`long`,`32位`系统`size_t`是`unsigned int`,`64位`是`unsigned long`,由于有时候返回`-1`,为了对比方便,我把`unsigned`去掉了……大家可以根据自己的平台改改试试…… 然后写个`testIconv.lua`来测试一下~ ```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 then value = nil; end print("after iconv:"..value); ``` 把该文件保存为`utf-8`格式,然后用命令行执行 ```Shell luajit testIconv.lua ``` 或者写个`bat`耍帅的来运行一下: ```Shell @echo off echo -----Test iconv----- luajit iconvTest.lua pause ``` 执行结果: ``` before iconv:鎴戠埍姹夊瓧~鎴戠敤UTF-8... after iconv:我爱汉字~我用UTF-8... ``` 事已至此,当天晚上就这么结束了…… 第二天起来感觉好别扭……为啥`open`后面非要跟个`close`,多别扭啊……我看`lua Iconv`的示例里面不用`close`啊…… 然后看它的源码,发现它用了带`__gc函数`的元表,然而我不记得`lua table`有这个元表函数啊…… 查了一下`lua文档`,发现只有`C语言`中定义的`C Type`有这个元表函数……感觉是不是应该放弃? 当然不是……我感觉它能行我肯定也能行……我又去查了`luajit`文档,主要是`ffi`的函数,到最后终于找到了方法…… ```Lua ctype = ffi.metatype(ct, metatable) cdata = ffi.gc(cdata, finalizer) ``` 这两个函数很关键,一个是给`c type`的复杂类型(结构体、共用体等)添加元表的,其中包括`__gc元表函数`;另一个是给`C Data`添加析构函数的,我仿佛看到了救星~ 写一段代码试一下呗~ ```Lua 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~ 1 I'll over~goodbye~ ``` 看来效果拔群啊~ 于是我今天晚上又花了一晚上的时间改写了上面的`iconv.lua` ```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" then error("paramater error,please input string!", 2); return false; end local o = nil; if self._cd ~= nil then if self._cd[0] ~= -1 then error("please close it first!", 2); return false; end o = self; else o = {_cd = ffi.new("long[1]",-1)}; setmetatable(o, {__index = self}); end o._cd[0] = ICONV.libiconv_open(tocode,fromcode); ffi.gc(o._cd,o.__gc); if o._cd[0] == -1 then return false; end return 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 then return false,nil; end local 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 then ICONV.libiconv_close(_cd[0]); end end, closeHandle = function(self) if self._cd == nil or self._cd[0] == -1 then error("please open it first!", 2); return false; end ICONV.libiconv_close(self._cd[0]); self._cd[0] = -1; end }; return iconv; ``` 于是上面的`testIconv.lua`变成这样了: ```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 then value = nil; end print("after iconv:"..tostring(value)); ``` 这种情况下不用各种担心`open`之后必须要`close`什么的,也不用非得强迫症似的非要置`nil`,写`C语言`或者`java语言`等等的程絮媛们估计都烦透了拖家带口的`open函数`……


发表评论

必填,公开,这样称呼起来方便~

必填,不会被公开,不必担心~

http://

非必填,公开,目的是方便增进友好访问~

必填,请输入下方图片中的字母或数字,以证明你是人类

看不清楚
必填,最好不要超过500个字符
     ↑返回顶端↑