Skip to content
Zwetan Kjukov edited this page Feb 14, 2017 · 14 revisions

Native Definitions

Everything there is to know and other notes to write native definitions.

Introduction

An ActionScript 3.0 definition can be a Class, an Interface, a variable, a function, a constant, etc.

A native definition is a definition that is implemented either natively in C/C++, or in pure ActionScript 3.0 or a bit of both.

What makes it native is that the implementation is embedded in the runtime (the Redtamarin shell).

Here few properties of a native definition:

  • it is initialised before any user code
  • it is available at all time
  • if there is a bug it will most likely crash the runtime
    and prevent any user code to run

Naming

Any definition must be unique, especially class name.

Even when a class definition is isolated by its package, the class name need to be unique.

For example: a FileStream class

You can not have flash.filesystem.FileStream and shell.streams.FileStream it will creates a name collision.

Why?
When we run the nativegen.py script to generates glue code for those native definitions it creates a "shell manifest", so the shell can access from the C/C++ frontend any definitions

see src/generated/shell_toplevel.h

class shell_toplevelClassManifest : public avmplus::ClassManifestBase
{
    friend class avmplus::AvmCore;
    friend class avmplus::IntVectorClass;
    friend class avmplus::UIntVectorClass;
    FLOAT_ONLY(friend class avmplus::FloatVectorClass;)
    FLOAT_ONLY(friend class avmplus::Float4VectorClass;)
    friend class avmplus::DoubleVectorClass;
    friend class avmplus::ObjectVectorClass;
private:
    REALLY_INLINE shell_toplevelClassManifest(avmplus::ScriptEnv* e) : ClassManifestBase(168, e) { }
    REALLY_INLINE static shell_toplevelClassManifest* create(avmplus::ScriptEnv* e) { return new (MMgc::GC::GetGC(e), MMgc::kExact, sizeof(ClassClosure*)*167) shell_toplevelClassManifest(e); }
public:
    //...
    REALLY_INLINE GCRef<avmplus::FileStreamClass> get_FileStreamClass() { return (avmplus::FileStreamClass*)(lazyInitClass(avmplus::NativeID::abcclass_flash_filesystem_FileStream)); }
    //...
    REALLY_INLINE GCRef<avmplus::FileIOStreamClass> get_FileIOStreamClass() { return (avmplus::FileIOStreamClass*)(lazyInitClass(avmplus::NativeID::abcclass_shell_streams_FileIOStream)); }
   //...
};

This is a limitation but also very convenient when you need to reuse an AS3 class from your C/C++ code.

That's why we have flash.filesystem.FileStream and shell.streams.FileIOStream,
the class name must be unique but we do not want to change the Flash or AIR API classes name.

Limitations

Even if you write your native definition in pure AS3, you still need to be more careful about it.

Always use full import paths

Do not use shortcuts like import C.stdio.*;,
use the full path like import C.stdio.fread;

Why?
In most case it would work, but in some particular cases it may leak a definition into the unnamed package and then it will creates a bug when running the nativegen.py script.

Always use full extends or implements paths

Do not use shortcuts like public class Something implements IDataOutput,
use the full path like public class Something implements flash.utils.IDataOutput

Why?
In most case it would work, but in some particular cases it may leak a definition into the unnamed package and then it will creates a bug when running the nativegen.py script.

Always use builtin types for your constants

In ActionScript 3.0, you can have this pattern of instantiating a class into a constant,
for example:

package test.whatever
{

    public class Something
    {

        public static const ONE:Something = new Something( "one" );
        public static const TWO:Something = new Something( "two" );

        private var _name:String;

        public function Something( name:String )
        {
            _name = name;
        }

    }
}

Which is valid and convenient when you want to pass a specially typed constant as an argument:

public function doTheWorkWith( something:Something ):void
{
    // implementation
}

Don't do that for native definitions, use builtin types only,
eg. String, Number, int, uint, etc.

Why?
When the shell initialises it creates first the builtin domain environment, then it creates
the shell_toplevel domain environment, and finally it creates the user_domain domain environment.

Each domain environments are linked as a parent of each other:

builtin
    |_ shell_toplevel
               |_ user_domain

Native definitions are defined in the shell_toplevel domain environment, and not all definitions could be available to create a constant inside the shell_toplevel domain.

You need to wait to be inside the user_domain domain to be sure to have access to all shell_toplevel definitions.

So when you are inside the shell_toplevel domain environment, the only constants you can be sure to use safely are the ones defined in the builtin domain which is declared as the parent of the shell_toplevel domain.

Now you know why all constants in the Flash and AIR API are mostly declared as String.

Note:
A little trick and exception to this rule: the boot.as file (see src/as3/shell/boot.as).
This file is what we call our "boot system" or the "redtamarin boot sequence", it allows us to do some "wiring" like instantiating some variables that are defined in the native definitions.

This boot is placed in such a way that we are sure it is always executing at the end of our native definitions (the shell_toplevel domain) but before any user code (the user_domain domain).

So yeah with the boot.as we can lazy-init some instantiations but it does not solve the problem of type definitions being available ahead of time for constants, see the following example

In the Program class you have 3 properties:

