I'll fill in what I can. This may not be the best way to do this (and I'm sure there's bugs), but its been working for me so far.
In order to use p/invoke means you need to have a DLL that exports some functions. Don't worry about the calling convention, the P/Invoke API lets you set that for your DLL you're calling into. You also don't really need a def file, building your project as a DLL and having the right preprocessor flags set will export the symbols you want to bind to (at least in the Lua source). To build the Lua library as a DLL you simply need to build the lua library with the flag LUA_BUILD_AS_DLL, which if you look in luaconfig.h (line 235) to cause the symbol LUA_API to be defined as __declspec(dllexport). I didn't change any code and that is all that you need to do to build lua5.3.dll with its symbols exported.
Once you have a dll with symbols (or feel free to take mine, it's a vanilla 64bit Lua dll), you need to write a C# file that can call into it. I usually create my dll interface as a static class since each of these functions will be static functions. I simply went through the public Lua header and then copied the functions I want to be able to call to my C# file as comments so I have them there to reference and then started writing out the p/invoke function definition for each of the C functions.
#region state manipulation
/*
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
*/
[DllImport(LUA_DLL, EntryPoint = "lua_newstate", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern IntPtr lua_newstate(lua_Alloc f, IntPtr ud);
[DllImport(LUA_DLL, EntryPoint = "lua_close", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern void lua_close(IntPtr state);
[DllImport(LUA_DLL, EntryPoint = "lua_newthread", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern IntPtr lua_newthread(IntPtr state);
[DllImport(LUA_DLL, EntryPoint = "lua_atpanic", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern LuaCSFunction lua_atpanic(IntPtr state, LuaCSFunction panicf);
#endregion
Here you can see each of the static functions bind to a function in the C dll. The DllImport attribute takes the dll filename, the function name as the entry point, the calling convention (see, no need to change how Lua is built, just change how pinvoke calls it), and a flag to suppress unmanaged security features for performance reasons. Most basic types are handled automatically. IntPtr is used for the pointer to the lua_State* since that's an opaque object (we don't access anything inside it ever). If you need to shuffle complex structures between C and C#, you should look into how marshalling and layout is done on the C# side. For callbacks, you simply need to define a C# delegate, but give it attributes that allow it to be called from C, and respecting the calling convention of your library. Here's the delegate definition for a typical function pushed into Lua from C#:
//Delegate for functions passed to Lua as function pointers using cdecl vs the standard stdcall
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int LuaCSFunction(IntPtr luaState);
You also need to make sure any delegates you pass into Lua from C# don't get garbage collected. You do this by keeping a reference to the delegate around, maybe as a member of a class (see how I have myPrintFunction as a member of the LuaState below).
The dll filename is where I do something a little different. Its an idea blatenly stolen from another project (OVR C# bindings I believe). I preload the dll into the application before pinvoke looks for it. I can specific the architecture I want (x86 vs x64) by prepending a path to the dll name and then using LoadLibrary to bring that dll into the application space before any p/invoke functions are called. When P/Invoke tries to find the function it looks in the application's loaded dlls first (only by name, not path+name which is why this works), then going to the working directory and looking for dlls with matching names, then if one isn't found there looking in the system path. Pulling the DLL into the application space first can help prevent the wrong DLL being used (can happen when you have 2 versions of lua in your path, or getting a 32bit DLL when you need a 64bit DLL). You don't have to do this, just be sure that the DLL you want is in your application working directory or your path.
As I worked through the lua C api, I occasionally came across a macro that acted like a function, so I just implemented it as a function. Such as this:
/*#define luaL_dostring(L, s) \
(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
*/
public static int luaL_dostring(IntPtr L, String s)
{
int ret = luaL_loadstring(L, s);
if (ret != 0)
{
return ret;
}
return lua_pcall(L, 0, LUA_MULTRET, 0);
}
Once I got through the API (there were a couple of things I either stubbed out or didn't implement, but not many), I started adding some functionality on top of the raw dll calls. I created a LuaState object that internally has an IntPtr to a lua_State* object that is created from LuaDLL. This class provides access functions to get/set values by name/index as well as create tables and references to them. The class also provides some nice debug functions such as printing out the lua stack and printing out a lua table to a C# console.
public class LuaState
{
LuaCSFunction myPrintFuction;
IntPtr myStatePtr;
LuaObject myGlobalTable;
public LuaState()
{
myStatePtr = LuaDLL.luaL_newstate();
LuaDLL.luaL_openlibs(myStatePtr);
LuaDLL.lua_getglobal(myStatePtr, "_G");
myGlobalTable = new LuaObject(this, -1);
myPrintFuction = new LuaCSFunction(print);
LuaDLL.lua_pushcclosure(myStatePtr, myPrintFuction, 0);
LuaDLL.lua_setglobal(myStatePtr, "print");
printCallback = new PrintCallback(defaultPrint);
}
...
}
The LuaObject provides an interface to any lua value whether or not it's a number, string, table, or function. It allows for iterating over a table, retrieving values or calling lua functions. It's just a nice abstraction.
Let me know if you have any questions.
cheers,
Bob