C++ and Lua Integration Using a Template

This C++ code demonstrates how to create a template to call C++ functions from Lua scripts. The code is composed of several key sections, including class methods, Lua-to-C++ function bindings, and the process of registering those functions in Lua.
I am very sorry. This is an example on macOS.

1. LuaFunctions Class
The LuaFunctions class defines three member functions that are callable from Lua:
• myFunction: Accepts an integer argument from Lua, prints a message to the C++ console, and returns twice the input value back to Lua.
• luaAdd: Takes two integer arguments from Lua and returns their sum.
• luaSubtract: Takes two integer arguments from Lua and returns their difference.
These functions demonstrate how C++ code can manipulate Lua stack values and return results.

2. Global Functions for Lua
To expose C++ class methods to Lua, three global functions (my_function, lua_add, and lua_subtract) are created. These functions act as bridges between Lua and the LuaFunctions instance:
• my_function: Calls LuaFunctions::myFunction.
• lua_add: Calls LuaFunctions::luaAdd.
• lua_subtract: Calls LuaFunctions::luaSubtract.
An instance of LuaFunctions (luaFunctionsInstance) is used to ensure that the member functions are accessible from these global functions.

3. luaL_Reg Table for Function Registration
The luaL_Reg structure is used to define the list of functions that will be registered in Lua. The array mylib contains mappings between the function names that Lua will call (e.g., "C_add", "C_function") and the corresponding C++ functions (lua_add, my_function, etc.).
The nullptr entry marks the end of the array, as required by the Lua C API.

4. Function Registration in Lua (register_functions)
The register_functions function is designed to register the provided C++ functions into the Lua state. It allows for registration either in the global namespace or within a specific namespace (e.g., "Name" or "MK").\
• If NameSpace is nullptr or an empty string, the functions are registered globally.
• Otherwise, a new Lua table is created, and the functions are registered under that table (i.e., namespace).
This function relies on Lua’s lua_register and luaL_setfuncs APIs to bind C++ functions to Lua functions.

5. Main Program Flow
In the main function, the Lua environment is initialized with luaL_newstate, and Lua standard libraries are loaded with luaL_openlibs. Then, the register_functions function is called three times:
• First, to register the C++ functions globally.
• Second, to register the same functions under the "Name" namespace.
• Third, to register the same functions under the "MK" namespace.
After registering the functions, the Lua script script.lua is executed using luaL_dofile. If the script encounters any errors, they are printed to the console, and the Lua environment is closed.

6. Running the Code
When this program runs, the Lua functions defined in script.lua will call the corresponding C++ functions. The Lua functions C_add, C_subtract, and C_function will trigger the C++ functions lua_add, lua_subtract, and my_function, respectively. The same functions can be accessed from different namespaces ("Name", "MK"), showcasing how to organize and call C++ code within Lua namespaces.

This code provides a basic but powerful template for integrating C++ functionality into Lua, enabling bidirectional communication between Lua scripts and C++ code. It is especially useful for applications that require complex logic written in C++ while maintaining flexibility in scripting via Lua.

main.cpp

#include <iostream>
#include <lua.hpp>

class LuaFunctions {
public:
  // Sample member function for Lua
  int myFunction(lua_State *L) {
    int arg = luaL_checkinteger(L, 1);
    std::cout << "C++ member function called with argument: " << arg
              << std::endl;
    lua_pushinteger(L, arg * 2);
    return 1;
  }

  // Sample member function for Lua
  int luaAdd(lua_State *L) {
    int a = luaL_checkinteger(L, 1);
    int b = luaL_checkinteger(L, 2);
    lua_pushinteger(L, a + b);
    return 1;
  }

  // Sample member function for Lua
  int luaSubtract(lua_State *L) {
    int a = luaL_checkinteger(L, 1);
    int b = luaL_checkinteger(L, 2);
    lua_pushinteger(L, a - b);
    return 1;
  }
};

// Instance of LuaFunctions for Lua API
LuaFunctions luaFunctionsInstance;

// C++ function to be called from Lua
extern "C" int my_function(lua_State *L) {
  return luaFunctionsInstance.myFunction(L);
}

// C++ function to be called from Lua
extern "C" int lua_add(lua_State *L) { return luaFunctionsInstance.luaAdd(L); }

// C++ function to be called from Lua
extern "C" int lua_subtract(lua_State *L) {
  return luaFunctionsInstance.luaSubtract(L);
}

/**
 * @brief Functions information to be registered to Lua.
 *  .name : Function name in Lua
 *  .func : Function pointer
 *  nullptr : End of the array
 */
static const struct luaL_Reg mylib[] = {
    {"C_add", lua_add},
    {"C_subtract", lua_subtract},
    {"C_function", my_function},
    {NULL, NULL} // End of the array
};

/**
 * @brief Register functions in the given table to the Lua state.
 *
 * @param L Lua state
 * @param lib_table Function registration table
 * @param NameSpace If nullptr, register to the global namespace.
 */
void register_functions(lua_State *L, const struct luaL_Reg *lib_table,
                        const char *NameSpace = nullptr) {
  if (NameSpace == nullptr || strlen(NameSpace) == 0) {
    // Register to the global namespace
    const luaL_Reg *func = lib_table;
    while (func->name != nullptr) {
      lua_register(L, func->name, func->func);
      func++;
    }
  } else {
    // Register to the specified namespace
    lua_newtable(L);
    luaL_setfuncs(L, lib_table, 0);
    lua_setglobal(L, NameSpace);
  }
}

