見出し画像

LuaからC Programを呼び出す開発を CMakeで

だいたいLua言語ってどれくらいの人口がいるんだろうか
新規にスクリプトをアプリケーションとかに組み込む時に、Luaを使えば導入・学習コストもそんなに高くないと思うのですが私の廻りには殆どいない

で、今回はLua言語からC言語(正確にはC++言語)で書かれた機能を呼び出したいという事
まぁC++をC言語のインターフェイスに対応させるのは簡単な話なので、そこは省略

環境はMacOS

LuaとCとのやり取りはLua側にAPIが用意されていて単純に呼び出すだけ
このあたりはLispからCのモジュールを呼び出すのと手法は殆ど同じ

とりあえず開発のテンプレートとして
1. Luaのコード
2. Cのコード
3. CMakeLists.txt
を作る
1.と2.のインターフェイス用のAPIは無責任にドキュメントを見てもらうとして
3. は手持ちのCMakeLists.txtのテンプレートから作成しました

たぶん、このnoteを見る人は、コードだけ見れば判ると思うので1.と2.はコードを載せただけ

CMakeLists.txtもCMakeを使う羽目になった人は理解していると思うので解説無し

不親切で無責任です

Luaのコード

test.lua

-- Load the C library; luaopen_mylib is the initialization function for the library
local mylib = package.loadlib("./mylib.so", "luaopen_mylib")

-- Check if the library was successfully loaded; if not, handle the error
if not mylib then
    error("Failed to load C library 'mylib.so'")
else
    -- Call the initialization function to register the C functions in Lua
    mylib()
end

-- Call the C function 'add' that was registered by the library
local result = add(3, 7)

-- Output the result of the C function
print("Result from C function: " .. result)

Cのコード

mylib.c

#include <lauxlib.h> // Auxiliary functions for Lua
#include <lua.h>     // Lua core definitions
#include <lualib.h>  // Standard Lua libraries

// C function to be called from Lua
int lua_add(lua_State *L) {
  // Ensure the first argument is an integer, otherwise, throw an error
  int a = luaL_checkinteger(L, 1);

  // Ensure the second argument is an integer, otherwise, throw an error
  int b = luaL_checkinteger(L, 2);

  // Calculate the sum of the two integers
  int sum = a + b;

  // Push the result (sum) onto the Lua stack to return it to Lua
  lua_pushinteger(L, sum);

  // Return the number of results (in this case, 1) to be passed back to Lua
  return 1;
}

// Function to be called when the library is loaded into Lua
int luaopen_mylib(lua_State *L) {
  // Register the C function 'lua_add' under the name 'add' in Lua
  // This allows the function to be called as 'add' from Lua scripts
  lua_register(L, "add", lua_add);

  // Return 0 to indicate successful loading of the library
  return 0;
}

CMakeLists.txt

#[[
Directory structure for this example:

$ tree .
.
├── CMakeLists.txt
├── build
├── lua
│   └── test.lua
└── src
    └── mylib.c

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
        $ lua test.lua
    
    5. Enjoy!
]]

# Minimum CMake version required
cmake_minimum_required(VERSION 3.20)

# Define the project name and specify that C is the primary language
# The project name is used to name the output library
# For example, You set the project name to `mylib`, the output library will be `mylib.so`
project(mylib C)

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

# Find all source files in the src directory
file(GLOB SOURCE_FILES "src/*.c")

# Create a shared library from the source files
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

# 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 standard to be used (C17 in this case)
target_compile_features(${PROJECT_NAME} PRIVATE c_std_17)

# Set properties for the shared library (name, suffix, prefix)
set_target_properties(${PROJECT_NAME} PROPERTIES
    OUTPUT_NAME ${PROJECT_NAME} # Name of the output library
    SUFFIX ".so" # Shared library suffix for Unix-like systems
    PREFIX "" # No prefix for the library name
)

# Collect all .lua files from the lua directory
file(GLOB LUA_FILES "${CMAKE_SOURCE_DIR}/lua/*.lua")

# Add a custom command to copy Lua files to the build directory after the library is built
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"
)

CMakeLists.txtで面倒だったのは、MacOSにもかかわらず .soを要求してくる事
local mylib = package.loadlib("./mylib.so", "luaopen_mylib")
をmylib.dylibにしてもうまく行かない(ちゃんと検証していないけど)
なので"./mylib.so"のように拡張子をsoとした

Luaのコードはluaディレクトリに格納するルールにしたので、実際に動作検証するときにファイルを移動したりする必要が発生
面倒なので buildにコピーする事にした
関連するファイルはどうするかとか色々とあるけど、そういうのは現状に合わせてください
テンプレートとしては、正常にコンパイル、共有ライブラリの生成が出来たあとにLuaから呼び出して簡単な動作試験を行える所まで用意した

Enjoy!

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