Methods of using Lua to extend C + + programs

Time:2021-11-24

introduce

If users can modify the behavior of the application itself through some scripting languages, many applications can become more suitable for users. Some commercial applications provide such convenience. For example, VBA script programming for Microsoft office or used in video game world of WarcraftLua。 Scripting language takes the application as a platform to provide a series of services that end users can obtain and manipulate.

As a language embedded in programs, we have many options available: open source and non open source scripting engines, or we can create one from scratch. Now, the most familiar scripting languages are JavaScript, Lua and python, as well as many other languages. The general method of embedding the script engine on Microsoft Windows platform is to use a dynamic link library (DLL) containing the script engine, and then use a series of function calls to access the services in the engine.

In this article, we will see how to use lua5.2 script engine to embed into C + + code. The development tool used for our test case is Visual Studio 2005. Before we begin to explain, we need to download some necessary components from Lua’s official website. Lua5.2 contains compiled DLLs and import libraries for header files and links. It should be noted that when you use Lua to embed into C + + code, the compiled executable file must contain Lua’s DLL, that is, dynamic link library, otherwise you will be prompted with running error and lack of necessary DLL. These examples are written in Visual Studio 2005. Of course, they are compatible with subsequent versions of vs.


This article describes a specific implementation using lua5.2 as a script engine. There is a lot of information about this in Lua’s official documents (Lua. ORG) and some websites. Here are just some test examples for you to guide you before you start Lua’s journey

For product development, developing a complete professional application has different strategies and methods from developing a single application. For example, to develop a complete professional application platform, it is necessary to provide a large number of rich plug-in tools and services to end users to meet their specific domain needs. The strategies and methods of plug-ins are applied in many software, such as Microsoft office, visual studio, integrated development environment eclipse, and evenpictureThe processing software Adobe Photoshop. Online game world of Warcraft provides a large number of add-on (plug-ins) to users. Some other games provide similar tools so that players can add additional content to create a different community.


As a commercial application, genpos has some features that are also highly toolset strategies:

  • Layout manager tool. However, the user can adjust the layout of the interface by adjusting the position, text, content and other attributes of windows and buttons.
  • The character control function can automate some workflows
  • Rich parametric design can change the function of the software
  • The mnemonics of the database can make it easier to display different languages
  • Parameters and mnemonics can be changed dynamically through the remote interface
  • Provide an interface to obtain property and operation status data from the terminal


In any case, some modules are actually controlled by the source code, and the change of this part of the module needs the cooperation of the development department to complete the change of software behavior. For example, print receipts (some displays are obtained from the input of parameters or mnemonics). A short list of these limitations may include:

  • The existing character control function lacks status test and jump
  • Unable to communicate, dynamically modify and display information according to the existing status
  •  

 

Instead of trying to strengthen and improve the current very simple control string function to provide sales terminals with additional script functions, we finally decided to find other possible solutions, which do not need a lot of development and improvement, and provide more product improvement capabilities. Just as the layout manager we introduced enables customers to design their own screen layout and workflow, we hope to have a quite flexible mechanism for dealers’ sales terminals to provide value-added services to their customers through scripts. We also intend to provide sufficient access to application services so that dealers and end customers will be able to modify the behavior of their applications. Finally, we should use the language that has a certain degree to the user community and be interested in the resources that people can use the community.

We have done some simple experiments on POS source code with version 5.2 Lua script engine to observe the difficulty of adding functions to the program. From the experimental results, various functions we want to show through program services are available, and can be used by making some modifications to Lua script engine.

Using Lua 5.2 script engine

Lua script engine itself is written in C language, and using Lua script in C or C + + is also quite simple. You can also find many programs or program fragments integrated with Lua 5.2 script engine on the Internet, and the program interface of lua 5.2 and the previous Lua version have only a few changes in initialization and startup interfaces, so the old Lua program can be transplanted to Lua 5.2 environment without modification or only minor modification.


The basic initialization steps are as follows:

  • Using Lua_ Newstate() creates a new Lua state machine.
  • Call lual if necessary_ The openlibs() function loads Lua’s standard library.