int main(int argc, char *argv[]) {
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);
  register_functions(L, mylib);         // Register to global namespace
  register_functions(L, mylib, "Name"); // Register to "Name" namespace
  register_functions(L, mylib, "MK");   // Register to "MK" namespace

  if (luaL_dofile(L, "script.lua") != LUA_OK) {
    fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
    lua_close(L);
    return 1;
  }

  lua_close(L);
  return 0;
}

script.lua

-- Calling C++ functions directly in the global namespace
print("Global namespace:")
local result1 = C_add(10, 5)         -- Calls lua_add function
local result2 = C_subtract(20, 5)    -- Calls lua_subtract function
local result3 = C_function(6)        -- Calls myFunction

print("C_add(10, 5) = " .. result1)      -- Should print: 15
print("C_subtract(20, 5) = " .. result2) -- Should print: 15
print("C_function(6) = " .. result3)     -- Should print: 12

-- Calling functions inside the "Name" namespace
print("\n'Name' namespace:")
local result4 = Name.C_add(15, 5)
local result5 = Name.C_subtract(30, 10)
local result6 = Name.C_function(8)

print("Name.C_add(15, 5) = " .. result4)     -- Should print: 20
print("Name.C_subtract(30, 10) = " .. result5) -- Should print: 20
print("Name.C_function(8) = " .. result6)      -- Should print: 16

-- Calling functions inside the "MK" namespace
print("\n'MK' namespace:")
local result7 = MK.C_add(7, 3)
local result8 = MK.C_subtract(25, 12)
local result9 = MK.C_function(9)

print("MK.C_add(7, 3) = " .. result7)     -- Should print: 10
print("MK.C_subtract(25, 12) = " .. result8) -- Should print: 13
print("MK.C_function(9) = " .. result9)      -- Should print: 18

CMakeLists.txt

#[[
Directory structure for this example:

$ tree .
.
├── CMakeLists.txt
├── build
├── lua
│   └── script.lua
└── src
    └── main.cpp

Goal
    1. Generate a shared library for Lua bindings
    2. Copy all .lua files from the lua directory to the build directory after the library is built

Instructions
    1. Install Lua Homebrew package:
        $ brew install lua
    2. Write your C code in the src directory
    3. Write your Lua code in the lua directory
    4. Run the following commands:
        $ cd build
        $ cmake ..
        $ make
        $ ./a.out
    
    5. Enjoy! (^_^)
    
]]

# Minimum CMake version required
cmake_minimum_required(VERSION 3.20)

# Get the name of the directory where CMakeLists.txt is located
get_filename_component(DIR_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)

# Set the project name
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
    project(a.out VERSION 0.0.1)
else()
    project(${DIR_NAME}.out VERSION 0.0.1)
endif()

# Generate compile_commands.json for clangd, ccls, etc. in the build directory
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Set the default build type if not specified (Release, Debug, RelWithDebInfo, MinSizeRel, Custom)
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
    message(STATUS "CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
endif()


# Generate compile_commands.json for tools like clangd, ccls, etc.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Find all source files in the src directory
#[[ Adds an executable to the project using the specified source files. ]]
if(EXISTS ${PROJECT_SOURCE_DIR}/src)
    set(LOCAL_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src)
else()
    set(LOCAL_SOURCE_DIR ${PROJECT_SOURCE_DIR})
endif()

file(GLOB SRC
    # C/C++
    "${LOCAL_SOURCE_DIR}/*.cpp"
    "${LOCAL_SOURCE_DIR}/*.cc"
    # "${LOCAL_SOURCE_DIR}/*.cxx"
    "${LOCAL_SOURCE_DIR}/*.c"
)

add_executable(${PROJECT_NAME}
    ${SRC}
)


# Find the Lua package (headers and libraries)
find_package(Lua REQUIRED)

# Include Lua's header files in the build
target_include_directories(${PROJECT_NAME} PRIVATE ${LUA_INCLUDE_DIR})

# Link Lua libraries with the generated shared library
target_link_libraries(${PROJECT_NAME} PRIVATE ${LUA_LIBRARIES})

# Add the -fPIC compiler option, necessary for shared libraries
target_compile_options(${PROJECT_NAME} PRIVATE -fPIC)

# Specify the C,C++ standard to be used (C17,C++17 in this case)
target_compile_features(${PROJECT_NAME} PRIVATE c_std_17 cxx_std_17)

# Collect all .lua files into build directory from the lua directory
# Add a custom command to copy Lua files to the build directory after the library is built
file(GLOB LUA_FILES "${CMAKE_SOURCE_DIR}/lua/*.lua")
foreach(LUA_FILE ${LUA_FILES})
    get_filename_component(LUA_FILENAME ${LUA_FILE} NAME)
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy
        ${LUA_FILE} ${CMAKE_BINARY_DIR}/${LUA_FILENAME}
        COMMENT "Copying ${LUA_FILENAME} to build directory"
    )
endforeach()

# Create a custom target that explicitly depends on the library and triggers the Lua file copy command
add_custom_target(copy_lua_files ALL
    DEPENDS ${PROJECT_NAME}
    COMMENT "Copying all .lua files to the build directory"
)

Output

$ ./a.out 
Global namespace:
C++ member function called with argument: 6
C_add(10, 5) = 15
C_subtract(20, 5) = 15
C_function(6) = 12

'Name' namespace:
C++ member function called with argument: 8
Name.C_add(15, 5) = 20
Name.C_subtract(30, 10) = 20
Name.C_function(8) = 16

'MK' namespace:
C++ member function called with argument: 9
MK.C_add(7, 3) = 10
MK.C_subtract(25, 12) = 13
MK.C_function(9) = 18

この記事が気に入ったらサポートをしてみませんか?