Skip to content

Instantly share code, notes, and snippets.

@Shchvova
Last active October 2, 2018 06:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shchvova/68a3c667327ff33e3cfe0ccd7474135c to your computer and use it in GitHub Desktop.
Save Shchvova/68a3c667327ff33e3cfe0ccd7474135c to your computer and use it in GitHub Desktop.
Developing Windows Plugin

Developing Windows Plugin

Prerequisites

This tutorial requires knowledge of basic Lua C api and C/C++.

Abstract

This tutorial is a walkthrough for developing simple plugin for Windows Simulator or Windows desktop app. We would implement wrapper around Tolk, open source library allowing applications to output text through screen reader software (assistive technology for the blind and visually impaired).

Tolk provides C bindings, described in Tolk.h.

Setup

One would have to obtain latest CoronaEnterprise daily build as well as Tolk distribution. Visual Studio 2013 is used to compile a plugin.

Daily build 2017.3043 contains significant improvements to Windows plugin template. This tutorial would not work with earlier Corona Enterprise templates as-is.

Unpack CoronaEnterprise to you Projects folder and copy CoronaEnterprise\ProjectTemplates\App next to it.

It is important to have copied App template next to CoronaEnterprise folder in order for Windows template to compile. Rename App into TolkPlugin to have more specific project name.

Windows simulator plugin lookup

Usually plugins are required by Lua code like

local tolk = require("plugin.tolk")

When encountering such code one of the things Corona Simulator would attempt is to locate a library named plugin_tolk.dll and run luaopen_plugin_tolk function from it. This, in a nutshell, how native Lua plugins work.

Project Structure

Windows project template is now located in TolkPlugin\win32\ folder. Open Plugin.sln with Visual Stodio 2013. In Solution Explorer one can see that currently plugin contains both Lua and C++ code. Our plugin woul have only single C++ source file.

First our step would be to delete existing source code in a plugin. In Solution Explorer, select all 3 files under Source Files (one .lua, one .cpp and one .h file), then select Remove from context menu. Confirm the removal.

Now it is time to add a new source file which would contain our plugin. Right click on "Source Files" and select "Add" → "New Item…". In pop-up dialog select "C++ File (.cpp)" and type in a name, "TolkPlugin" for example.

We would be presented with empty C++ file. Time to write some code:

// TolkPlugin.cpp - simple Tolk wrapper for Corona SDK

#include "CoronaLua.h"
#include "CoronaMacros.h"

int test(lua_State *L)
{
    lua_pushstring(L, "Hello, World!");
    return 1;
}

CORONA_EXPORT int luaopen_plugin_tolk(lua_State *L)
{
	static const luaL_Reg kVTable[] =
	{
		{ "test", test },
		{ NULL, NULL }
	};
	luaL_openlib(L, "plugin.tolk", kVTable, 0);
	return 1;
}

Before compiling the code we would have to change output name from plugin_library.dll to plugin_tolk.dll. To do that right click on Plugin project in Solution Explorer and select Properties. In top of the Properties window, select "All Configurations" from Configurations drop-down, then change value of "Target Name" from plugin_library to plugin_tolk (it is in "Configuration Properties" → "General" section).

Press OK and select Build → "Build Solution". We just made our first Windows plugin. It has single function "test" which just returns "Hello, World!" string.

Now we can test our plugin. Start Corona Simulator and create a new project.

local tolk = require("plugin.tolk")
print( tolk.test() )

This should print out "Hello, World!" into Corona Simulator Output window.

Note that even if Corona would produce a warning that plugin isn't set up in build.settings, it will still work. Please, ignore missing plugin warning while developing a plugin.

Hint: in order to debug a plugin you can attach to running Simulator with "Debug" → "Attach To Process…" or setup "Command" in Project Properties, Debugging section to Corona Simulator executable. There is handy "<Browse..>" option in a drop-down selection box. This way, pressing green triangle would start Corona Simulator, and automatically attach debugger to it.

Expanding a plugin

Our plugin doesn't do much. We would have to add more functionality to it. From this point I would provide snippets from my TolkPlugin.cpp with explanations. Full source code is available on-line on Github

Including tolk and windows libraries

We will be using Tolk.lib and Tolk.h from Tolk distribution. Copy of them should be in root of the project (inside win32 folder, next to Plugin.sln).

Visual Studio provides handy way to link project to static library:

