Make sure you have read everything about Packages.
Foreign Function Interface and the C++ API
Emojicode offers an API that allows you to implement methods in another language.
Basics
Basically, Emojicode does not care about what language you implement native functions in. Letβs say you have define a type method like this:
π π π± π
πβοΈ π π» π€catsimulatorMeowπ€
π
All this tells the compiler is that it should not care about how π works but it can assume that when linking a function named catsimulatorMeow
will be available that matches the declaration.
Furthermore, the compiler expects this function to be callable via the C calling convention.
This means that you can theoretically implement that method in any language you want as you can meet these requirements. Yet, if you need a little bit of interaction with the Emojicode runtime or the s package types, the C++ API is required.
Using the API can be very challenging sometimes and the API is subject to change. Implement everything that can be implemented in Emojicode in Emojicode.
Implementing a Type Method
Let us get started by implementing the catsimulatorMeow
in C++:
#include <emojicode/runtime/Runtime.h>
extern "C" void catsimulatorMeow(runtime::ClassInfo*) {
puts("Meow!");
}
The method as we declared it in Emojicode does neither take arguments nor does it return a value. So we used void
as the return type in C++. Our method, however, does take an argument of type runtime::ClassInfo*
. This is because a type method in a class has the class as its context.
Function Signatures
Depending on the type of function you implement certain additional arguments are passed to your function. Moreover, you also need to return a value as expected by the Emojicode compiler. The following table shows these requirements:
Type | Kind of function | Additional Arguments | Expected Return |
---|---|---|---|
Class | Type method | runtime::ClassInfo* | as declared |
Class | Method | Callee Object | as declared |
Class | Initializer | Initialized Object unless class is foreign | Initialized Object |
Value Type | Type method | none | as declared |
Value Type | Method | Pointer to Callee Value | as declared |
Value Type | Initializer | Pointer to Initialized Value | void |
Additionally, an error-prone method or initializer takes a runtime::Raiser*
as the last argument.
In order to be able to find the correct function signature, youβll also need to map from Emojicode types to the proper C++ type. This table will help you:
Emojicode Type | C++ Type |
---|---|
π’ | runtime::Integer |
π― | runtime::Real |
π§ | runtime::Byte |
π | runtime::Boolean |
any enumeration | runtime::Enum |
ππ | runtime::Callable<...> |
π¬ | runtime::SimpleOptional<...> |
π‘ | s::String* |
π | s::Data* |
π§ | runtime::MemoryPointer<...> |
custom class | pointer to appropriate runtime::Object<T> subclass |
custom value type | pointer to appropriate struct |
Please see the C++ documentation for more detailed documentation on the mentioned types. The next section will discuss how you can represent class and value types in C++.
Never use an Emojicode type that was not mentioned in the above table and that you did not define yourself from C++.
Box Storage
It is important to understand that the Emojicode compiler will box values sometimes. Boxed values are used with generics for instance. Boxed values cannot be used from C++. To avoid any problems, do not implement methods that take arguments of a generic type or return a generic type in C++.
C++ Types for Emojicode Types
You can define C++ types that match Emojicode types you defined, to interact with them from C++.
In order to interact with class instances you have to create a C++ copy of the class. For example:
π π π± π
ππ name π‘
π
would translate to the following in C++:
#include <emojicode/s/String.h>
#include <emojicode/runtime/Runtime.h>
class Cat : public runtime::Object<Cat> {
public:
s::String *name;
};
SET_INFO_FOR(Cat, catsimulator, 1f431)
Every representation of an Emojicode class must inherit from the class runtime::Object
. Furthermore, you use the macro SET_INFO_FOR
to specify which Emojicode class the C++ class represents. The first argument to the macro is the C++ type. The second is the Emojicode package in which the Emojicode class is defined. The third is the Unicode code point of the name of the Emojicode class in lower case. We call such a C++ class a mirror class as it mirrors the Emojicode class definition.
Value Types can be represented by simple structs:
π π³ π
ππ number π‘
ππ expirationDate π‘
ππ securityCode π‘
π
struct CreditCard {
s::String *number;
s::String *expirationDate;
s::String *securityCode;
};
With this knowledge, weβll extend the π± class:
π π π± π
ππ name π‘
π πΌ name π‘ ππ
βοΈ π π» π€catsimulatorCatPrintπ€
π
#include <emojicode/s/String.h>
#include <emojicode/runtime/Runtime.h>
class Cat : public runtime::Object<Cat> {
public:
s::String *name;
};
extern "C" void catsimulatorCatPrint(Cat *cat) {
puts(cat->name->stdString().c_str()); // This is slow, don't do this
}
SET_INFO_FOR(Cat, catsimulator, 1f431)
What follows are some more examples of Emojicode initializer/method declarations and C++ functions:
βοΈ π search π offset π’ β‘οΈ π¬π’ π» π€sDataFindFromIndexπ€
extern "C" runtime::SimpleOptional<runtime::Integer> sDataFindFromIndex(Data *data, Data *search, runtime::Integer offset)
πβοΈ πͺ status π’ π» π€sSystemExitπ€
extern "C" void sSystemExit(runtime::ClassInfo*, runtime::Integer code)
βοΈ π β‘οΈ π― π» π€sRealSinπ€
extern "C" runtime::Real sRealSin(runtime::Real *real)
Borrowing and Escaping Use
Familiarize yourself with Borrowing and Escaping Use.
If you let a value escape in a method or initializer you implement in C++ it is crucial that you attribute the argument with ππ₯‘. If you let the callee itself escape attribute the function with ππ₯‘. Example:
ππ₯‘ π ππ₯‘ callback ππ π» π€sThreadNewπ€
Managing Memory
Arguments
Objects (class instances), callables and memory pointers are reference counted. This section applies to all of them.
Emojicode manages memory with reference counting. It is guaranteed that all objects that are passed into any Emojicode method whether implemented in C++ or not will be retained for the duration of the call. This means that you do not need to explicitly retain any of the objects provided to your method. If the value does not outlive the call.
If you, however, make a copy of the value or otherwise use the value in a way that makes use of it after the function has returned (i.e. starting another thread) you need to explicitly retain and release it appropriately.
Take the following simplified example from the s package:
extern "C" Thread* sThreadNew(runtime::Callable<void> callable) {
// ...
callable.retain();
std::thread([thread, callable]() {
callable();
callable.release();
});
// ...
}
When the callable is passed to sThreadNew
it is guaranteed to have a reference count of at least 1. Yet we want to call the callable on another thread. Due to the way threading works, this call will occur before or after sThreadNew
has returned. We therefore have to retain callable
so it is not deleted when sThreadNew
returns. In order not to produce a leak, we must also release it once we no longer need it.
If you need to retain a value somewhere this is a clear sign that you also let the value escape.
Return
An object returned from a function must have a reference count of 1 or greater.
Instantiating Emojicode Classes from C++
If you define a mirror C++ class for an Emojicode class and appropriately used the SET_INFO_FOR
macro, you can create instances of the class in Emojicode.
Here is the mirror class for π‘ for example:
class String : public runtime::Object<String> {
public:
String(const char *string);
// ...
runtime::MemoryPointer<char> characters;
runtime::Integer count;
// ...
};
SET_INFO_FOR(String, s, 1f521)
You can then create an instance of this class using the class method init
inherited from runtime::Object<T>
. init
forwards all arguments to the subclasses constructor. So we could create an Emojicode string from a C++ string by doing:
String::init("Test");
This works because the constructor String(const char *string)
was defined for the String
class above.
If you do not define an appropriate constructor for a mirror class, it is possible to create an improperly initialized instance, which will cause undefined behavior. Avoid instantiation from C++.
An instance returned from init
always has a reference count of 1. This means that you can, for instance, immediately return it.
Foreign Classes
As said before, implement everything that can be implemented in Emojicode, in Emojicode. For instance, the s package implements the basic data structures like π¨ and π― entirely in Emojicode. Substantial parts of π‘ are implemented in Emojicode too, only the parts that have to deal with complex logic and use a a third-party library are implemented in C++.
The π‘ class is therefore defined in Emojicode like this:
π π π‘ π
ππ bytes π§
ππ count π’
π ...
π
Sometimes, however, a class has to store something that cannot be represented in Emojicode, because it is a platform dependent value or system resource for example. An example from the s package is the 𧡠class. Threads are very platform dependent and there is no safe way to represent std::thread
in Emojicode. Thus, there is no way to fully define the class 𧡠in Emojicode. This is where foreign classes come in. Foreign classes are defined by attributing them with π». The 𧡠is defined like this:
π π» π 𧡠π
π ...
π
This tells the compiler that the exact definition of the class is unknown. Consequently, you must not declare any instance variables or any initializer that is implemented in Emojicode.
The class defines its initializer like this:
π π» π 𧡠π
π ππ₯‘ callback ππ π» π€sThreadNewπ€
π ...
π
On the C++ side the following happens:
class Thread : public runtime::Object<Thread> {
public:
std::thread thread;
};
extern "C" Thread* sThreadNew(runtime::Callable<void> callable) {
auto thread = Thread::init();
// ...
return thread;
}
SET_INFO_FOR(Thread, s, 1f9f5)
Note that sThreadNew
does not take Thread*
or any other pointer as first argument like initializers would do normally. Naturally, no code for object allocation can be generated by the Emojicode compiler. Thus, we create the instance ourselves by calling Thread::init()
, which we can do, since we have a full definition of the class. We return this value at the end, like any initializer does.
Do not abuse foreign classes. If the class can be defined in Emojicode, define it there (too).
If your foreign class stores an object that requires destruction make sure to implement an appropriate deinitializer and call your C++ classβ destructor:
extern "C" void sThreadDestruct(Thread *thread) {
thread->~Thread(); // Manually call the destructor
}
β»οΈ π» π€sThreadDestructπ€
The C++ destructor of a foreign class is not called by default when the object is deleted!
Raising an Error
An error-prone method or initializer takes a runtime::Raiser*
as its last argument. For instance:
extern "C" Data* filesFileReadFile(runtime::ClassInfo*, String *path, runtime::Raiser *raiser) {
// ...
}
runtime::Raiser
only provides one method:
template <typename T>
void raise(T *errorObj, const char *location)
It can be used to raise an error. The first argument must be an error object allocated by the usual init
call. The second argument should describe the location where the error was raised. You must immediately return from the function after raising an error. The return value of the function will be ignored and can thus be an undefined value.
It is strongly recommended that you use the macros EJC_RAISE
and EJC_RAISE_VOID
to raise errors, which also handle returning.
extern "C" Data* filesFileReadFile(runtime::ClassInfo*, String *path, runtime::Raiser *raiser) {
// ...
if (/* error condition */) {
EJC_RAISE(raiser, s::IOError::init());
// this line is unreachable as EJC_RAISE returns
}
// ...
}
Can I Create a List or Dictionary from C++?
No. Not at this time.
How to Link to Shared Libraries?
See Specifying Shared Libaries To Link.
How Do I Do β¦?
Please check if the s package does something similar. It is a great resource for examples. If not please ask in the chat or open an issue on GitHub.