我们知道,lua通过lua_State堆栈可以很方便的与C语言进行交互
http://blog.csdn.net/sm9sun/article/details/68946343
也可以调用专门为lua调用而封装的C库。
具体步骤:
1.原C文件中引入lua相关头文件
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
2.声明调用函数
static int c_hello(lua_State *L) //参数一律为lua_State堆栈
static const luaL_reg functions[] = {
{"hello", c_hello},
{0,0}
};
int luaopen_*(lua_State *L)//*是想打成so的包名
{
luaL_register(L, "*", functions);
return 1;
}
5.打包成so文件(注意引入lua)
6.lua调用
require('*')
*.hello
但是这种方式毕竟太局限,我们希望的是lua可以调用普通的c库,lua的第三方库alien就可以实现这一功能http://alien.luaforge.net/
安装alien可以通过git https://github.com/mascarenhas/alien,更好的是借助luarocks安装https://luarocks.org/
LuaRocks是Lua模块的软件包管理器,相信常用lua的人应该不陌生。
首先在当前产品所需要的lua版本下安装luarocks(这个是必须的,例如我用skynet框架里lua5.3版本,但是我系统是lua5.1,这样后期就会不兼容)
wget http://luarocks.org/releases/luarocks-*.tar.gz
tar zxpf luarocks-*.tar.gz
cd luarocks-*
./configure
make bootstrap
*为版本号,安装好后确定版本
然后luarocks install alien安装alien
alien需要libffi,如果没有,请先安装
yum install -y libffi-devel
如果你的系统里有python2.6,这里可能会出现一个python不兼容的情况,如没有请无视
升级python 2.6.5到2.7.13以上,能兼容yum
# 安装所有的开发工具包
yum groupinstall -y "Development tools"
# 安装其它的必需包
yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel
# 下载、编译和安装 Python 2.7.13
wget https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz
tar zxf Python-2.7.13.tgz
cd Python-2.7.13
./configure
make && make install
/usr/local/bin/python2.7 -V
mv /usr/bin/python /usr/bin/python2.6.6
ln -s /usr/local/bin/python2.7 /usr/bin/python
python -V
让yum install仍然走2.6.6
vim /usr/bin/yum
#!/usr/bin/python 改成#!/usr/bin/python2.6.6
git clone git://github.com/waylan/Python-Markdown.git python-markdown
cd python-markdown
python setup.py install
cp /usr/bin/markdown_py /usr/bin/markdown
markdown
也可以安装LuaJIT,其包含ffihttp://luajit.org/
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar zxvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make && make install
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
确定安装好ffi后通过luarocks install alien安装alien
如还报ffi.h找不到,则指定路径
luarocks install alien FFI_DIR=/usr/lib64/libffi-3.0.5 FFI_LIBDIR=/usr/lib64
查看安装的alien信息
安装完毕后在lua环境下调用alien模块,若不报错则安装成功
require("alien_c")
require("alien")
alien = require("alien_c") --1.加载alien
libc = alien.load("*.so") -- 2.加载动态链接库so,dll都可以
libc.hello:types("string","string") -- 3.说明参数类型:例如输入一个json,返回一个json
in_json=""
out_json="ret"
out_json=libc.hello(in_json) -- 调用
print(out_json)
以下为alien官方api文档译文
基本用法
加载动态库非常简单; Alien在默认情况下假定一个名为 .dylib的OSX 命名方案和其他Unix系统的lib 名称 .so。如果名
称不是alien模块导出的函数之一,那么可以使用库获取引用alien.name。否则(例如,要加载一个名为libwrap.so的库
),您必须使用alien.load("wrap")。
您还可以通过alien.load使用路径或相应的扩展名(如 alien.load("mylibs/libfoo.so")或)调用库来指定库的全名
alien.load("libfoo.so")。无论哪种方式,您都可以获得对库访问的参考,以访问其功能。
您还可以使用当前正在运行的模块的引用 alien.default,这样可以引用模块导出的任何函数及其对ELF和Mach-O系统的
传递依赖性。
一旦你有一个对库的引用,你可以使用libref.funcname获得一个导出函数的引用。例如:
> def=alien.default
> =def.puts
alien function puts, library defaults
>
要使用一个函数,首先必须告诉Alien函数原型,使用func:types(ret_type,arg_types ...),其中的类型是以下字
符串之一:“void”,“int”,“uint”,“double” ,“char”,“string”,“pointer”,“ref int”,“ref
uint”,“ref double”,“ref char”,“callback”,“short”,“ushort”长“,”乌龙“,”浮“。大多数直
接对应C类型; 字节是一个带符号的char,字符串是const char *,指针是void *, 回调是一个通用函数指针,ref char
,ref int 和ref double是通过参考版本的C类型。继续以前的例子:
> def.puts:types("int", "string")
> def.puts("foo")
foo
>
您可以看到,在定义原型后,您可以像Lua函数一样调用该函数。外来人将Lua号转换为C数字类型,将nil转换为NULL,将
Lua字符串转换为const char *作为 字符串,并将nil转换为NULL,将userdata 转换为void *作为 指针。对于返回值,
转换工作相反(指针类型转换为light userdata)。
参考类型是特殊的; Alien在堆栈中为参数分配空间,复制传递给它的Lua号(适当转换),然后使用该空间的地址调用该
函数。然后将该值转换回Lua号,并在函数正常返回值后返回。一个例子,使用scanf:
> scanf = alien.default.scanf
> scanf:types("int", "string", "ref int", "ref double")
> _, x, y = scanf("%i %lf", 0, 0)
23 42.5
> =x
23
> =y
42.5
即使功能没有使用,您也必须传递一个值,如上所述。
指定类型的另一种方法是将表传递给func:types。这个表的数组部分每个参数都有一个项目,你也可以传递两个哈希键
,ret,函数的返回类型(默认int为常规), abi是函数的调用约定(对Windows有用,可以指定“stdcall”作为
__stdcall函数的ABI,默认的ABI始终为“default”,所有系统还支持通常的C调用约定“cdecl”。在没有stdcall约定
的系统中,“stdcall”是与“默认”相同。
这是使用此替代定义的上一个示例:
> scanf = alien.default.scanf
> scanf:types{ ret = "int", "string", "ref int", "ref double" }
> _, x, y = scanf("%i %lf", 0, 0)
23 42.5
> =x
23
> =y
42.5
如果您获得原始的函数指针(从函数返回,例如或传递给回调),则可以将其转换为异常函数alien.funcptr(fptr)。这
返回一个Alien函数对象,您可以正常键入和调用函数。
缓冲区
基本用法足以与C代码进行大量接口,特别是处理自己的内存分配的良好的库(Lua C API是这样的API的一个很好的例子
)。但是有些库不能导出这样一个良好的API,并且需要您分配由库突变的内存。这样可以防止您将Lua字符串传递给他们
,因为Lua字符串必须是不可变的,所以Alien提供了一个缓冲区抽象。功能 alien.buffer分配一个新的缓冲区。如果没
有参数调用它,它将为您的平台分配一个标准缓冲区大小的缓冲区。如果使用一个数字来调用它,它将分配一个具有这个
字节数的缓冲区。如果你传递一个字符串,它将分配一个作为字符串副本的缓冲区。如果您传递一个light userdata,它
将使用该userdata作为缓冲区(请注意)。
制作缓冲区后,您可以传递它来代替字符串或指针类型的任何参数 。要获取您使用的缓冲区的内容,buf:tostring您可
以再次传递要从缓冲区读取的字符数,也可以不传递任何内容,将缓冲区视为C字符串(读取直到找到\ 0)。您也可以调
用buf:len,调用strlen缓冲区。最后, tostring(buf)是一样的buf:tostring()。
一个使用缓冲区的例子:
> gets = alien.default.gets
> gets:types("pointer", "string")
> buf = alien.buffer()
> gets(buf)
Foo bar
> =tostring(buf)
Foo bar
>
您可以访问缓冲区的第i个字符buf[i],并可以设置其值buf[i] = v。请注意,这些是C字符(字节),而不是Lua 1个字
符的字符串,因此您需要使用string.char和string.byte 转换Lua字符和C字符。从Lua访问Alien缓冲区是基于1而不是0
的。
您还可以使用buf:get(offset,type)获取并设置其他值,并通过buf:set(offset,val,type)设置它。偏移量以
字节为单位,而不是元素,所以如果buf有三个“int”值,则它们的偏移量分别为1,5和9,假设每个“int”为四个字节
长。
所有获取和设置操作都不执行边界检查,因此要特别小心,或者使用构建在缓冲区之上的更安全的alien.array抽象。
数组
阵列是缓冲区,顶部有一层额外的安全和糖。您创建一个数组alien.array(type, length),其中type是数组元素的Alien
类型,长度是数组中有多少个元素。创建数组后ARR你可以得到与元素的类型arr.type,它有多少个元素与arr.length每
个单元的,和大小(以字节为单位)arr.size。底层缓冲区是arr.buffer。
您可以使用arr [i]访问第i个元素,并使用arr [i] = val进行设置。类型转换与缓冲区或函数调用相同。将数组中的字
符串或用户数据存储在数组中,以使其在数组中不被收集。
为了方便起见,alien.array还可以接受其他两种形式:alien.array(type, tab)创建一个与选项卡长度相同的数组,并
使用它的值进行初始化; alien.array(type, length, buf)使用buf创建一个数组作为底层缓冲区。您也可以使用数组的
内容进行迭代arr:ipairs()。
以下示例显示了数组的使用:
local function sort(a, b)
return a - b
end
local compare = alien.callback(sort, "int", "ref int", "ref int")
local qsort = alien.default.qsort
qsort:types("void", "pointer", "int", "int", "callback")
local nums = alien.array(t, { 4, 5, 3, 2, 6, 1 })
qsort(nums.buffer, nums.length, nums.size, compare)
for i, v in nums:ipairs() do print(v) end
这将在控制台上打印数字1到6。
结构
外星人也对声明性结构有基本的支持,也可以在基本缓冲区上实现一层糖。该alien.defstruct(description)函数创建一
个具有给定描述的结构类型,它是具有每个字段的名称和类型的对的列表,其中类型是任何基本的外来类型(结构体内部
没有结构体)。例如:
rect = alien.defstruct{
{ "left", "long" },
{ "top", "long" },
{ "right", "long" },
{ "bottom", "long" }
}
这将创建一个新的结构类型,其中包含四个类型为“long”的字段,并存储在其中rect。要创建一个此结构的实例(由缓
冲区支持)调用rect:new()。然后,您可以像在Lua表上一样设置结构体的字段,例如r.left = 3。要获取底层缓冲区(
例如将其传递给C函数),您必须调用该实例r()。继续的例子:
r = rect:new()
r.left = 2
doubleleft = alien.rectdll.double_left
doubleleft:types("void", "pointer")
doubleleft(r()))
assert(r.left == 4)
您还可以将缓冲区或其他用户数据传递给newstruct类型的方法,在这种情况下,这将是您正在创建的struct实例的后备
存储。这对于解包C函数返回的外部结构很有用。
指针开箱
Alien还提供了三个方便的功能,可让您取消引用指针并将值转换为Lua类型:
alien.tostring使用userdata(通常从具有指针返回值的函数返回),将其转换为char *,并返回一个Lua字符串。您可
以提供一个可选的大小参数(如果您不先Alien 在缓冲区调用strlen)。
alien.toint使用userdata,将其转换为int *,取消引用它并将其作为数字返回。如果你传递一个数字,它假定userdata
是一个包含这个元素数量的数组。
alien.toshort,alien.tolong,alien.tofloat,和 alien.todouble像alien.toint,但与各自的类型转换工作。还提供
无符号版本。
数字alien.to 类型函数使用一个可选的第二个参数来告诉从用户数据解压缩多少项目。例如,如果ptr是一个指向四个浮
点数组的指针,则以下代码解包该数组:
> fs = alien.tofloat(ptr, 4)
> =#fs
4
>
请仔细使用这些功能,不进行任何安全检查。对于更高级的解组使用alien.struct.unpack 功能。
标签
从C库中包装对象时的常见模式是在完整的用户数据中放置一个指向此对象的指针,然后将该用户数据与与字符串标签关
联的metatable关联。此标记用于检查用户数据是否在使用它的每个函数中的有效用户数据。由于userdata是一个完整的
用户数据,它还可以有一个 __gc资源回收的方法。
外星人有三个功能可以让您在扩展上复制此模式:
alien.tag(*tagname*)使用标签标记名创建一个新的metatable(如果不存在),或者使用该标签返回metatable。标签的
命名空间是全局的,所以一个很好的模式是使用模块的名称(如mymod_mytag)将标签名称前缀。
alien.wrap(*tagname*, ...)创建一个完整的userdata,使用与tagname相关联的metatable进行标记,存储您传递的值,
然后返回完整的userdata。有效值为nil,整数和其他用户数据。
alien.unwrap(*tagname*, obj)测试如果obj用 标记名标记,如果不是,则抛出错误,然后返回之前存储的值。
alien.rewrap(*tagname*, obj, ...)用新值替换obj上的元素。如果您传递的值超过obj,则以前会忽略额外的值。如果
你通过较少的tehn obj充满了 零。
例如,假设libfoo有一个create_foo返回Foo*对象的函数。destroy_foo当不再使用这些对象时,必须通过调用来妥善处
理这些对象 。这很容易实现:
local tag_foo = alien.tag("libfoo_foo")
alien.foo.create_foo:types("pointer")
alien.foo.destroy_foo_types("void", "pointer")
function new_foo()
local foo = alien.foo.create_foo()
return alien.wrap("libfoo_foo", foo)
end
tag_foo = {
__gc = function (obj)
local foo = alien.unwrap("libfoo_foo", obj)
alien.foo.destroy_foo(foo)
end
}
然后在任何对Foo*类型进行操作的函数上,首先解开它来获取指针,然后将其传递给libfoo中的函数。
错误代码
几个操作系统函数在一个名为errno的特殊变量上返回错误。用外部电话 获取errno的值alien.errno()。
回调
一些库具有函数,可以使用回调函数,函数库可以调用。大多数GUI库都使用回调函数,但即使是C库也有qsort。外星人
让您可以从Lua功能创建回调alien.callback。你传递函数和库预期的回调原型。Alien将返回一个回调对象,您可以传入
任何回调类型的参数。一个简单的例子,使用qsort:
local function cmp(a, b)
return a - b
end
local cmp_cb = alien.callback(sort, "int", "ref char", "ref char")
local qsort = alien.default.qsort
qsort:types("void", "pointer", "int", "int", "callback")
local chars = alien.buffer("spam, spam, and spam")
qsort(chars, chars:len(), alien.sizeof("char"), cmp_cb)
assert(chars:tostring() == " ,,aaaadmmmnpppsss")
在快速排序函数对就地数组,因此我们必须使用一个缓冲区。
回调可以像Lua一样调用,就像任何其他的Alien功能一样,你可以用他们的“类型”方法自由地改变它们的类型。
魔法数字
C库充满了真正的魔术数字的符号常量,因为在C编译器有机会看到它们之前,它们被预处理器所取代。这意味着所有这些
常量都在头文件上。这也包括图书馆所依赖的结构的布局和大小等。所有这些信息可以从版本到图书馆版本,或从平台更
改为平台。
Alien提供了一个称为常数的实用程序脚本,可以更轻松地处理这些数字。此实用程序在命令行中使用三个参数:定义文
件,要生成的C文件的名称以及在编译和运行时C文件将生成的Lua文件的名称。定义文件可以包含预处理程序指令,空白
行和具有表单标识符或lua_identifier = c_identifier的定义的行。第一种形式相当于identifier = identifier。最好
通过例子解释(从libevent绑定):
#include
#include
EV_SIZE = sizeof(struct event)
EV_READ
EV_WRITE
EV_TIMEOUT
EVLOOP_NONBLOCK
EVLOOP_ONCE
具有预处理器指令的行将逐字复制到C文件 常量生成。上述定义文件生成此C文件:
/* Generated by Alien constants */
#include
#include
#include
#define LUA_FILE "event_constants.lua"
int main() {
FILE *f = fopen(LUA_FILE, "w+");
fprintf(f, "-- Generated by Alien constants\n\n");
fprintf(f, "%s = %i\n", "EV_SIZE ", sizeof(struct event));
fprintf(f, "%s = %i\n", "EV_READ", EV_READ);
fprintf(f, "%s = %i\n", "EV_WRITE", EV_WRITE);
fprintf(f, "%s = %i\n", "EV_TIMEOUT", EV_TIMEOUT);
fprintf(f, "%s = %i\n", "EVLOOP_NONBLOCK", EVLOOP_NONBLOCK);
fprintf(f, "%s = %i\n", "EVLOOP_ONCE", EVLOOP_ONCE);
fclose(f);
}
哪些在编译和运行时在Linux / Intel系统上生成此文件:
-- Generated by Alien constants
EV_SIZE = 84
EV_READ = 2
EV_WRITE = 4
EV_TIMEOUT = 1
EVLOOP_NONBLOCK = 2
EVLOOP_ONCE = 1
这些步骤(生成C文件,编译,生成Lua文件)最好在扩展的构建步骤中完成。