#include <windows.h>
#include "Tolk.h"
#pragma comment(lib, "Tolk.lib")

First two lines include header files, and last line links Tolk.lib to our dll.

Loading and Unloading Tolk

As per documentation, Tolk library requires Tolk_Load() to be called before using any of its APIs and Tolk_Unload() after. Calling Tolk_Load() when library is loaded is rather easy. Unloading library is trickier. To achieve that, we will create an userdata, and attach metatable with __gc (Garbage Collection) method to it. Corona Enterprise provides handy functions for it:

int Finalizer(lua_State*)
{
    Tolk_Unload();
}

CORONA_EXPORT int luaopen_plugin_tolk(lua_State *L)
{
    // This will macro creates "unique" string with filename and line nubmer in it
	const char kMetatableName[] = __FILE__;
	// This call will create metatable named kMetatableName and GC callback "Finalizer"
	CoronaLuaInitializeGCMetatable(L, kMetatableName, Finalizer);
	// This pushes pushes new userdata and assigns metatable to it
	// we're using nullptr: we don't care about contents of userdata
	// we just need a garbage collection callback on it when it is
	// no longer held by the plugin
	CoronaLuaPushUserdata(L, nullptr, kMetatableName);

	static const luaL_Reg kVTable[] =
	{
	//...
	};
	
	Tolk_Load();
	
	// note last 1. This means that we will push our single userdata as "upvalue"
	// for this library (plugin). It means, that lifetime of userdata would be bound
	// to lifetime of library. So when library is released (e.g. Simulator is reloaded)
	// userdata would be freed alongside with library and Finalizer called
	luaL_openlib(L, "plugin.tolk", kVTable, 1);

	return 1;
}

This technique is very common in Corona plugins. Often userdata would contain some state we want to pass around, unlike nullptr in our case. This can be data structure containing listeners user set up. For such example check out iOS plugin code.

String encodings

All lua strings are UTF8. Tolk library works with windows wide wide character encoding, where each character is represented by 16-bit. Windows provides several functions to translate text from one encoding to another. This is why we included windows.h earlier:

    // function to actually output a text
	static int Output(lua_State *L)
	{
	    // check if first parameter is actually a string
		if (lua_type(L, 1) == LUA_TSTRING) {
			size_t utf8Len = 0;
			// retrieve string and it's length
			const char * utf8Text = lua_tolstring(L, 1, &utf8Len);
			// create buffer for wide char string
			// It will always have less elements than UTF 8 string
			wchar_t	*wcharText = new wchar_t[utf8Len+1]();
			// function to convert UTF8 to wide char
			MultiByteToWideChar(CP_UTF8, 0, utf8Text, utf8Len, wcharText, utf8Len);
			// send converted text to Tolk
			Tolk_Output(wcharText, interrupt);
			// delete the buffer we just allocated
			delete wcharText;
		}
		return 0;
	}

Where to go from here

  • Explore CoronaEnterprise and Lua native APIs: https://docs.coronalabs.com/native/
  • Try making macOS cross-platform plugin. You can try writing plugins which would work on all Corona supported platforms with single code base. Check out memoryBitmap as an example of such plugin.

Testing plugin

Corona expects plugins to be in "%APPDATA%\Corona Labs\Corona Simulator\Plugins". You can manually copy dll there.

plugin.tolk

Wrapper around Tolk, library allowing applications to output text through screen reader software (assistive technology for the blind and visually impaired).

Functions

All functions are closely connected to counterparts in Tolk.h

tolk.output(text[, interrupt])

Outputs text with default driver. If interrupt is set to true will interrupt current speach. Returns boolean, if call was successful.

tolk.hasSpeech()

tolk.hasBraille()

Return boolean if driver for speech or Braille output is available.

tolk.speak(text[, interrupt])

tolk.braille(text)

Outputs text with in a specific way.

tolk.isSpeaking()

Return boolean if currently speaking.

tolk.sielence()

Interrupts speech if any.

tolk.trySAPI([enable=true])

By default, Microsoft SAPI driver is disabled. Call this function to enable it with lowest priority.

tolk.preferSAPI([prefer=true])

By default, Microsoft SAPI driver has lowest priority. If prefer parameter is set to true, it will move the driver to top priority

Example

