zproject(part 2): the generated files
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;
}
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;
}
}
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>
<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);
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;
}
// 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;
}
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");
}
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