Luaパッケージ&C++実践(一)-LuaとC/C++の基本的なインタラクション

31303 ワード

Luaはコンパクトなスクリプト言語で、それ自体が埋め込みスクリプトとして設計されており、現在のすべてのスクリプトエンジンの中でLuaの速度が最も速い.また、その解釈器は非常に軽量で、その解釈器は200 kにすぎない(バージョンによってはわずかに異なる可能性がある).
Luaプロジェクトには多くの技術点が含まれており、時間をかけて研究することで多くの収穫が得られ、多くのことを学ぶことができます.ホスト言語とのインタラクション、メモリ管理、仮想マシン実装、コラボレーション、クローズドパッケージ、異常キャプチャメカニズムなどが含まれ、その後、徐々に検討する時間があります.
本シリーズは主にLuaパッケージ関連ノートを記録し、主にC++11関連学習と実践を記録する.Luaに関する原理は分からないので、メモの中でもしばらく言わないで、後でLuaを深く勉強する時間があるときは、メモを取ります.
LuaとC/C++のインタラクションベース
LuaとC/C++言語通信の主な方法はLua先進後出(FILO)の仮想スタックである.Luaでは、Luaスタックはstructであり、スタックインデックスの方式は正数でも負数でもよい.違いは、正数インデックス1はスタックの底を永遠に表し、負数インデックス-1はスタックの頂を永遠に表す.
Luaの使用は、Luaのステートマシンlua_Stateに依存し、Luaのスタックもステートマシンに存在する.ここのステータスマシンは、Javaのjvmに似ていて、luaを解析、実行する礎です.LuaがC/C++を呼び出すにしても、C/C++がLuaを呼び出すにしても、Aがデータをスタック、Bがデータをスタックから取り出し、ここのデータはメタデータでもアドレスでもある可能性があります.
LuaとC/C++のインタラクションを行う場合、メソッド、パラメータ、戻り値はスタックに圧入する必要があります.後で例で説明します.
C/C++呼び出しLua
C/C++呼び出しLuaは比較的簡単であるが、C++を使用する場合、Luaヘッダファイルを導入する際には、#include "lua.hpp"、lua.hppの内容は,extern cを用いてコンパイラに通知し,C Linkage方式でコンパイル,すなわちC++のname manglingメカニズムを抑制する.コンパイルエラーが発生します.
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

Luaファイルが必要です
function helloAdd(num1, num2)
    return (num1 + num2)
end

C/C++でLuaを呼び出すには、次の手順に従います.
void testCCallLua(){
    int ret;
    //   :    Lua    
    lua_State * l = luaL_newstate();
    //   :        。       ,        。             luaopen_xxx(l)
    luaL_openlibs(l);
    //   :  (  )   lua  ,lua  main  。  、table、            ,             
    ret = luaL_dofile(l, "../res/test1.lua");
    std::cout<<"doFile : "<< ret<< std::endl;
    //   :     lua  ,               
    ret = lua_getglobal(l, "helloAdd");
    std::cout<<"getFunction : "<< ret<< std::endl;
    //   :             
    lua_pushnumber(l, 10);
    lua_pushnumber(l, 5);
    //   :              ,               ,          ,  lua         
    lua_call(l, 2, 1);
    //   :     ,       ,        。        ,       ,         。           lua_pop        
    double iResult = lua_tonumber(l, -1);
    std::cout<<"result:" << iResult << std::endl;
    lua_pop(l,1);
    //    :     ,          
    lua_close(l);
    l = nullptr;
}

Lua呼び出しC
Lua呼び出しCは、ライブラリを登録し、luaにライブラリをロードすることによって呼び出すこともできるし、直接関数スタックを押すことによって呼び出すこともできる.ここに記録されているのは関数スタックの方式です.
ここでは、呼び出されるC関数を用意します.
//      C  
double cFuncAdd(double a, double b){
    return a+b;
}