local tolk = require("plugin.tolk")
tolk.trySAPI()
tolk.output("Hello from Corona SDK!")
// TolkPlugin.cpp - simple Tolk wrapper for Corona SDK
#include "CoronaLua.h"
#include "CoronaMacros.h"
#include <windows.h>
#include "Tolk.h"
#pragma comment(lib, "tolk.lib")
CORONA_EXPORT int luaopen_plugin_tolk(lua_State *L);
namespace TolkPlugin {
static int Finalizer(lua_State *L)
{
if (Tolk_IsLoaded())
{
Tolk_Unload();
}
return 0;
}
static int HasSpeech(lua_State *L)
{
lua_pushboolean(L, Tolk_HasSpeech());
return 1;
}
static int HasBraille(lua_State *L)
{
lua_pushboolean(L, Tolk_HasBraille());
return 1;
}
static int Output(lua_State *L)
{
bool ret = false;
bool interrupt = false;
if (lua_type(L, 2) == LUA_TBOOLEAN) {
interrupt = (lua_toboolean(L, 2) != 0);
}
if (lua_type(L, 1) == LUA_TSTRING) {
size_t utf8Len = 0;
const char * utf8Text = lua_tolstring(L, 1, &utf8Len);
wchar_t *wcharText = new wchar_t[utf8Len+1]();
MultiByteToWideChar(CP_UTF8, 0, utf8Text, utf8Len, wcharText, utf8Len);
ret = Tolk_Output(wcharText, interrupt);
delete wcharText;
}
lua_pushboolean(L, ret);
return 1;
}
static int Speak(lua_State *L)
{
bool ret = false;
bool interrupt = false;
if (lua_type(L, 2) == LUA_TBOOLEAN) {
interrupt = (lua_toboolean(L, 2) != 0);
}
if (lua_type(L, 1) == LUA_TSTRING) {
size_t utf8Len = 0;
const char * utf8Text = lua_tolstring(L, 1, &utf8Len);
wchar_t *wcharText = new wchar_t[utf8Len + 1]();
MultiByteToWideChar(CP_UTF8, 0, utf8Text, utf8Len, wcharText, utf8Len);
ret = Tolk_Speak(wcharText, interrupt);
delete wcharText;
}
lua_pushboolean(L, ret);
return 1;
}
static int Braille(lua_State *L)
{
bool ret = false;
if (lua_type(L, 1) == LUA_TSTRING) {
size_t utf8Len = 0;
const char * utf8Text = lua_tolstring(L, 1, &utf8Len);
wchar_t *wcharText = new wchar_t[utf8Len + 1]();
MultiByteToWideChar(CP_UTF8, 0, utf8Text, utf8Len, wcharText, utf8Len);
ret = Tolk_Braille(wcharText);
delete wcharText;
}
lua_pushboolean(L, ret);
return 1;
}
static int IsSpeaking(lua_State *L)
{
lua_pushboolean(L, Tolk_IsSpeaking());
return 1;
}
static int Sielence(lua_State *L)
{
lua_pushboolean(L, Tolk_Silence());
return 1;
}
static int trySAPI(lua_State *L)
{
bool sapi = true;
if (lua_type(L, 1) == LUA_TBOOLEAN)
{
sapi = (lua_toboolean(L, 1) != 0);
}
Tolk_TrySAPI(sapi);
return 0;
}
static int preferSAPI(lua_State *L)
{
bool sapi = true;
if (lua_type(L, 1) == LUA_TBOOLEAN)
{
sapi = (lua_toboolean(L, 1) != 0);
}
Tolk_PreferSAPI(sapi);
return 0;
}
}
CORONA_EXPORT int luaopen_plugin_tolk(lua_State *L)
{
using namespace TolkPlugin;
const char kMetatableName[] = __FILE__;
CoronaLuaInitializeGCMetatable(L, kMetatableName, Finalizer);
CoronaLuaPushUserdata(L, nullptr, kMetatableName);
static const luaL_Reg kVTable[] =
{
{ "hasSpeech", HasSpeech },
{ "hasBraille", HasBraille },
{ "output", Output },
{ "speak", Speak },
{ "braille", Braille },
{ "isSpeaking", IsSpeaking },
{ "sielence", Sielence },
{ "trySAPI", trySAPI },
{ "preferSAPI", preferSAPI },
{ NULL, NULL }
};
if (!Tolk_IsLoaded())
{
Tolk_Load();
}
luaL_openlib(L, "plugin.tolk", kVTable, 1);
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment