SkillAgentSearch skills...

KopiLua

A C# port of the Lua v.5.1.4 virtual machine, parser, libraries and command-line utilities.

Install / Use

/learn @Myndale/KopiLua
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Kopi Lua v 1.0

Copyright © 2018 Mark Feldman. All Rights Reserved.

Introduction

Kopi Lua is a C# port of the Lua v.5.1.4 virtual machine, parser, libraries and command-line utilities. KL is not simply an interface to a compiled DLL, all original code has been ported over to managed C#.

The latest version of Kopi Lua can be downloaded from the GitHub repository: https://github.com/Myndale/KopiLua

How to use KopiLua in a C# Application

KopiLua implements the standard Lua interface functions, so interfacing is done virtually the same as for C applications.

Very important: Lua scripts must be encoded as ASCII files! If you create a new script file using Visual Studio 2017 (say) then the encoding will default to UTF-8 and the file will not load. The safest workaround is to create the file with something like Notepad and then add the file to your project.

The following is an adapted version of Goutham Balaraman's "Minimal Example of Calling Lua Functions from C++". It demonstrates the minimum code needed to initialize the virtual machine, load/parse a Lua script and call it from C#:

using static KopiLua.Lua;

public class Program
{
	static int Main(string[] args)
	{
		// initialization
		var L = lua_open();
		luaL_openlibs(L);

		// execute script
		var lua_script = "function sum(a, b) return a+b; end"; // a function that returns sum of two
		luaL_loadbuffer(L, lua_script, (uint)lua_script.Length, "program");			
		lua_pcall(L, 0, 0, 0);

		// load the function from global
		lua_getglobal(L, "sum");
		if (lua_isfunction(L, -1))
		{
			// push function arguments into stack
			lua_pushnumber(L, 5.0);
			lua_pushnumber(L, 6.0);
			lua_pcall(L, 2, 1, 0);
			double sumval = 0.0;
			if (!lua_isnil(L, -1))
			{
				sumval = lua_tonumber(L, -1);
				lua_pop(L, 1);
			}

			// note that C-style printf is available via the KopiLua library
			printf("sum=%lf\n", sumval);
		}

		// cleanup
		lua_close(L);
		return 0;
	}
}     

This second example shows this process in reverse, i.e. implementing the "sum" function in C# and calling it from a Lua script.

using static KopiLua.Lua;

public class Program
{
	static int Main(string[] args)
	{
		// initialization
		var L = lua_open();
		luaL_openlibs(L);

		// set up a table
		lua_newtable(L);
		lua_pushstring(L, "sum");
		lua_pushcfunction(L, (_L) =>
		{
			var a = luaL_checknumber(_L, 1); // get a
			var b = luaL_checknumber(_L, 2); // get b
			var c = a + b;
			lua_pushnumber(_L, c);           // push result
			return 1;                        // number of return parameters
		});
		lua_rawset(L, -3);
		lua_setglobal(L, "foo");

		// execute script
		luaL_loadfile(L, "program.lua");
		lua_pcall(L, 0, 0, 0);

		// cleanup
		lua_close(L);
		return 0;
	}
} 

This example also introduced two additional concepts. First, a global table "foo" is created, and sum is added as a member function. Secondly, the luaL_loadfile function is being used to load the "program.lua", which could contain a simple program such as the following to call the C# implementation directly:

print(foo.sum(5, 6))  -- output: 11

This final example shows how you might use KopiLua in a real-world application where you don't want the VM running constantly on the main GUI thread. In this example the VM is run in its own task and a Lua program sits in a loop repeatedly printing characters at 1-second intervals. Lua does not have a "delay" function as such, so we provide a global function ourselves with a C# implementation that calls Task.Delay() so as to "sleep" the VM thread during these wait times (a real application would typically use synchronization objects to wake the task up in response to events triggered by the main application). Finally, we include a CancellationTokenSource that the main thread can use to signal the task when it's time to terminate:

using System;
using System.Threading;
using System.Threading.Tasks;
using static KopiLua.Lua;

public class Program
{
	private static CancellationTokenSource CancelSource;

	static void Main()
	{
		// start the Lua VM task
		CancelSource = new CancellationTokenSource();
		var vmTask = Task.Run(() => RunVM(), CancelSource.Token);

		// Wait for a keypress
		Console.Write("VM is running, press any key to stop");
		Console.ReadKey();

		// cancel the task and wait for it to finish
		CancelSource.Cancel();
		vmTask.Wait();
	}

	static string lua_script =
		"while true do		" +
		"	io.write(\".\")	" +
		"	delay(1000)		" +
		"end";

