Question

I'm new to lua, this might be something quite simple, but I couldn't figure it out. I did all night search, read some posts here, but are not quite what I'm looking for. I finally worked out a lame solution that i'm not happy with, so I'm here to ask for help.

I'm trying to embed lua inside c++, and this program will run as part of an app on the iPhone, as we know, every iPhone app has a resource bundle, and the lua scripts are distributed with the bundle.

// I printed out the bundle path:
bundle directory /var/mobile/Applications/C6CEF090-B99A-4B9B-ADAC-F0BEF46B6EA4/LuaThirdTry.app

Say I have two script files (main.lua, mylib.lua) in the same folder, I put them in my Xcode project, organized like this:

somefolder
|--main.lua
|--mylib.lua

and main.lua is as below:

--main.lua
print(package.path)
require("mylib")

obviously I want to use code from mylib.lua, however, I got error from lua vm:

/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua
PANIC: unprotected error in call to Lua API (...090-B99A-4B9B-ADAC-F0BEF46B6EA4/LuaThirdTry.app/test.lua:5: module 'mylib' not found:
    no field package.preload['mylib']
    no file '/usr/local/share/lua/5.2/mylib.lua'
    no file '/usr/local/share/lua/5.2/mylib/init.lua'
    no file '/usr/local/lib/lua/5.2/mylib.lua'
    no file '/usr/local/lib/lua/5.2/mylib/init.lua'
    no file './mylib.lua'
    no file '/usr/local/lib/lua/5.2/mylib.so'
    no file '/usr/local/lib/lua/5.2/loadall.so'
    no file './mylib.so')

When I add a line modifying package.path, I got it running correctly:

--main.lua modified
print(package.path)
package.path = package.path .. ";/var/mobile/Applications/C6CEF090-B99A-4B9B-ADAC-F0BEF46B6EA4/LuaThirdTry.app/?.lua"
require("mylib")

But this is an absolute path, which should definitely be avoided. One way to solve this problem is to provide lua a function from c, which returns the full path of lua file in ipa bundle at runtime to lua script, and concatenate the package.path with that path, but I think that shouldn't be the "official" way of doing this.

I noticed "./?.lua" inside package.path variable, I just wonder why mylib.lua can't be found, isn't it for files within the same directory?

Sorry for the blah, so the question is: how do I do the "require" decently in iOS environment?

Was it helpful?

Solution 2

Most likely, "." is not the folder containing main.lua, but a working directory (such as where xcode runs from). The app that runs your script probably runs it via a path, like

lua /full/path/to/your/main.lua

So having ./?.lua in LUA_PATH does not help here. Instead, you should have the script run a command to determine where it is running from, and append that to package.path. This should be the path part of arg[0]. So you could try (not tested):

local scriptPath = arg[0]
local dir = string.match(scriptPath, '^.*/')
package.path = package.path .. ';' .. dir .. '?.lua'

The arg is automatically populated by the interpreter, see Section 6 of Lua ref man.

You definitely don't need C to do what you want.

OTHER TIPS

Okay, I finally find a good answer and the job is done, thank to this answer.

So, the solution is to modify the path within c++ code, add this function

#include <string>

int setLuaPath(lua_State* L, const char* path) {
    lua_getglobal(L, "package");
    lua_getfield(L, -1, "path"); // get field "path" from table at top of stack (-1)
    std::string cur_path = lua_tostring(L, -1); // grab path string from top of stack
    cur_path.append(";"); // do your path magic here
    cur_path.append(path);
    lua_pop(L, 1); // get rid of the string on the stack we just pushed on line 5
    lua_pushstring(L, cur_path.c_str()); // push the new one
    lua_setfield(L, -2, "path"); // set the field "path" in table at -2 with value at top of stack
    lua_pop(L, 1); // get rid of package table from top of stack
    return 0; // all done!
}

and then, call this function when init lua_State:

// yourfile.cpp
void runLua() {
    lua_State *L;
    L = luaL_newstate();
    luaL_openlibs(L);

    OCCaller oc_caller;
    std::string bundlePath = oc_caller.get_ios_bundle_path();
    bundlePath.append("/?.lua");
    setLuaPath(L, bundlePath.c_str());

    ....
}

your oc_caller class might look like this:

// OCCaller.h
#ifndef __LuaThirdTry__OCCaller__
#define __LuaThirdTry__OCCaller__

#include <iostream>

class OCCaller {
public:
    OCCaller();
    ~OCCaller();
    std::string get_ios_bundle_path();

};

#endif

impl file:

// OCCaller.mm
#include "OCCaller.h"
#import <Foundation/Foundation.h>

OCCaller::OCCaller() { }
OCCaller::~OCCaller() { }

std::string OCCaller::get_ios_bundle_path() {
    NSString *bundlePath = [[NSBundle mainBundle]resourcePath];
    return std::string([bundlePath UTF8String]);
}

This is a revision on Bryophyte's answer. To me, this was far too complex. I'm also not sure why he did all these extra classes and used C++ strings instead of NSStrings. Here is my revised and hopefully easier to understand iOS code based on this solution:

-(void)addBundlePathToLuaState:(lua_State*)L
{ 
    lua_getglobal(L, "package");
    lua_getfield(L, -1, "path"); // get field "path" from table at top of stack (-1)

    const char* current_path_const = lua_tostring(L, -1); // grab path string from top of stack
    NSString* current_path = [NSString stringWithFormat:@"%s;%@/?.lua", current_path_const, [[NSBundle mainBundle]resourcePath]];

    lua_pop(L, 1); // get rid of the string on the stack we just pushed on line 5
    lua_pushstring(L, [current_path UTF8String]); // push the new one
    lua_setfield(L, -2, "path"); // set the field "path" in table at -2 with value at top of stack
    lua_pop(L, 1); // get rid of package table from top of stack
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top