运用模版手段向lua中注入c函数

本文介绍了如何使用模板技术在Lua中注册和调用C函数,避免了将每个C函数修改为lua_CFunction类型的需要。通过代理C函数和lightuserdata,实现了C函数的注入,并通过模板推导保持了类型安全。详细讨论了代理函数、参数传递和返回值处理的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在lua中使用c函数 需要预先进行注册 ,lua只接受 lua_CFuntion 类型 (lua.h中的定义typedef int (*lua_CFunction) (lua_State *L) ).返回值为函数实际返回的返回值个数,lua与c的交互式通过lua stack进行,通过lua CAPI 在lua和c中进行参数传递.
//ex1 注册c函数到lua中
int fun(lua_State* l)
{
 //do some thind
 return 0; //返回0个值
}

//将fun注册到lua中去
lua_register(l,"fun",&fun);

//lua中的调用
fun()

lua_register 做两步工作 lua_pushcfunction 和lua_setglobal
lua_pushcfuntion 即 lua_pushcclousre 并且 upvalue 为0
同时使用lua_setglobal 将 lua中 的全局变量 fun指向刚才压入的c闭包

问题: 每一个与lua交互的c函数 必须改为lua_CFuntion 类型

解决方案:
1.使用一个代理c函数来作为与lua的交互 真正的c函数指针 以lightuserdata的形式压入堆栈 并作为代理函数的一个upvalue存在
//用代码说明
//将全局函数注入lua
template<typename Func> //此处的Func为c函数的型别
void def(lua_State* l,char* name,Func func)
{
 //压入函数名字 作为global table的key
 lua_pushstring(l,name);
 lua_pushlightuserdata(l,func);
 push_functor(l,func); //此处让编译器进行template 匹配
 lua_settable(l,LUA_GLOBALSINDEX); 
}

//负责压c函数 无参版本
template <typename RVal>
void push_functor(lua_State* l,RVal (*)())
{
 //代理函数为functor::invoke 一个static函数
 lua_pushcclosure(l,functor<void>::invoke<RVal>,1);//此处的1为一个upvalue 指先前压入的函数指针
}

//仿造可写出一参版本
template <typename RVal,typename Arg1>
void push_functor(lua_State* l,RVal (*)(Arg1))

//无参调用的c函数
template<>
struct functor<void>
{
 //functor中的invoek函数 为宿主程序在lua中的代理
 //lua只能注册 luaCFunction类型  int (*func)(lua_State* l)
 template<typename RVal>
 static int invoke(lua_State* l)
 {
  //将以upvalue保存的函数指针拿出来执行
  //push c函数的返回值到lua stack
  push(l,upvalue<RVal (*)()>(l) ());
  return 1;
 }
 
 //特化版本 函数无返回
 template<>
  static int invoke<void>(lua_State* l)
 {
  //无返回的
  upvalue<void (*)()>(l)();
  return 0;
 }
};

//以制定类型读取upvalue值
template <typename T>
T upvalue(lua_State* l)
{
 return read<T>(l,lua_upvalueindex(1));//c函数指针在upvalueindex 1处
}

//泛化版本的read函数
//在指定索引处读取lua stack中的函数
template <typename T>
T read(lua_State* l,int index)
{
 return lua2type<T>(l,index);
}

//一些简单的特化版本
template <>
int read<int>(lua_State* l,int index)
{
 return lua_tonumber(l,index);
}

template<>
bool read<bool>(lua_State* l,int index)
{
 if(lua_isboolean(l,index))
 {
  return lua_toboolean(l,index)!=0;
 }
 else
 {
  return lua_tonumber(l,index);
 }
}

template<>
char* read<char*>(lua_State* l,int index)
{
 return (char*)lua_tostring(l,index);
}

//将保存在lua中的值 以宿主程序的型别返回回来
template<typename T>
T lua2type(lua_State* l,int index)
{
 return lua2object<T>(l,index);
}

