<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      把C++綁定到Lua腳本的方法很多。但是在C++11之前,都沒有太好的辦法。比如tolua++,這個工具需要手動編寫額外的pkg文件,每次需要手動執行命令生成額外的C++文件,使用比較繁瑣,所以逐漸沒落了。

      而我自己用的是一個自己實現的綁定庫,只是這個綁定庫比較簡單,只能綁定int (*lua_CFunction) (lua_State *L)這種特定格式的函數,和lua的pushcfunction比,也就是多了能綁定類成員函數的功能。所以復雜一點的函數,都需要寫一個專門的函數包裝一下,太麻煩。

      雖然我經常在以C++為底層,Lua為腳本的框架上寫代碼,但其實很少需要把C++接口綁定到Lua。原因是框架已經很成熟,邏輯大部分都在Lua實現,該有的接口都有了,需要綁定新接口的情況少之又少,所以也就一直這么將就用著。自從前幾年項目把C++的標準提高到C++11以后,可以支持parameter pack了,對一些利用parameter pack來實現的Lua綁定庫實屬眼饞,終于決定要更換自己的綁定庫了。

      開源的綁定庫是很多的,但看了一圈后,發現這些庫的實現太復雜。比如sol2,實在是太過復雜了一點,出現問題自己很難調試,里面的很多功能自己也用不到。看了下他們的實現,基本都是利用parameter pack把參數展開,感覺并不復雜,于是動了自己實現一個庫的念頭。

      實現這個庫的初始版本并不困難,但修修補補持續了好長時間,現在有時間就整理一下遇到的問題。

      C++與Lua的基礎交互機制

      Lua本身就提供一套完善的C與Lua交互機制,任何一個格式為int (*lua_CFunction) (lua_State *L)的函數都可以利用lua_pushcfunction注冊到Lua腳本,從而實現在Lua調用。C++與Lua的交互也是基于這個機制,但它首先要兩個問題,一個是C++是有對象的,所以需要能把成員函數函數注冊到Lua。比如

      class Test
      {
      public:
          int test(int a, double b, const char *c);
      };
      

      這顯然是一個成員函數,調用方式為this call,如何把它轉換為C方式的int (*lua_CFunction) (lua_State *L)函數呢?眾所周知,在Lua中是可以obj:func(a,b,c)這樣寫的,表示調用obj的func函數,它其實是一個語法糖,等同于obj.func(obj,a,b,c),但是恰好這個機制與C++中的thiscall是非常類似的,obj相當于this指針,它在Lua棧的第一個位置,其他是參數即可。這樣問題就解決了。

      另一個問題是我們希望注冊到Lua的函數并不都是這個格式的,比如沒有返回值 ,參數并不是lua_State*等等。比如

      int test(int a, double b, const char *c);
      

      怎么自動把參數、返回值都不匹配的函數轉換為一個特定格式為int (*lua_CFunction) (lua_State *L)的函數

      對于問題1,在C++11之前通常只能手寫綁定函數或者用工具自動生成,頂多也只是用一些宏來輔助一下,沒法做到參數的自動推導,非常繁瑣。而parameter pack允許可變參數作為模板參數,奠定了“自動推導”這個基礎。以上面的test函數為例,手寫時綁定函數時,是這樣的:

      int test_binding(lua_State *L)
      {
          // 取參數
          int a = lua_tonumber(L, 1);
          // ... 其他參數
      
          // 返回參數
          lua_pushnumber(L, v);
          return 1;
      }
      

      可見,沒法自動推導的難點在于函數的參數數量、類型,還有返回值的類型都是不一樣的,才沒法做到自動。而現在利用parameter pack,可以把參數和返回值取出來

      template <typename C, typename Ret, typename... Args> // 返回值、參數都在這里了
      class ClassRegister<Ret (C::*)(Args...)>
      {
      };
      
      ClassRegister<test> cr; // 模板參數傳入test函數,將會自動推導出返回值Ret和各個參數的類型、數量。
      

      既然能把參數和返回值取出來,那么意味著整個過程就可以做到自動了,那具體是怎么做到的呢?

      首先是返回值Ret,這個比較容易理解。在C++中,返回值只有void和其他類型這兩種,所以只需要區分void和其他就行。

              static int caller(lua_State *L, const std::index_sequence<I...> &)
              {
                  if constexpr (std::is_void_v<Ret>)
                  {
                      ((*ptr)->*fp)(lua_to_cpp<remove_cvref<Args>>(L, 2 + I)...);
                      return 0;
                  }
                  else
                  {
                      cpp_to_lua(L, ((*ptr)->*fp)(
                                        lua_to_cpp<remove_cvref<Args>>(L, 2 + I)...));
                      return 1;
                  }
              }
      

      其余是參數,參數需要數量和類型才能進一步處理。C++11提供了typename... Args這種寫法,當然也提供了遍歷它的方式。在這里,由于要從Lua的棧上取值,需要構建一個棧索引,所以用make_index_sequence比較合適。

          template <typename C, typename Ret, typename... Args>
          class ClassRegister<Ret (C::*)(Args...)>
          {
          private:
              static constexpr auto indices =
                  std::make_index_sequence<sizeof...(Args)>{};
      
              template <auto fp, size_t... I>
              static int caller(lua_State *L, const std::index_sequence<I...> &)
              {
                  T **ptr = (T **)luaL_checkudata(L, 1, class_name_);
      
                  if constexpr (std::is_void_v<Ret>)
                  {
                      ((*ptr)->*fp)(lua_to_cpp<remove_cvref<Args>>(L, 2 + I)...);
                      return 0;
                  }
                  else
                  {
                      cpp_to_lua(L, ((*ptr)->*fp)(
                                        lua_to_cpp<remove_cvref<Args>>(L, 2 + I)...));
                      return 1;
                  }
              }
      };
      

      從上面的代碼可以看到,當注冊test函數時,用ClassRegister<test> cr來實例化一個ClassRegister,這樣test函數的返回值和參數都在ClassRegister的模板參數中了,同時用make_index_sequence根據參數個數生成一個0 1 2 3這樣的序列I。然后取參數時,用lua_to_cpp(Args(L, 2 + I) ...)依次從Lua的棧上取值。lua_to_cpp(Args(L, 2 + I) ...)的意思是把參數Args一個個展開,然后以(L, 2 + I)作為參數去調用模板函數lua_to_cpp,代入test函數的參數,就依次調用

      template <> inline int lua_to_cpp<int>(L, 2 + 0);
      template <> inline double lua_to_cpp<double>(L, 2 + 1);
      template <> inline const char *lua_to_cpp<const char *>(L, 2 + 2);
      

      只要我們實現了各個類型的lua_to_cpp函數,那Lua棧的的參數就會被一個個取出來。現在有了this指針,有了函數指針,有了參數,就可以正確調用C++的函數了。

      到了這里,一個基礎的C++綁定Lua的機制也就完成了。但原理歸原理,實際還會遇到許多問題。

      lua_CFunction、C函數、成員函數、lua_CFunction成員函數

      綁定不同類型參數的函數是實現了,可有時候,我們也希望不要自動推導參數而是手動一個個從Lua棧上取出參數。比如我們要寫一個sha1函數,支持傳多個字符串,自動把它們拼接起來計算出sha1值。

      local val = sha1("abc", "def")
      local val2 = sha1("abcdef")
      
      assert(val == val2)
      

      這樣,用const char *sha1(const char *str)就不合適,沒法實現支持傳入任意數量字符串,而用int sha1(lua_State *L)就可以實現。所以需要對lua_CFunction格式的函數進行特殊處理,不自動推導而是直接push到Lua。同樣的,成員函數、static函數也是需要做一些特殊處理。最終,寫了一串if else來特殊處理不同類型的函數

          template <auto fp> void def(const char *name)
          {
              lua_CFunction cfp = nullptr;
              if constexpr (std::is_same_v<decltype(fp), lua_CFunction>)
              {
                  cfp = fp;
              }
              else if constexpr (!std::is_member_function_pointer_v<decltype(fp)>)
              {
                  cfp = StaticRegister<decltype(fp)>::template reg<fp>;
              }
              else if constexpr (is_lua_func<decltype(fp)>)
              {
                  cfp = &fun_thunk<fp>;
              }
              else
              {
                  cfp = ClassRegister<decltype(fp)>::template reg<fp>;
              }
      
              luaL_getmetatable(L_, class_name_);
      
              lua_pushcfunction(L_, cfp);
              lua_setfield(L_, -2, name);
      
              lua_pop(L_, 1); /* drop class metatable */
          }
      

      remove_cvref

      在C++中,可以把函數或者變量指定為const,參數可以是引用,比如

      int get() const
      {
      }
      
      void set(Val &v)
      {
      }
      

      在模板推導中,加不加const和引用,推導出來的類型是不一樣的,但對于C++和Lua交互來說,這個類型就是一樣的,比如從Lua傳進來的this指針,不存在是否為const這個說法。理想情況下,我們可以規定push到Lua的函數,不能加const這種修飾詞,參數不作引用。但我在實際使用過程中,偶爾遇到一個函數,既需要在C++中調用,又需要在lua中調用,這時候又不想再多寫一個專門push到lua的函數,所以在類型推導過程中往往多加了remove_cvref<Args>這個來去掉修飾詞。

      這提供了便利,但也增加了風險,假如有些人就直接修改了lua的一些引用呢?程序是真可能會出問題。

      構造函數

      C++的構造函數是個麻煩事,因為它和其他函數不一樣,是沒有返回值的,而且構建函數可以有多個的。那這就意味著上面的推導是解決不了這個問題。我最初的想法是每個類push到C++時,簡單地調用默認構造函數,參數通過其他函數傳入即可。但后來發現不行,比如說有些類是單例,可不希望有人在Lua另外創建一個實例。

      最終我覺得比較穩妥的方案是:如果提供了默認構造函數,則使用默認構造函數,否則需要手動指定構造函數。若沒有默認構造函數,也沒有指定構造函數,則無法在Lua創建一個C++對象。

      重載

      重載意味著同一個函數名有多個函數,上面通過函數指針直接推導出函數參數和返回值的機制就會失效。目前沒有太好的解決方案,可以像Sol2那樣提供一個模板物化機制,或者用lambda來包一層。但我的意見是,push到Lua的函數不要有重載,換個函數名。

      重載實現起來太過于麻煩,我沒有興趣去做這個。

      C++調用Lua函數

      需要在C++中調用Lua函數時,我原來一直是手動push參數,再直接調用lua_pcall的,畢竟C++調用Lua的地方總共加起來也沒有幾處。但是一想到C++綁定Lua的庫都實現了,這個不包裝一下實在說不過去。C++調用Lua,意味著參數是C++的,那它的類型就是確定了的,這個通過模板就能解決。具體的方案在[Howling at the Moon - Lua for C++ Programmers - Andreas Weis - CppCon 2017](https://github.com/CppCon/CppCon2017/blob/master/Presentations/Howling at the Moon - Lua for C%2B%2B Programmers/Howling at the Moon - Lua for C%2B%2B Programmers - Andreas Weis - CppCon 2017.pdf)上也有說過,我這里就不再說了。

      
      /**
       * 調用lua全局函數,需要指定返回類型,如call<int>("func", 1, 2, 3)。錯誤會拋異常
       * @param name 函數名
       * @param Args 參數
       */
      template <typename Ret, typename... Args>
      Ret call(lua_State *L, const char *name, Args... args)
      {
      #ifndef NDEBUG
          StackChecker sc(L);
      #endif
      
          lua_getglobal(L, "__G_C_TRACKBACK"); // 需要自己在Lua實現trace函數
          assert(lua_isfunction(L, 1));
          lua_getglobal(L, name);
      
          (lcpp::cpp_to_lua(L, args), ...);
      
          const size_t nargs = sizeof...(Args);
          if (LUA_OK != lua_pcall(L, (int32_t)nargs, 0, 1))
          {
              std::string message("call ");
              message = message + name + " :" + lua_tostring(L, -1);
              lua_pop(L, 2); // pop error message and traceback
      
              throw std::runtime_error(message);
          }
          Ret v = lua_to_cpp<Ret>(L, -1);
          lua_pop(L, 2); // pop retturn v and traceback function
      
          return v;
      }
      

      其他問題

      1. 為什么不直接用fp(lua_to_c<Args>(L, ++i), lua_to_c<Args>(L, i), ...)而用make_index_sequence
        上面的代碼中,從Lua堆棧取參數時,是依次從棧位置1 2 3...取參數,那為什么不直接使用一個簡單的++i呢?

      嗯,一開始我確實是這樣寫的,而且跑起來確實沒出問題。但后來在Linux下編譯出現'multiple unsequenced modifications to 'i' [-Wunsequenced]'這個警告,我才意識到,lua_to_c是把參數從lua取出,放到C++的棧上作為fp的參數去調用。但在不同平臺,參數入棧的順序是由調用約定決定的,順序是不一樣的,這++i的值就會不一樣,程序就要出bug了。

      1. 為什么用函數指針而不用upvalue
        許多C++綁定Lua的庫,原始的函數指針是存在push到lua函數的upvalue中,而我寫的是放在模板函數的參數auto fp中。我的本意是,通過模板參數調用肯定會比取upvalue更快,在編譯時就已確定好,無需要管理。而其他庫會放upvalue,是因為他們允許動態綁定,有一套生命周期管理,可以動態創建和釋放這些函數。

      2. 異常安全問題
        C++與Lua交互一直有一個問題,C++中的對象是依賴C++本身的異常機制來構造和銷毀的,即有錯誤發生,應該要拋一個異常才行。但是Lua使用的是C的異常機制,調用long jump,這可能會導出一些對象的析構函數沒有調用。

      當然可以以C++的方式編譯Lua,但這沒法保證。而我也沒找到好的處理方式,也從未見過一了百了,完美的處理方式。但根據我的經驗,只要你不是C++調用Lua再調用C++再調用Lua這樣穿插著調用,并且在調用的過程中手動創建了對象,而又不愿意用pcall,一般是沒有問題的。我可以保證一次庫的調用安全,但沒法保證多次。

      例如,在Lua中調用一個C++函數,其中有一個參數是std::string類型,那它就會創建一個std::string對象。接著發現后面的參數不匹配,這時候會拋一個runtime_error,保證std::string對象,然后在最外層的函數catch這個runtime_error,再調用luaL_error,這樣可以保證庫接口的安全性。但這個luaL_error的影響,如果回到lua層沒有xpcall而導致越過了一些C++代碼,那就得由寫代碼的人負責了。

      還有許多的細節,比如如果把一個類注冊到Lua,如何把一個已有的對象指針push到Lua而不gc掉等等,這里就不再細說了。原本只想簡單地實現,但修修補補了幾回,也有一千行代碼了,變得比預想中復雜了。整個代碼我放在了lcpp.hpp中,有興趣的可以去看代碼。

      posted on 2025-10-07 17:10  coding my life  閱讀(23)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品一区二区不卡91| 中国亚州女人69内射少妇| 思思热在线视频精品| 亚洲中文字幕在线无码一区二区| 国产又粗又猛又爽又黄 | 国产综合色在线精品| 国产超碰无码最新上传| 欧美综合人人做人人爱| 国产成人精品一区二三区| 国产mv在线天堂mv免费观看| 绝顶丰满少妇av无码| 慈溪市| 日本东京热一区二区三区| 日本黄页网站免费观看| 国产内射XXXXX在线| 99久久免费精品国产色| 一区二区三区成人| 99精品国产一区二区三| 亚洲国产美女精品久久久| 少妇人妻偷人精品系列| 久久精品国产亚洲av品| 亚洲成A人片在线观看无码不卡| 这里只有精品免费视频| 久久精品99国产国产精| 天天做天天爱夜夜爽导航| 国产日韩精品视频无码| 少妇私密会所按摩到高潮呻吟| 久久人妻夜夜做天天爽| 中文国产成人精品久久不卡| 亚洲大尺度一区二区三区| 东京热一精品无码av| 好男人好资源WWW社区| 中文在线а√天堂| 中文字幕av无码免费一区| 亚洲av色在线播放一区| 欧美成本人视频免费播放| 午夜免费无码福利视频麻豆| 成人乱码一区二区三区四区| 免费特黄夫妻生活片| 久久狠狠一本精品综合网| 日韩有码中文字幕国产|