	static void RunVM()
	{
		lua_State L = null;

		try
		{
			// initialization
			L = lua_open();
			luaL_openlibs(L);

			// add a delay function
			lua_pushcfunction(L, Delay);
			lua_setglobal(L, "delay");

			// execute script
			luaL_loadbuffer(L, lua_script, (uint)lua_script.Length, "program");
			lua_pcall(L, 0, 0, 0);

		}
		finally
		{
			// cleanup
			lua_close(L);
		}
	}

	static int Delay(lua_State L)
	{
		var ms = luaL_checkinteger(L, 1);        // get wait time
		Task.Delay(TimeSpan.FromMilliseconds(ms), CancelSource.Token).Wait();
		return 0;
	}
}        

Projects and Solutions

Kopi Lua provides a project file for Microsoft Visual Community 2017 containing the following solutions:

  • KopiLua is a class library containing the port of the core Lua VM and parser.
  • Lua is a Windows console application port of the Lua 5.1.4 interpreter.
  • Luac is a Windows console application port of the Lua 5.1.4 compiler.
  • Examples:
    • Example1 shows how to call a Lua function from C#
    • Example2 shows how to call a C# function from Lua
    • Example3 shows how to run the VM in it's own Task

At this time all projects containing the Lua core files must be compiled with the following compilation flags, changing them will break the compile:

LUA_CORE;_WIN32;LUA_COMPAT_VARARG;LUA_COMPAT_MOD;LUA_COMPAT_GFIND;CATCH_EXCEPTIONS

The only new flag is CATCH_EXCEPTIONS and this is the one flag that can be safely disabled. When the Lua VM encounters an error it typically breaks out of execution with a long jump, although this can be changed to throw an exception in C++ builds. The C# port also throws exceptions and catches them higher up the execution heirarchy, but the catch is only done when CATCH_EXCEPTIONS is specified during the build. In general, this is the desired behavior, but turning it off can make it easier to track down exceptions in the code while debugging the VM code.

Known Issues

  • There is currently no support for locale.
  • There are several differences in the behaviour of calls to operating system functions, particularly with undefined operations e.g. renaming an open file works in C but throws an exception in C#.
  • The strftime() function has not yet been ported, so os_date() currently has an incomplete implementation.
  • The handling of lua_str2number() errors is a bit of a hack job at the moment. If the number being converted is outside the allowable range then the function is supposed to return either positive or negative infinity. The current code will return the correct value in most non-pathological cases but it needs additional work to make it more robust.
  • Memory allocation and tracking has not been properly implemented with user data objects so the behaviour of the garbage collector won't be 100% identical to the C version when user data objects are in use. This is currently high on the list of things to be fixed.

Porting Notes

The primary goal of KL was for the port to match the behaviour of the C code as closely as possible. A secondary goal was to maintain a code base that is as close to the original C version as possible in order to assist with future version upgrades and patches.

The C code has been modified to remove all structs with the exception of Value; all other objects are passed by reference. This was done to assist in a future port to Java and/or any other languages that don't support passing objects via the stack. At some point the KL code will be modified so that Value is implemented with a class as well.

The project currently does not employ the use of any pre-processor, so all function #defines etc have been implemented by expanding them out to full-bodied functions. This will be changed in future as it obviously has a negative effect on performance.

The original source is littered with C-style conditionals of the form “if (val)” where val is a pointer or integer variable. These have all been expanded out to their explicit forms i.e. “if (val != 0)” etc.

There is a significant amount of mixing of 16-bit/32-bit and signed/unsigned arithmetic; in most cases a simple cast was enough to fix this. It will, however, need to be cleaned up at some point as it may be having an effect on performance.

The C code makes judicious use of pointer arithmetic, which presented a particularly challenging technical difficulty. When KL creates or resizes an array it loops through every element and stores a pointer to the array as well as its position index within it. Overloaded operators are then used to reference neighbouring elements. Similarly, statements that use indirection such as “*s” etc have been converted to array look-ups of the form “s[0]”. In certain cases the code works with pointer pointers (e.g. GCObject **), this was solved by creating an intermediate class to represent a pointer-to-array (i.e. CGObjectRef).

The handling of strings is a very specific example of arrays that needed to be solved with a custom object (CharPtr) which stores a pointer to the char array and an offset into that array. Operator overloading then allows for the code to work with the string similar to the way in which the original code works with the char *. One notable difference here is the fact that in the original C code a char * passed to a functio

Related Skills

View on GitHub
GitHub Stars30
CategoryDevelopment
Updated1mo ago
Forks3

Languages

C#

Security Score

75/100

Audited on Feb 22, 2026

No findings