Once the Lua script engine is initialized, you can execute a Lua script through the following steps:

  • Using lual_ LoadFile loads a Lua program or script into the Lua execution engine;
  • Call Lua_ The pcall function executes the loaded script.

 

If you want to load the Lua script and execute the functions in the application, you must execute the loaded Lua block (chunk). The program block just loaded is only compiled and stored in Lua’s script engine, and has not been executed. Global variables and functions in Lua are created only after the block is executed. Until then, any of these global variables and functions are not available to the application. As the environment of lua engine, any global variables and functions provided by the application to Lua script engine are also unavailable. The application must first create variables and functions and use the function Lua_ Setglobal () makes them available. The function int luasimplewrapper:: triggerglobalcall() in the file utilityfunctions.cpp defines an example, which dynamically creates a Lua function call on the Lua virtual stack and uses the Lua in the Lua script engine_ Pcall() function to execute this function with the given parameters.

All Lua script engine functions or services use a handle or data structure pointer containing Lua script engine status information. This handle is specified as Lua_ State * or point to Lua_ Pointer to the state variable. Every successful Lua_ Every call to newstate () returns a Lua_ State pointer, which returns null on failure. This data structure is a variable used to indicate a specific session. It allows the Lua script engine to have multiple sessions in parallel at the same time. When the application uses the functions in the Lua script engine, or the services provided by the application to the Lua script engine are called, Lua_ The session environment provided by the state structure uniquely indicates a Lua session state. This means that the functions provided by the application to Lua script engine should be completely reentrant, or provide some monitoring, such as semaphores and critical areas, so that unshared services can provide thread safe access.

Use code

The Visual Studio 2005 project directory contains three C + + files and a Lua example to test basic functions. The main part is in parser01.cpp. Here, a specific Lua file is loaded, and then some other functions are called. Utilityfunctions.cpp contains the source code for the luasimplewrapper method. Initenviron contains the functions we want to provide to Lua environment.

The method we are considering is to launch a file containing Lua script at the point of sale application. As a startup, the point of sale will start a thread that initializes the Lua script engine and loads and executes the specified Lua source file. The Lua block will remain in memory, and in the point of sale application, some events will be transferred to the Lua script for processing as the events go on. The testing tool in this sample program is an exploration of the method we are considering.

In the functions of lua source code provided below, we use several functions provided in the file initenviron.cpp to deal with the ‘wide string’ of non-standard Lua strings. The standard Lua string is a char string (C-style single byte string). These additional functions provide Lua with methods to process these wide strings, such as string connection and comparison.


The following shows a Lua function with three parameters and performs a series of operations. The function name xxfunc is a global name, and the application can call the function Lua_ Getglobal () retrieves from Lua global dictionary and passes the handle to Lua on Lua virtual stack_ getglobal。 Then the parameters of the function can be called like a Lua_ The function of pushstring() is pushed to Lua virtual stack, and then the Lua script engine function Lua is called_ Pcall() executes the xxfunc function.

 

