Skip to content

Tutorial: Setting up the ACEs

LB-- edited this page Dec 30, 2012 · 4 revisions

<< Tutorials | Creating the JSON >>

As with most extensions, the Actions, Conditions, and Expressions make up most of your extension. In EDIF, they are placed within the scope of the Extension class, so they have access to any data members and member functions you define in it. Before you start designing your A/C/Es, you need to be aware of what kind of parameters are available to you and how they affect each A/C/E.

A/C/E Parameters

As a general rule of thumb, all parameters and return types will be 4 bytes in size. This means int/long, float, and any kind of pointer. This is also a core reason why double precision floats cannot be used: they are 8 bytes instead of 4. For efficiency and convenience purposes, Yves and Francois opted not to pass around pointer to doubles and instead pass around floats. In fact, this 4-byte rule applies to the entire MMF2 SDK - but only for parameters and return types!

More in-depth parameter explanation is in Tutorial: Creating the JSON; for now we will discuss how to create A/C/Es with strings, integers, and floats.

Actually Making the A/C/Es

For starters, you need to open Extension.hpp, Extension.cpp, Actions.cpp, Conditions.cpp, and Expressions.cpp - you will, of course, need all five open.

There's no specific way to say that a particular function is an Action, Condition, or Expression when you declare/define them, but you can usually tell at a glance from the return types (usually). Actions don't return anything (void is the return type), conditions usually return a bool, and an expressions return an integer, float or c-string (const TCHAR *). And that's another thing to note - the things return from expressions are generally the parameters A/C/Es will receive - that is, integers, single-precision floats, and C-style strings.

To create an A/C/E, scroll down Extension.hpp - the example ones should stand out to you. Here you have a sort of planning area - set up your A/C/Es like normal functions keeping in mind the parameter and return type limitations. You should write parameter names even though you don't have to - it will make planning easier. Once you've planned them out, you'll need to visit each of Actions.cpp, Conditions.cpp, and Expressions.cpp to write the implementations. The reason the A/C/Es are kept in separate files is for organizational purposes - if it so pleases you, you can define them all in the same files, but it is not recommended. Remember that you have to write the return type and Extension:: before the name of the function, and re-write the parameter list and parameter names. You can then define it like a normal function, but since you're in the scope of your class and the A/C/Es are instance member functions, you have access to the instance data members in your Extension class. You don't have to write this-&gt; unless you prefer that coding style or you need to get around variable scope shadowing (e.g. if you have a parameter with the same name, which is a bad idea anyway).

Once you start setting up the A/C/Es like this you may notice you made some easrly design mistake and you may have changed the signatures of the A/C/Es - remember you also have to change the signatures in the Extension class too. The parameter names don't have to match, but you should make sure they do to avoid confusion later on. Once you've gotten everything set up, pay a visit to Extension.cpp - here you will need to actually link the A/C/Es to IDs your Extension class' constructor. These will correspond to the IDs in the JSON, so make sure everything matches up. IDs must start at 0 and you cannot skip IDs. Once you've assigned IDs and you release a version of your extension, you should consider those IDs permanent - changing them could mess up existing MFAs that people have made with older versions of your extension. You'll also need to know these IDs if you plan to port your extension to another runtime.

That's most of what you'll need to know for the basics of setting up the A/C/Es - next, you should read about Creating the JSON, which explains all the supported parameter types and how they affect the signature of your A/C/Es.

The Example ACEs, Explained

Extension.hpp:

//Actions - Defined in Actions.cpp
void ActionExample(int ExampleParameter);
void SecondActionExample();

//Conditions - Defined in Conditions.cpp
bool AreTwoNumbersEqual(int FirstNumber, int SecondNumber);

//Expressions - Defined in Expressions.cpp
int Add(int FirstNumber, int SecondNumber);
const TCHAR * HelloWorld();
These are member function declarations (sometimes called prototypes), just as you would see with any C++ class. You can implement A/C/Es inline if you want to, but it is not recommended. There's not much to point out here, except the return type for HelloWorld - const TCHAR * is a c-string that is a string of char in normal build configurations and a string of wchar_t in the Unicode build configurations. You should always use const TCHAR * with A/C/E paremeters and return types, as this is what MMF2 will expect. More information on being Unicode friendly is in the How to be Unicode friendly tutorial. For your first extensions, however, you may find it easier not dealing with Unicode stuff - it can be a hassle at times.

Actions.cpp:

void Extension::ActionExample(int ExampleParameter)
{
}

void Extension::SecondActionExample()
{
}
Conditions.cpp:
bool Extension::AreTwoNumbersEqual(int First, int Second)
{
	return First == Second;
}
Expressions.cpp:
int Extension::Add(int First, int Second)
{
	return First + Second;
}

const TCHAR *Extension::HelloWorld()
{
	return _T("Hello world!");
	//return Runtime.CopyString(MyString.c_str()); //for stdtstrings
}
As you can see, the function definitions match the function prototypes, but they are also scoped within the Extension class with Extension:: before the function name. This is all standard C++ stuff, nothing much specific to EDIF. You'll notice in HelloWorld that the string constant is wrapped in _T(), which is just a macro that places an L before the string constant in Unicode build configurations to make the string constant a wide string constant. (e.g. L"This is a string" is a Unicode string literal). You'll also notice the comment which shows off a couple things - firstly, the Runtime.CopyString() function. If you have a string that will only exist for the duration of your expression, the CopyString() function will make a copy of that string within MMF2's own memory, so once the copy is made the original can be safely destroyed. The copy can then be given to MMF2, and MMF2 and free it whenever it needs to. This is important especially with the std::string class, because copies and instances that are created in a function get destroyed at the end of the function, meaning that by the time MMF2 got the pointer to the string it would already be a dangling pointer (a pointer that points to where data used to be, but is now occupied by other data and is not safe to access). The example mentions stdtstring, which is a typedef for EDIF projects defined like this:
typedef std::basic_string<TCHAR> stdtstring;
This essentially evaluates to std::string in normal build configurations, and to std::wstring in Unicode build configurations, so it is a handy works-in-both class.

Extension.cpp: <constructor>

LinkAction(0, ActionExample);
LinkAction(1, SecondActionExample);

LinkCondition(0, AreTwoNumbersEqual);

LinkExpression(0, Add);
LinkExpression(1, HelloWorld);
Here, the A/C/Es are actually being linked to IDs that are used throughout MMF2, your extension, and the JSON. The condition IDs are important to remember, especially for triggered conditions (immediate events), because you need the ID to specify which condition to trigger.

<< Tutorials | Creating the JSON >>