In the previous tutorial, we created a simple plugin, part of which is written in C. This time, we will write the native part in Objective-C. Also, we will use Xcode instead of the command-line tools.
If you do not have Xcode installed, you can download it in the App Store.
Setting up the project
Creating the project
Create a new macOS library project with the following settings:
- Product Name:
objcinterop
- Framework:
Cocoa
- Type:
Dynamic
Setting build settings
Now, we need to tune some of its build settings. Go to the library target and open the “Build Settings” tab. Set these properties:
- Deployment
- Skip Install:
No
- macOS Deployment Target:
macOS 10.12
- Skip Install:
- Linking
- Dynamic Library Install Name:
$(EXECUTABLE_PATH)
- Runpath Search Paths:
$(inherited)
@executable_path/../Frameworks
@loader_path/../Frameworks
- Dynamic Library Install Name:
- Packaging
- Executable Extension:
so
- Executable Extension:
- Search Paths
- Header Search Paths:
/Applications/Marta.app/Contents/Frameworks/LuaKit.framework/Versions/Current/Resources/include (non-recursive)
- Library Search Paths:
/Applications/Marta.app/Contents/Frameworks/LuaKit.framework/Versions/Current/Frameworks (non-recursive)
- Header Search Paths:
Adding Lua dependency
The final step is to add the Lua library to the project dependencies.
- Go to the “General” tab.
- Click on the “+” button in the “Frameworks and Libraries” list.
- Choose “Add Other…” → “Add Files…”.
- Press
⌘⇧G
and navigate to/Applications/Marta.app/Contents/Frameworks/LuaKit.framework/Versions/Current/Frameworks
. - Choose
liblua.dylib
, press “Open”. - (Important) Change the “Embed” value to “Do not embed”.
🐧 If you need to link against some other version of Marta, change paths to the Marta application bundle accordingly.
Great! We are now ready to code.
Objective-C part
XCode automatically created a header (objcinterop.h
) and a source file (objcinterop.m
). We will use them.
Let’s start with the header. In order to make Lua understand our library, we need to declare an entry point.
Remove all existing code in the header file and declare luaopen_libobjcinterop()
instead:
#include "lauxlib.h"
int luaopen_libobjcinterop(lua_State *L);
Then we need to provide the implementation for the function we declared. Similar to the C Interoperability tutorial, we will expose to Lua a single function called helloWorld()
.
Replace code in objcinterop.m
with the following listing:
#import "objcinterop.h"
static int helloWorld(lua_State *L) {
return 0;
}
static const struct luaL_Reg mylib [] = {
{"helloWorld", helloWorld},
{NULL, NULL}
};
int luaopen_libobjcinterop(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}
Now you should be able to build the project. Yet, our function does nothing – it simply returns zero, telling Lua there are no return values. Let’s make use of Objective-C by calling some Cocoa APIs:
#import <Cocoa/Cocoa.h>
static int helloWorld(lua_State *L) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText: @"Hello from Objective-C asasas!"];
[alert runModal];
return 0;
}
Here is the complete contents of objcinterop.m
:
#import "objcinterop.h"
#import <Cocoa/Cocoa.h>
static int helloWorld(lua_State *L) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText: @"Hello from Objective-C!"];
[alert runModal];
return 0;
}
static const struct luaL_Reg mylib [] = {
{"helloWorld", helloWorld},
{NULL, NULL}
};
int luaopen_libobjcinterop(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}
Building the library
To build the native library:
- Choose “Product” → “Archive” in the XCode application menu.
- As the binary is built, click on “Distribute Content” button on the right.
- Select “Build Products” and click “Next”.
- Choose some location and click “Export”.
- You can find the built library inside
usr/local/lib
.
Lua part
Create a multi-file plugin. Let’s name it objcinterop
for consistency. Copy to there libobjcinterop.so
we built in the previous section.
As we do not expect anything to be returned from helloWorld()
, we just call the function inside apply()
:
marta.expose()
plugin {
id = "marta.example.objcinterop",
name = "Objective-C Interoperability Test",
apiVersion = "2.1"
}
local interop = require "libobjcinterop"
action {
id = "test",
name = "Test Objective-C Interoperability",
apply = function()
interop.helloWorld()
end
}
Add the Lua part to init.lua
. Here is how the plugin layout should look like:
Plugins/
objcinterop/
init.lua
libobjcinterop.so
Now you should be able to launch Marta and run the “Test Objective-C Interoperability” action.
Congratulations!