博客

  • 勿用屠龙来杀猪-论如何正确整合Lua与C++

    经常有人问到关于Lua的问题是“Lua如何能使用C++的成员变量?”“Lua如何调用C++类的成员函数?”“C++的复杂数据结构(如数组)如何传递进入Lua让Lua可以使用?”

    这些问题之频繁,几乎每天都可以看到。问题的起源也很简单,这些提问者大多是C++程序员,公司需要使用Lua,他们就想如何能够“完美无缝”整合C++和Lua。

    如果在谷歌中文(google.cn)搜索Lua关键字,第一页大概前五六个网址中会有云风的这篇文字《Lua 不是 C++》,里面的结论部分十分精彩:

    对于那些新接触 lua 的 C/C++ 程序员来说,我的第一条建议通常是:看看 lisp/scheme 吧,可能 lua 的血统里,scheme 的成分比 C 更多一些。要不玩一下 Haskell ,增进对函数式编程的了解。C++ 借助 template 是可以玩玩函数式编程,但很少有人真的去用。进入 lua 的领域后,你得正正经经的理解一下了。

    Lua与C++不是一种语言,Lua的产生也不是为了让C++这个大奶高兴,它的地位是独立的。

    我在twitter上发了这样一些感慨:

    感觉这里面有百分之五十的问题都是c++如何导出所有类型给lua,lua如何调用c++成员变量、类成员、内部函数。这些人太纠结了!思路不对,就跟把c++当做高级c来使用一样。展开一点说,这种用法的来源,首先是设计人员脑子不清楚了,lua与c++的边界完全混在一起,没有把接口最小化,以及适当简化,就是c++完全贴到lua上。清晰地api(通用意义上的),就是定义适当的操作,可以扩展,但是不过分耦合。lua和c++应该是哑铃状连接,而不是水桶。

    为什么Lua与C++无缝整合有问题,原因很简单:任何一个有经验的程序员都知道,如果两个系统之间是紧密相连到成员函数成员变量都可以随便调用,那么这两个系统的耦合度一定是相当高,容易出现的问题就是难扩展、难修改、难维护。这两个系统不如直接整成一个系统算了。

    如何正确使用Lua与C++?

    首先第一个问题是,你的系统谁是主导?Lua还是C++?首先搞清楚这个问题。因为主导的部分代码将作为框架出现,负责调用或者响应事件、逻辑。如果Lua与C++的代码都做同样的工作,那么这个系统的设计是有问题的。

    第二个问题是,你的系统里面,作为主导部分想开放那些接口?(我们姑且认为Lua与C++的中间连接部分为接口interface)。两个系统之间,不需要完全整合,只要开放必要的接口或者说是API即可。在著名的《Designing Qt-Style C++ APIs》中,作者Matt总结了下面6个优秀API的特点:

    总而言之,Lua本身有它自身的特点,如函数式编程,尾调用,变量无类型等等,这些特点与C++是截然不同的,如果你非要把C++与Lua弄得无缝整合,将来一定会因为这个高耦合产生各种各样的问题。

    就如我在twitter中说的那样:编程弄得这么纠结,何必呢?!

  • for douban

    doubanclaim016a1cb97bb3d39b

  • 一个VARIANT引发的bug

    昨天一位同事跟领导讨论过程中提到,他认为微软Visual c++6.0编译器的最大优化有bug。因为以前没有使用最大优化的时候代码没有问题,把project setting改成最大优化以后就出现bug了。

    因为这个max speed optimization设置修改是我提出来的,所以感觉有必要看看原因到底是什么。因为我是不相信这种“微软编译器优化能导致接口调用出错”的说法,往往此类问题就出在我们自己的代码上面。

    在这个同事的电脑上重现了一下这个问题,然后回到我自己电脑上,果然也可以重现,这就有点意思了。

    在代码中加入断点,发现是某个接口函数调用失败,这也是那个同事所谓的“最大速度优化导致接口调用失败”说法的起源,不过我相信,这之前的代码一定已经有问题了。

    查看运行时的调用栈,在几个相关函数加入log信息,然后跑了一遍错误的情况(setting为最大优化),再跑一次结果正确的情况(setting为不优化)。然后使用winmerge比较两次log的文本,发现它们俩有不同的输入参数。

    为何同样的代码,只是修改了project setting就有不同的结果呢?难道真的是VC++出了问题被我这个同事抓到bug?

    我又跑了几次错误的情况,发现输入参数字符串的最后一部分是随机变化的(正常情况应该是不变的)。我记起一种可能性,当我们使用最大优化的时候,某些没有初始化的变量会有这种可能。

    从调用栈上溯,发现这个参数是来自另外一个接口,它的调用方式类似这样:

    CComVariant var;
    p->GetAddr(&var);
    CString str.Format("aaaa %d", var.uiVal);

    uiVal的定义是unsigned short,也就是一个2Bytes数据,可是查看GetAddr()这个函数,里面代码类似这样:

    HRESULT CAaaa::GetAddr(VARIANT* pVar)
    {      
         CComVariant var(m_addr); // 这里的m_addr的声明是BYTE m_addr;
         return var.Detach(pVar);
    }

    好像能抓到一些问题的原因了。我们查看VARIANT的定义可以发现它内部包含了一个union,里面有BYTE bVal;有unsigned short uiVal;有unsigned long ulVal;

    OK,原因很清楚了。我们在GetAddr中赋值的时候,只(使用)设置了VARIANT的联合中1个BYTE的数据,而我们在后面的代码里使用uiVal形式,实际上读取了2个BYTE,高位部分的数据是无效的。通过观察随机数的16进制格式也可以发现,正确数据应该为0x00,我们得到的错误结果都是0x5800,0x6300,0xCC00这样的。

    知道了问题的原因该如何修改呢?有两种办法,一个是在结果中也是使用bVal,这是最好的办法。另外,如果要使用比赋值时数据长度更宽的类型,一定要通过var.ChangeType(VT_UI2);这样的类型转换,保证高位部分数据有效。

    改完了这部分代码,程序在最大优化的设置下一样运行正确,根本就不是什么设置导致的问题,就是代码有问题。

    另外非常需要注意的是,这种bug跟优化不优化没有任何联系,而是跟CComVariant以及VARIANT这种复杂数据结构里面的union有关系,CComVariant好像没有对VARIANT的数值成员做清零操作,而是调用了VariantInit()。使用union的时候都需要注意类似的情况。

    我构造了一个test case可以在不设置最大优化的情况下一样出现类似问题。如果认为这是“最大速度优化导致接口调用失败”,只是看到了一种表象,真正的原因离着远呢。

  • Lua源代码阅读分析问题列表

    我个人的习惯是带着问题去研究一个新题目,比如这次阅读Lua代码,暂列下面这些问题。

    1)什么是基于栈、基于寄存器的虚拟机(VM)设计?Lua如何实现基于寄存器的设计?

    2)Lua如何解析一段代码,生成中间代码?

    3)Lua如何使用VM运行中间代码?

    4)Lua如何实现GC机制?

    5)ipairs与pairs的不同(这是前几天写代码时发现的)?

    6)Lua如何实现闭包功能?

    7)Lua如何实现协程功能?

    8)Lua与c语言交互时栈的变化?

    9)如何为Lua增加一个continue关键字?

    10)Lua代码中常用的C语言编程技巧有那些?

    11)table如何实现的?

    12)如何实现的hook机制?

    13)如何实现的debug机制?

    暂列这些,以后再添。

  • Implement Object-Oriented in Lua

    Lua is not a Object-Oriented language. If we want to use concepts like “class, constructor, desctructor, member function”, we must use table or metatable to simulate.

    <Programming In Lua> introduces a method to implement OO in lua. http://www.lua.org/pil/16.html

    (更多…)