Copy codeThe code is as follows:
— a sample Lua global function that can be invoked from the application or from Lua
function xxfunc (myMessage, wide1, wide2)
    trace(“## xxfunc() called.”)
    trace(”  “..myMessage)
    trace (”  myFrame index “..myFrame.FrameIndex)
    trace (”  myFrame2 index “..myFrame2.FrameIndex)
    trace (”  myFrame3 index “..myFrame3.FrameIndex)
   
    — compare two wide char strings that were passed in as arguments
    trace (”  compare wide “..wcscmp(wide1, wide2))
   
    — generate a wide char string from a Lua string
    local widestring = wcscre(“WIDE1”)
    trace (”  compare with generated “..wcscmp (wide1, widestring))
   
    — try out the wcscat and the wcscre functions to generate a string
    traceW (wcscat (wcscre(”  concat two “), wcscat(wide1, wide2)))
   
    traceW (wcscat (wcscre(”  concat multi “), wide1, wcscre(” “), wide2))
   
    — tryout wcscat with a non-string argument which should be skipped.
    traceW (wcscat (wcscre(”  concat multi “), 2, wcscre(” “), wide2))
  
    local myMemEntry = GetMnemonic (15)
    if (myMessage) then
        if (myMessage.Type == “FRAMEWORK”) then
            local myNem = GetMnemonic (20)
        end
    end
end

 


The Lua functions that have been loaded in the Lua script can be called by the application. Using a helper function from the luasimplewrapper class, we can call the Lua function with the following lines of C + + code. The method triggerglobalcall() requires a descriptive string identifying a global Lua function (function or function assigned to a variable or table instance) to call a parameter type with a description. These parameters follow descriptive strings. This type of variable function call can be a source of runtime errors because it contains several separate sources that must be matched:

1. Descriptive string in triggerglobalcall();
2. The actual parameters provided by thetriggerglobalcall();
3. Called Lua function.

 

Copy codeThe code is as follows:
if (myLua.TriggerGlobalCall (“xxfunc:s,w,w”, “TriggerGlobalCall”, L”WIDE1″, L”WIDE2″) < 0) {
    cout << “%% ” << myLua.GetLastErrorString() << endl ;
}

 


Expressive strings use a comma separated list to distinguish the types of parameters. Each letter in the list represents its own type, such as an ANSI string, a long string, a floating point type, an integer type and the address of a function. In the code of triggerglobalcall () method, commas in the middle of letters are ignored. In fact, they exist only to make this expressive string easier to recognize and understand.

The above method triggerglobalcall() calls Lua’s xxfunc() function to produce the following output. This output is generated by the Lua function using the parameters of the parameter list defined in triggerglobalcall().

 

Copy codeThe code is as follows:
## xxfunc() called.
  TriggerGlobalCall
  myFrame index 0
  myFrame2 index 1
  myFrame3 index 2
  compare wide -1
  compare with generated 0
  concat two WIDE1WIDE2
  concat multi WIDE1 WIDE2
  concat multi  WIDE2
  getTransactionMnemonic() 15

 


The triggerglobalcall () method provided in the luasimplewrapper class allows you to specify a table value that can access a function, which has allocated a key value in the Lua table. The format of descriptive string is tablename.key, where “tablename” is the global name of a Lua table, and “key” is the key value required to access the function.
 

Copy codeThe code is as follows:

// specify a function to be invoked by the OnEvent() handler
// specify a different event type which is not in the Lua script
if (myLua.TriggerGlobalCall (“myFrame2.OnEvent:s,f”, “EVENT_TYPE_J2”, SimpleFunc) < 0) {
    cout << “%% ” << myLua.GetLastErrorString() << endl;
}

 

Export C / C + + functions to Lua engine

In order to create a C or C + + auxiliary function to be used in the Lua script, the C / C + + application must provide the function body and use the appropriate Lua engine function to make the new function available in the Lua engine. In the application, a function is provided to the function calls used in the Lua engine to press some values into the virtual stack of Lua, and then the lua_ is called. With the setglobal () function, you can provide the functions in the application as global functions for use in the Lua script engine.
 

Copy codeThe code is as follows:

lua_pushcclosure (lua, concatMultiWideStrings, 0);
lua_setglobal (lua, “wcscat”);

 


Lua provides closure concepts for functions. When a Lua script engine calls an application function, a closure allows an application to specify one or more values provided to the application function. These values can be updated by the application function. One feature used in this example is to provide a unique value through a counter increment associated with the application function. An example of C + + source code in this regard can be found in the method int luasimplewrapper:: initluaenvironment(), which provides the createframe() function for the Lua script engine.

 

Copy codeThe code is as follows:
// CreateFrame() function that will create a frame with an index
// This function uses two variables which are used to store the
// frame data allowing it to be used by the application in order to
// send events to a specific frame object in the Lua code.
// we access the array of the list of objects, m_ListOfObjects[], with objectindex
// and we access the specific frame for the object with frameindex.
lua_pushnumber(m_luaState, 0);                // frameindex, count of frames for this Lua state object, init to zero
lua_pushnumber(m_luaState, m_MyObjectCount);  // objectindex, which Lua state object am I?

 

// create the C closure with the above two arguments,
lua_pushcclosure (m_luaState, ParserLuaCreateGlobalFrame, 2);
lua_setglobal (m_luaState, “CreateFrame”);

 


The source code of the C + + function to be exported should have the form shown below. The function concatmultiwidestrings () uses a series of functions in the Lua engine to process some values in the Lua virtual stack and return the processing results to the Lua engine. The functions shown below illustrate the use of lua_ The parameter state * is provided to the session environment related to the function. This function is used to splice multiple strings together. The Lua script engine provides information on the number of parameters located in the Lua virtual stack. We can also use the Lua provided by the Lua engine_ The type () function determines the data type of the parameter so that you can skip parameters that are not of the correct type.
 

However, in fact, we only want to use Lua_ In the case of string, Lua script engine will perform corresponding type conversion, but the result of lua’s conversion from other types to string types is a character string composed of C-style single byte characters, rather than a string composed of double byte width characters as expected.
 

Copy codeThe code is as follows:
// concatenate multiple wide strings
//  const wchar_t *wcscat(wchar_t *wcharSt1, const wchar_t *wcharSt2, const wchar_t *wcharSt3, …)
static int concatMultiWideStrings (lua_State *lua)
{
    int  nPushCount = 0;
    int  nArgIndex = 1;
    int argc = lua_gettop(lua);
    wchar_t  tempBuffer[2048];
 
    if (argc > 0) {
        wchar_t    *pWideString = &tempBuffer[0];
        size_t     iLen = 1;
 
        while (nArgIndex <= argc) {
            if (lua_type(lua, nArgIndex) == LUA_TSTRING) {
                const wchar_t *msgX = (wchar_t *) lua_tostring (lua, nArgIndex);
                while (*msgX) {*pWideString++ = *msgX++; iLen++; }
            }
            nArgIndex++;
        }
        *pWideString = 0;  // final zero terminator
        lua_pushlstring (lua, (char *)(&tempBuffer), iLen * sizeof(wchar_t));
        nPushCount++;
    }
 
    return nPushCount;
}

 

Focus

The first version of the test harness is just a very simple beginning. It simply loads a simple Lua script. After running, it just uses Lua’s output function to output a “Hello world” in the console. As we investigate the potential capabilities of embedded Lua, scripts and test tools will become more and more complex. It will soon be found that there is a clear need for some way to print all the contents of lua virtual stack in order to understand how Lua engine communicates with applications. To break suddenly during operation for many times, when using the debugger to step into the C + + source code, use the stack content output function to view the content in the stack. Only in this way can we understand what the problem is and find out the solution to the problem.

The dynamic characteristics of Lua language, like loosely typed languages such as JavaScript, will encourage adventurous programmers to write some very interesting code, but it will also produce some code that is very difficult to debug and test. This difficulty mainly comes from the use of two languages and the lack of support for Lua debugging in visual studio.


When implementing the method int luasimplewrapper:: triggerglobalcall(), when running in Visual Studio debugger, we encountered a test case that caused Lua’s script engine to close or exit the application in the windows error dialog box. We believe that the reason for this problem is that when an error occurs, some specific values are pushed into Lua’s virtual stack and are not handled properly. To solve this problem, when an error is found, we need to clean Lua’s virtual stack with the C + + code shown below. In this test harness, we have two different tests. One is used to test whether the specified global variable exists. The other is to test whether if a key value is specified by using the “global. Key” syntax, the global variable must be a table, otherwise it is an error.

Copy codeThe code is as follows:
 
lua_getglobal (m_luaState, globalName);
if (lua_type(m_luaState, lua_gettop(m_luaState)) == LUA_TNIL) {
    // if the global variable does not exist then we will bail out with an error.
    strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), “Global variable not found: “);
    strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalName);
    m_lastState = LuaDescripParse;
    // error so we will just clear the Lua virtual stack and then return
    // if we do not clear the Lua stack, we leave garbage that will cause
    // problems with later function calls from the application.
    // we do this rather than use lua_error() because this function is called
    // from the application and not through Lua.
    lua_settop (m_luaState, 0);
    return -1;
}

Recommended Today

Seven solutions for distributed transactions

1、 What is distributed transaction Distributed transaction means that transaction participants, transaction supporting servers, resource servers and transaction managers are located on different nodes of different distributed systems. A large operation is completed by more than n small operations. These small operations are distributed on different services. For these operations, either all of them are […]