//将lua中的userdata 返回为class object
template<typename T>
T lua2object(lua_State* l,int index)
{
 //先检测是否userdata
 if(!lua_isuserdata(l,index))
 {
  //非userdata
  lua_pushstring(l,"no class at index argument");
  lua_error(l);
 }
 //yes userdata为void* 是一块raw内存
 //从这里开始使用invoke函数 将void* 类型转换为T
 return void2type<T>::invoke(lua_touserdata(l,index));
}

//将void*转换为T的型别
template <typename T>
struct void2type
{
 static T invoke(void* input)
 {
  //将void*转换为T 依据T的型别做不同转换
  //分为三部分
  //1 T为class*  转换为 return (class*)(input)
  //2 T为class   转换为 return *((class*)(input))
  //3 T为class&  转换为 return *((class*)(input))
  //使用type traits技法
  return if_<isptr<T>::value,
   void2ptr<base_type<T>::type>,
   if_<isref<T>::value,
   void2ref<base_type<T>::type>,
   void2val<base_type<T>::type>
   >::type
   >::type::invoke(input);
  
 }
};

//type traits 的简单使用
//型别T是否ptr
template <typename T>
struct isptr
{
 static bool value=false;  
};

template <typename T>
struct isptr<T*> //偏特化版本 型别为T* 则value为true
{
 static bool value=true;
};

template <typename T>
struct isref
{
 static const bool value=false;
};

template <typename T>
struct isref<T&>
{
 static const bool value=true;
};

//一种型别选择器
template <typename A,typename B,bool>
struct if_{};

template <typename A,typename B,true>
struct if_
{
 typedef A type;
};

template <typename A,typename B,false>
struct if_
{
 typedef B type;
};

 if_<isptr<T>::value,
   void2ptr<base_type<T>::type>,
   if_<isref<T>::value,
   void2ref<base_type<T>::type>,
   void2val<base_type<T>::type>
   >::type
   >::type::invoke(input);
 首先检查型别T是否 ptr 如果是void* 转为 T* ,其次检查型别T是否ref 如果是void* 转为T& ,最后void* 转为 T

//base_type 萃取T的型别
template <typename T>
struct base_type
{
  typedef T type;
};

template <typename T>
struct base_type<T*>
{
 typedef T type;
};

template <typename T>
struct base_type<T&>
{
 typedef T type;
};

//void* to class*
template <typename T>
struct void2ptr
{
 static T* invoke(void* input)
 {
  return (T*)(input);
 }
};

//void* to val
template <typename T>
struct void2val
{
 static T invoke(void* input)
 {
  return  *((T*)(input));
 }
};

//void* to ref
template <typename T>
struct void2ref
{
 static T& invoke(void* input)
 {
  return *((T*)(input));
 }
};

当初的pushlightuserdata压入函数指针时 c函数指针转为了void* 如今通过正确的型别转换 c函数指针又再次转型为了正确的函数类型
自push_functor时 c函数的转型类型就已经被编译器确定了  这全部仰赖与template

template<typename Func> //此处的Func为c函数的型别
void def(lua_State* l,char* name,Func func)

不需要写一个wrap function ,无论什么样的c函数 向lua中压入c函数变为了轻松的一句
def(l,"cAdd",&cAdd)
template 的模版推倒会推出函数指针的型别

lua中的调用也是
i = cAdd(1,2)

这其中仍然包括的两个问题是:1. lua传向c 的参数 尚未读出 2.c函数 返回值压入lua stack
这里的c函数是正儿八经的 c 函数 只会返回一个值 所以代理函数 functor::invoke中只有一条压栈指令
并不是为了lua的调用而写的wrap函数 会向 lua stack 压入几个返回值

//lua中c 代理函数 无参版本
template<>
struct functor<void>
{
 template<typename RVal>
 static int invoke(lua_State* l)
 {
  push(l,upvalue<RVal (*)()>(l) ( ));
  return 1;
 }
 
 template<>
 static int invoke<void>(lua_State* l)
 {
  //无返回的
  upvalue<void (*)()>(l)();
  return 0;
 }
};

//lua中c 代理函数 一参版本
template<typename Arg1>
struct functor<Arg1>
{
 template<typename RVal>
  static int invoke(lua_State* l)
 {
  push(l,upvalue<RVal (*)(Arg1)>(l)(read<Arg1>(l,1)));//此处用read<T> 读出index 1 处的唯一一个参数
  return 1;
 }
 template<>
  static int invoke<void>(lua_State* l)
 {
  upvalue<RVal (*)(Arg1)>(l)(read<Arg1>(l,1));
  return 0;
 }
};

如此二参 ,三参 和多参 也可以写了

此处的push为将c 中的值压入lua 中的堆栈 是read的逆过程

//将c的值传入lua中 泛化版本
template <typename T>
void push(lua_State* l,T t)
{
 type2lua<T>::invoke(l,t);
}

//几个简单的特化版本
template <>
void push<int>(lua_State* l,int t)
{
 lua_pushnumber(l,t);
}

template<>
void push<bool>(lua_State* l,bool b)
{
 lua_pushboolean(l,b);
}

template<>
void push<const char*>(lua_State* l,const char* s)
{
 lua_pushstring(l,s);
}

//将class类型的值传入lua中
template<typename T>
struct type2lua
{
 static void invoke(lua_State* l,T val)
 {
  object2lua<T>::invoke(l,val);
 }
};

//class object 传入lua
template <typename T>
struct object2lua
{
 static void invoke(lua_State* l,T val)
 {
  if_<isptr<T>::value,
   ptr2lua<base_type<T>::type>, //指针转lua
   if_<isref<T>::value,
   ref2lua<base_type<T>::type>, //ref转lua
   val2lua<base_type<T>::type> //val转lua
   >::type
   >::type::invoke(l,val);
  
  //查找class object 存根在lua中的class metatable
  meta_push<base_type<T>::type>(l);
  lua_setmetatable(l,-2);
 }
};

//将ptr ref val三种不同类型值压入lua中不同做法
template <typename T>
struct ptr2lua
{
 static void invoke(lua_State* l,T* val)
 {
  if(val) //不为NULL指针
  {
   // new(lua_newuserdata(l,sizeof(T)) T(val);这是错误做法吗
   //将ptruser作为lua中的class object 在宿主中存根
   new(lua_newuserdata(l,sizeof(ptr2user<T>))) ptr2user<T>(val);
  }
  else
  {
   lua_pushnil(l);
  }
 }
};

//压入lua的是一个指针 以userdata方式压入
//宿主程序向lua返回三种class 类型
//这三个user类型的设计 主要还是为了从c中返回class val类型到lua中
//在lua中并不存储整个class obj 这是一个原则 lua只以userdata存储object ptr
//如果返回ref 也照存ptr
//如果返回val 就在Rua中 照原样给他存一份 给lua一个指向的ptr 以val2user形式存贮
struct user
{
 void* m_p;
 user(void* p):m_p(p){}
 virtual ~user(){}
};

//如果向lua中返回值 则重new一个object 出来 将ptr交给lua
template <typename T>
struct val2user : public user
{
 val2user() : user(new T){}
 
 template<typename Arg1>
  val2user(Arg1 arg1) : user(new T(arg1)){}
 
 template<typename Arg1, typename Arg2>
  val2user(Arg1,Arg2) : user(new T(arg1,arg2)){}
 
 template<typename Arg1,typename Arg2,typename Arg3>
  val2user(Arg1,Arg2,Arg3) : user(new T(arg1,arg2,arg3)){}
 
 //这个object是自向lua中返回时声称的 必须自己销毁
 ~val2user(){delete (T*)(m_p);}
};

template<typename T>
struct ptr2user : public user

 ptr2user(T* t):user((void*)t){}
};

template<typename T>
struct ref2user : public user
{
 ref2user(T& t):user((void*)&t){}
};

template<typename T>
struct ref2lua
{
 static void invoke(lua_State* l,T& val)
 {
  new(lua_newuserdata(l,sizeof(ref2user))) ref2user<T>(val);
 }
};

template<typename T>
struct val2lua
{
 static void invoke(lua_State* l,T& val)
 {
  new(lua_newuserdata(l,sizeof(val2user))) val2user<T>(val);
 }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值