In the first part we saw how to create and build a zproject and in this part we will have a look at the code that is actually generated and add some methods to our class using an corresponding api-file.

include/shouter.h:

/*  =========================================================================
    shouter - class description

    Copyright (c) the Authors
    =========================================================================
*/

#ifndef SHOUTER_H_INCLUDED
#define SHOUTER_H_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif

//  @interface
//  Create a new shouter
GS_EXPORT shouter_t *
    shouter_new (void);

//  Destroy the shouter
GS_EXPORT void
    shouter_destroy (shouter_t **self_p);

//  Self test of this class
GS_EXPORT void
    shouter_test (bool verbose);

//  @end

#ifdef __cplusplus
}
#endif

#endif

These is the generated header code for a default-class:

  • shouter_new() : the constructor will allocate memory on the heap and return the instance-data.
  • shouter_destroy() : the destructor will free the memory again.
  • shouter_test(bool): a self test in which you can add some test-logic that can be triggered via the automatically generated src/xx_selftest.c
  • Important to know: you can modify this file BUT must not do this in between the generation-block marked by '//  @interface' and '// @end'

src/shouter.c

The c-file is generated once and from the one never been touched again.

#include "gs_classes.h"

//  Structure of our class

struct _shouter_t {
    int filler;     //  Declare class properties here
};

Line1: include a header to all project visible data.
Line3: specify internal data

//  --------------------------------------------------------------------------
//  Create a new shouter

shouter_t *
shouter_new (void)
{
    shouter_t *self = (shouter_t *) zmalloc (sizeof (shouter_t));
    assert (self);
    //  Initialize class properties here
    return self;
}
Constructor

This is the implementation of the constructor. Initialize your internal struct-data here.

//  --------------------------------------------------------------------------
//  Destroy the shouter

void
shouter_destroy (shouter_t **self_p)
{
    assert (self_p);
    if (*self_p) {
        shouter_t *self = *self_p;
        //  Free class properties here
        //  Free object itself
        free (self);
        *self_p = NULL;
    }
}
Destructor

In the destructor free all needed resources.

I will ignore the third method in which you can add some test-code.

Extend the API

Let's add some custom methods. This is done via an api-file with the same name of the class-name that is put in the api-folder.

So let's create a file 'api/shouter.api':

<class name = "shouter">
    The shouter-class the one and only way to shout!

    <constant name = "DEFAULT_SHOUT" type="string" value = "HOORAY!">The default shout</constant>
    <constant name = "some_int" value="1895">This is an int constant</constant>

    <constructor>
        Create a new shouter
        <argument name = "name" type = "string" />
    </constructor>

    <destructor>
    </destructor>

    <method name = "shout">
    Shout once!
        <argument name="shout_text" type="string">The text to be shoutet</argument>
        <return type="integer"/>
    </method>

    <method name = "shout_multi">
    Shout multiple times!
        <argument name="shout_text" type="string">The text to be shoutet</argument>
        <argument name="times" type="integer">The amount of times shouting the text!</argument>
        <return type="integer">Return the amount of times shouted</return>
    </method>
</class>
api/shouter.api

<constant>-tag will result in #define-constants:

  • name-attribute: constant-name
  • type-attribute: the type (default 'integer')
  • value-attribtue: the value

<constructor/>-tag will generate in the header-file a method-signature  that returns a pointer to the internal data-struct. Can have multiple arguments, no return-type. Enclosing text is used as doc

  • name-attr: you can only have one default-constructor, more need to have another name. (e.g. 'create_from_data')

<method/>-tag will generate a method signature with specified arguments and return type.

  • name-attr: the name of the method

<argument/>-tag: define argument for method or constructor

  • name-attr: the name of the argument
  • type-attr: the type: "string","integer","real" and "number"(with size-attr),anything(void*), and many more (see SampleAPI-File)

<return/>-tag wil specify what type is returned

  • type: return type

<destructor/> will define the constructor. (afaik, if not specified the destructor is created but not visible everywhere)

What was generated?

Now regenerate the project:

gsl project.xml

This will create a new constructor in the header (include/shouter.h)

//  *** Draft method, for development use, may change without warning ***
//  Create a new shouter
GS_EXPORT shouter_t *
    shouter_new (const char *name);

You will have to modify the current one accordingly.

The new methods:

//  *** Draft method, for development use, may change without warning ***
//  Shout once!
GS_EXPORT int
    shouter_shout (shouter_t *self, const char *shout_text);

//  *** Draft method, for development use, may change without warning ***
//  Shout multiple times!
GS_EXPORT int
    shouter_shout_multi (shouter_t *self, const char *shout_text, int times);
generated methods in include/shouter.h

As you can see, the method names are all prefixed by [class-name]_ as it was done with the constructor('shouter_new') and destructor('shouter_destroy') as well.

Now we need to implement the new methods and the new signature of the constructor with some simple logic:


shouter_t *
shouter_new (const char* name)
{
    shouter_t *self = (shouter_t *) zmalloc (sizeof (shouter_t));
    assert (self);
    //  Initialize class properties here
    printf("Created shouter:%s\n",name);
    return self;
}
constructor
//  Shout once!
int shouter_shout (shouter_t *self, const char *shout_text)
{
    printf("SHOUT:%s\n",shout_text);
    return 1;
}

//  Shout multiple times!
int shouter_shout_multi (shouter_t *self, const char *shout_text, int times)
{
    for (int i=0;i<times;i++){
        shouter_shout(self,shout_text);    
    }
    return times;
}
methods

Let's implement some test:


void
shouter_test (bool verbose)
{
    printf (" * shouter: ");

    //  @selftest
    //  Simple create/destroy test
    shouter_t *self = shouter_new ("shouter1");
    assert (self);

    int times = shouter_shout(self,"forty");
    assert(times==1);

    times = shouter_shout_multi(self,"multi_shoot",4);
    assert(times==4);

    shouter_destroy (&self);
    //  @end
    printf ("OK\n");
}
Test

After compiling you can start run the test:

./gs_selftest

// show help
./gs_selftest -h
>  --verbose / -v         verbose test output
>  --number / -n          report number of tests
>  --list / -l            list all tests
>  --test / -t [name]     run only test 'name'
>  --continue / -c        continue on exception (on Windows)

// e.g. to show all tests:
./gs_selftest -l
>Available tests:
>    shouter                                  - draft    public

Feel free to add some logic to starter.c as well

Ok, that is it for this part.
On part3 we will add some targets to generate Docker-Files and bindings for python,java and lua.

Refs:
Sample-API-File with lots of info