#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函数`……