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


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 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来测试一下~

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格式,然后用命令行执行

luajit testIconv.lua

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

@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的函数,到最后终于找到了方法……

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~
1
I'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" 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变成这样了:
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函数……现在才体会到lua这种脚本语言的漂亮之处~

相关内容

    暂无相关文章