// C     ,    Lua   ,         
//              
int luaBindCFuncAdd(lua_State * l){
    double a = luaL_checknumber(l, 1);
    double b = luaL_checknumber(l, 2);
    lua_pushnumber(l, cFuncAdd(a, b));
    return 1;
}

C関数が呼び出され,luaのルールに合致する必要がある.前述したように、CとLuaの相互変調はスタックによって行われ、Lua解釈器はLuaで呼び出された関数を解釈する際にも、関数名、関数パラメータなどによってスタックと相互作用する.したがって、上記luaBindCFuncAddにおいても、実際の実行はスタックから2つのパラメータを順次取り出し、C関数実行を呼び出し、結果をスタックに押し込み、luaはスタックから結果を得る.
Luaはスクリプトを埋め込むだけで、その実行はホストプログラムに依存するので、私たちはやはりCコードを書いてluaを実行する必要があります.そして、上記はC関数を用意しただけですが、このC関数はLuaステータスマシンを通じてluaと連絡を取っていません.
void testLuaCallC(){
    std::cout << "start test Lua Call C --------------------- " << std::endl;
    //      ,     ,  lua 
    lua_State * l = luaL_newstate();
    luaL_openlibs(l);
    // C      ,             lua_State,   int    。
    lua_pushcfunction(l,luaBindCFuncAdd);
    //  C  lua     getglobal,             
    lua_setglobal(l,"cFuncAdd");
    //    lua  ,        lua  ,        
    int ret = luaL_dostring(l,"print('cFuncAdd ret :', cFuncAdd(98,9))");
    std::cout<<"doFile:" << ret << std::endl;
    lua_close(l);
}

CとLuaの相互呼び出しはこのように実は比較的に理解しやすくて、Cの関数をスタックの中に入れてLuaに呼び出されますか、それともluaの中の関数がluaに呼び出されますか、少なくとも表現の上で、区別がなくて、すべて実行する前に方法をスタックに押し込んで、実行する時、名前によって、正しい関数を見つけてスタックの上に置いて、それからパラメータを押し込んで、関数を実行して、スタックの上部から返される結果を取得します.
Lua呼び出しC++
これは相対的に面倒で、上のLuaがCを呼び出したとき、私たちはlua_を知っていました.Pushcfunctionには、固定フォーマットのC関数しか入力できません.LuaがC++を呼び出すことができ、C++でクラスを使用する方法で呼び出すには、C++コードをより多くの処理をしてluaの要求を満たす必要があります.実際、Luaのuserdataを巧みに使うことで、私たちのさまざまなニーズを満たすことができます.
ここでの例では、実際にはuserdata+metatableによってC++からLuaへのマッピングが実現され、C++クラスがLuaのtableにマッピングされる.ここでmetatableはLuaにあり,tableの挙動を変えることができる.Luaでは,各挙動に対応するメタメソッドが関連付けられている.一般的なメタメソッドは、次のとおりです.
__index //  table  table             
__gc	//table    ,      
__newindex //    , __index  ,__index     ,                 
//             ,             。
__add、__sub、__mul、__div、__mod、__unm、__concat、__eq、__lt、__le
__call //  Lua         
__tostring //          ,   java  tostring    

tableにメタテーブルを設定すると、tableに対して操作を実行すると、メタテーブルの定義に従って実行されます.たとえば、tableにメタテーブルが設定され、メタテーブルに__が実現されます.indexメタメソッドはtable.xxxとtable:xxxは先に実行します_indexメソッド、indexは何をすべきかを決めます.
我々は,Luaのこの特性を用いて,C++の呼び出しを実現する.
まず、最終的に期待しているLuaコードを書きます.
--         ,         ,          
operate = OperateCpp()
print("OperateCpp:multiply ret : ", operate:multiply(5.0,12.0))
print("OperateCpp.errorCode is :", operate.errorCode)

C++のクラスと、呼び出したC関数を準備します.

class OperateCpp{
private:
    double x{};
    double y{};
    int type{};
public:

    int errorCode = -1;

    OperateCpp() = default;
    ~OperateCpp(){
        std::cout<<"OperateCpp destroy"<<std::endl;
    }

    double multiply(double x, double y){
        return x * y;
    }
};

//         
static int LuaCreateOperateCpp(lua_State * l){
	//Lua       C++      ,  Lua  userdata     
	//  ,   ,       ,      ,      ,    userdata。    ,            
    auto ** pData = (OperateCpp**)lua_newuserdata(l, sizeof(OperateCpp*));
    //   userdata    ,     C++      
    *pData = new OperateCpp();
    //           OperateCpp  ,      。        ,             。
    //      ,             ,          。
    luaL_getmetatable(l, "OperateCpp");
    //         userdata    ,   -2,    new   ,-1           
    lua_setmetatable(l, -2);
    return 1;
}

//    
static int LuaDestroyOperateCpp(lua_State* L){
    //     
    delete *(OperateCpp**)lua_topointer(L, 1);
    return 0;
}

//     
static int LuaFuncMultiply(lua_State * l){
    auto * oc = *(OperateCpp **)lua_topointer(l, 1);
    auto x = lua_tonumber(l,2);
    auto y = lua_tonumber(l,3);
    auto ret = oc->multiply(x,y);
    lua_pushnumber(l,ret);
    return 1;
}

//            ,          __index  
static int LuaCallIndex(lua_State * l){
    auto * oc = *(OperateCpp **)lua_topointer(l, 1);
    auto filed = lua_tostring(l,2);
    if(strcmp(filed,"errorCode") == 0){
        lua_pushnumber(l, oc->errorCode);
    }else if(strcmp(filed, "multiply") == 0){
        lua_pushcfunction(l, LuaFuncMultiply);
    }
    return 1;
}


次に、C++とLuaのつながりを確立する必要があります.解釈もコード注釈に直接与えられます.

void testLuaCallCpp(){
    std::cout << "start test Lua Call Cpp --------------------- " << std::endl;
    lua_State * l = luaL_newstate();
    luaL_openlibs(l);
    //     ,   OperateCpp   OperateCpp            ,   OperateCpp, Lua     OperateCpp() 
    //        ,           table,        
    lua_pushcfunction(l,LuaCreateOperateCpp);
    lua_setglobal(l,"OperateCpp");
    //    ,            ,        ,                    
    //                     ,     ,       
    luaL_newmetatable(l, "OperateCpp");
    //     __gc  ,       table,              
    lua_pushstring(l,"__gc");
    lua_pushcfunction(l,LuaDestroyOperateCpp);
    //               metatable ,            
    lua_settable(l, -3);
    //    __index    ,       table,                     
    lua_pushstring(l, "__index");
    lua_pushcfunction(l,LuaCallIndex);
    lua_settable(l,-3);
	
    std::string content = loadString("../res/test2.lua");
    int ret = luaL_dostring(l,content.c_str());
    std::cout<<"doFile:" << ret << std::endl;
    lua_close(l);
}


これで、LuaとC/C++の基本的な相互呼び出しは完了しましたが、このように使うとどうしても面倒な感じがします.私はただ簡単なインタラクションをしたいだけで、こんなに多くのことをしなければなりません.何気なく苦痛なので、後でこの複雑な呼び出しをパッケージ化し、LuaとC++のインタラクションをもっと簡単にします.
その他
メモ関連のコードはGithub上ではコードが変動し続け、必要なものがあれば対応する提出を直接見ることができます.このブログは個人学習ノートや興味のある友人の参考としてのみ使用され、謙虚にアドバイスや指摘を受け、ツッコミや批判を受けず、デザイン思想やコードを引用して出典を明記したいので、ForkとStarを歓迎します.wLuaBindコードアドレス
転載を歓迎します.転載は文章の出所を残してください.湖広午王のブログ[http://blog.csdn.net/junzia/article/details/95001209]