package shell
{

    import shell.streams.StandardInputStream;
    import shell.streams.StandardOutputStream;
    import shell.streams.StandardErrorStream;

    [native(cls="::avmshell::ProgramClass", classgc="exact", methods="auto", construct="none")]
    public class Program
    {
        //...

        public static var standardInput:StandardInputStream;

        public static var standardOutput:StandardOutputStream;

        public static var standardError:StandardErrorStream;
    }

}

You can declare variables and constants using the type, but you could not instantiate the type itself.

Doing something like that:

        public static var standardOutput:StandardOutputStream = new StandardOutputStream();

This would create an error, even if the type is known it is not necessary available for instantiation.

That's why we used the boot.as file to lazy-init these properties at the very end of the native definitions.

in boot.as

package shell
{
    
    import shell.streams.StandardInputStream;
    import shell.streams.StandardOutputStream;
    import shell.streams.StandardErrorStream;

    //...

    Program.standardInput  = new StandardInputStream();
    Program.standardOutput = new StandardOutputStream();
    Program.standardError  = new StandardErrorStream();

    //...
}

This can work because those properties are variables, it would not work with constants.



Example of workarounds: with the IPv4Address class.

1. Use variables and lazy-init from within the class itself:

package shell.network
{

    public class IPv4Address
    {

        public static var LOOPBACK:IPv4Address;

        public static function _init()
        {
            LOOPBACK = new IPv4Address( "127.0.0.1/32" );
        }

        public function IPv4Address( any:* = null )
        {
            //...
        }
    }

    IPv4Address._init(); // the lazy-init happens here
}

here some basic usage:

// oh I need an IPv4 loopback
someCall( IPv4Address.LOOPBACK ); // we reuse an already instantiated class

Problem is that this variable can be overridden

IPv4Address.LOOPBACK = new IPv4Address( "192.168.0.1" );

2. Use constants and builtin types:

package shell.network
{

    public class IPv4Address
    {

        public static const LOOPBACK:String = "127.0.0.1/32";

        public function IPv4Address( any:* = null )
        {
            //...
        }
    }

}

But then the usage differ:

// oh I need an IPv4 loopback

// we have to instantiate a class
someCall( new IPv4Address( IPv4Address.LOOPBACK ) ); // less convenient

3. Use getters and lazy-init from within the class itself:

package shell.network
{

    public class IPv4Address
    {

        private static var _LOOPBACK:IPv4Address;

        public static function get LOOPBACK():IPv4Address { return _LOOPBACK; }

        public static function _init()
        {
            _LOOPBACK = new IPv4Address( "127.0.0.1/32" );
        }

        public function IPv4Address( any:* = null )
        {
            //...
        }
    }

    IPv4Address._init(); // the lazy-init happens here
}

basic usage is the same as workaround 1:

// oh I need an IPv4 loopback
someCall( IPv4Address.LOOPBACK ); // we reuse an already instantiated class

The big advantage being you can not override a read-only getter, but yeah the setup is a bit more complicated.

Now, all that said, we are still maintaining this statement
Always use builtin types for your constants!!

So yeah, for "native code", we advise to use builtin types with constants, even if those are less convenient than a specific Class (eg. workaround 2).



Example of fail: with the SocketFamily class.

the original definition was

package shell.network
{
    import C.sys.socket.AF_UNSPEC;
    import C.sys.socket.AF_INET;
    import C.sys.socket.AF_INET6;

    public class SocketFamily
    {
        public static const UNSPECIFIED:int = AF_UNSPEC;

        public static const IPv4:int = AF_INET;

        public static const IPv6:int = AF_INET6;
    }
}

And this fail, yep even an integer definition will not be found from another package.

Here our problem is that things like AF_INET are in fact getters defined in native classes.

See src/api/clib/CSysSocketClass.h

namespace avmshell
{
    using namespace avmplus;

    class CSysSocketClass : public ClassClosure
    {
    public:
        CSysSocketClass(VTable* cvtable);
        ~CSysSocketClass();
        //...

        int get_AF_INET();
        int get_AF_INET6();
        int get_AF_UNSPEC();

        //...
        DECLARE_SLOTS_CSysSocketClass;
    };
}

See src/api/clib/CSysSocketClass.cpp

namespace avmshell
{

    CSysSocketClass::CSysSocketClass(VTable *cvtable)
        : ClassClosure(cvtable)
    {
        createVanillaPrototype();
    }

    CSysSocketClass::~CSysSocketClass()
    {
        
    }

    //...

    int CSysSocketClass::get_AF_INET() { return AF_INET; }
    int CSysSocketClass::get_AF_INET6() { return AF_INET6; }
    int CSysSocketClass::get_AF_UNSPEC() { return AF_UNSPEC; }

    //...
}

We do that in case of different constant values between operating systems, but then it makes those constants "dynamic" sort of.

So for this to work we need to do that:

package shell.network
{
    import C.sys.socket.AF_UNSPEC;
    import C.sys.socket.AF_INET;
    import C.sys.socket.AF_INET6;

    public class SocketFamily
    {
        public static function get UNSPECIFIED():int { return AF_UNSPEC; }

        public static function get IPv4():int { return AF_INET; }

        public static function get IPv6():int { return AF_INET6; }
    }
}

eg. we need to use getters instead of constants.

C++ Front-end

TODO

Types

ActionScript 3.0 C/C++

ActionScript 3.0 Front-end

TODO

Native Metadata

Clone this wiki locally