CMOD

[ Start > PikeDevel > C Modules > CMOD ] [ Edit this Page | Show Page Versions | Show Raw Source ]


What is CMOD?

CMOD is the name of a simplified mechanism for writing extensions to the Pike programming language. CMOD files consist of C language code plus simplified function definition elements that handle many of the most tedious tasks associated with module development. A CMOD file is pre-processed by the CMOD precompiler resulting in a standard C file that is then compiled normally.

Tasks the CMOD compiler takes care of:

  • Module initialization and shutdown
  • Class definition
  • Storage allocation for objects (including modules)
  • Function definition and registration
  • Variable definition and storage
  • Function argument checking and passing
  • Return value handling
  • Polymorphic function overloading (function variants based on function type signature)
Limitations, according to the precompiler:
  • Parenthesis must match, even within #if 0
  • Not all Pike types are supported yet.
  • No support for functions that take a variable number of arguments yet.
  • RETURN; (void) doesn't work yet
  • need a RETURN_NULL; or something.. RETURN 0; might work but may be confusing as RETURN x; will not work if x is zero.
o Comments does not work inside prototypes (?)

In short, for most module applications, using CMOD can save you a large amount of time and effort, particularly in the area of avoiding bugs. Function bodies are written using standard C, so programming skills will transfer directly.

Tools and Samples

Sample Module

A sample module that can be used as a starting point for further module development can be found at http://hg.welliver.org/pike_modules-samplemodule

This sample module represents a minimal CMOD module with a Pike module over-wrapper. The over-wrapper in this case is non-functional, and is included to show the necessary constants for providing module repository functionality.

Module Stub Generator

From Pontus Östlund comes a program (with both a GTK and CLI interface) that creates Pike module stubs - both CMODs and PMODs:

https://github.com/poppa/Pike-Modules/blob/master/tools/pike-project

Elements of a CMOD file

The following represents the basic structure of a CMOD file. We'll cover each element in turn afterward.

// PIKE INCLUDES MUST BE PRESENT FOR COMPILATION.

PIKECLASS fnord attributes; { INHERIT bar attributes;

CVAR int foo; PIKEVAR mapping m attributes;

DECLARE_STORAGE; // optional

PIKEFUN int function_name (int x, CTYPE char * foo) attribute; attribute value; { C code using 'x' and 'foo'. RETURN x; } INIT { // Object initialization code. }

EXIT { // Object cleanup code. } GC_RECURSE { // Code to run under the gc recurse pass. }

GC_CHECK { // Code to run under the gc check pass. }

EXTRA { // Code for adding extra constants etc. }

OPTIMIZE { // Code for optimizing calls that clone the class. // The node for the call is available in the variable n. } }

PIKECLASS is used to define the code for a given class in Pike. These code blocks may be nested in order to provide sub-classes, and multiple PIKECLASS blocks may appear sequentially within a given CMOD file. Strictly speaking, the PIKECLASS definition is optional, in as far as without one, there will be no class, and any variables and functions will appear directly within the resulting module. Note that if you are combining multiple CMOD files into one module, you may only use this technique within one of them, otherwise you will get compiler conflicts.

PIKECLASS/PIKEFUN Attributes

Attribute Purpose
efun; makes this function a global constant (no value)
flags [FLAGS]; ID_STATIC | ID_NOMASK etc.
optflags [FLAGS]; OPT_TRY_OPTIMIZE | OPT_SIDE_EFFECT etc.
optfunc; Optimization function.
type; Override the pike type in the prototype with this type. FIXME: this doesn't quite work
rawtype [TYPE]; Override the pike type in the prototype with this C-code type, e.g. tInt, tMix etc.
errname [NAME]; The name used when throwing errors.
name [NAME]; The name used when doing add_function.
prototype; Ignore the function body, just add a prototype entry.
program_flags [FLAGS]; PROGRAM_USES_PARENT | PROGRAM_DESTRUCT_IMMEDIATE etc.

INHERIT

The INHERIT keyword is used to specify inheritance for a class. The token immediately following the keyword should be the name of a class within the CMOD.

CVAR

Defines a C level variable within the class of the C type and name specified. Multiple CVAR definitions may be provided, or you may define a struct to hold multiple data items for the object. Note that you must allocate and free any memory for any pointers defined, typically these happen in INIT and EXIT blocks. Ie. if we declare a CVAR

CVAR int example;

later we can access the variable in the module instance's memory space using the THIS pointer.

THIS->example++;

DECLARE_STORAGE

__PIKEVAR__

Defines a Pike accessible variable within the class of the type and name specified. You may access variables defined in this way using one of the macros defined by the preprocessor, such as THIS->varname. The variable will be defined as the appropriate C-level type or structure, except in the situation of variable argument types, which will be stored as svalue pointers.

PIKEFUN

The PIKEFUN keyword is used to define the beginning of a code block with impliments a Pike function. The keyword is used in this manner:

PIKEFUN returntype functionName(piketype arg1, ...)
{
  // C-level code
}

The PIKEFUN keyword will cause your function to be registered, and code will be created to handle argument checking and take the incoming arguments on the stack and assign them to c variables of the approriate type and name.

For example, if you defined a function like this:

PIKEFUN int strlen(string my_string)
{
  ...
}

Then your function, strlen, would be registered and code would be generated to ensure that checks the input argument count and types, throwing an error in the event that things do not match up. In this example, the variable my_string would be a struct pike_string. Accordingly, you could use write the following code (which isn't recommended, for various reasons):

PIKEFUN int strlen(string my_string)
{
  int r;

printf("we have a string: %sn", my_string->str);

// we can't really do this reliably, as pike strings can contain binary data r = strlen(my_string->str);

RETURN r; }

RETURN

The RETURN keyword will cause the arguments to the function to be popped from the stack, and the value immediately following the RETURN to be pushed onto the stack using the push function appropriate for the return type. Note that this means that if you have a variable return type, you will have to do this yourself.

INIT

This block is used to provide any code that must be performed when the object is initialized, but before create() is called. This might include variable initialization or memory allocation.

EXIT

Specify any code that must be performed in this block to de-initialize the object. This code is called following any applicable destruct() methods. You should free any storage that you have previously allocated during the lifetime of this object in this block.

EXTRA

This code block typically contains code for adding any constants to the module. This code is called after the class has been defined, and before any INIT blocks are called. This block may also be used to call initialization methods for "multi CMOD modules," as described in a later section.

GC_CHECK

__GC_RECURSE__

OPTIMIZE

Multiple CMOD files in a single module

At a certain point, a module cannot be reasonably managed as a single source file. Typically, a module will be split into multiple classes, and it is often desirable to have each class be contained in a separate file. The Pike module build system will happily compile multiple CMOD files and link them into a single module shared object. However, a few steps must be taken in order for the resulting object to be functional.

The first step is to identify the CMOD which will be the "master" from which the other CMODS will be initialized. Typically, if you have a CMOD that provides a set of module level PIKEFUN definitions (that is, they are not enclosed within a PIKECLASS,) it would make sense to use that as the master. Indeed, this method may not even work if you don't (this theory has not been tested).

In each "non-master" CMOD in your module, you need to define your INIT and EXIT blocks as usual. Additionally, you need to create an init and exit function that will be called by your "master" CMOD at the appropriate time. The only contents of these two functions should be the INIT or EXIT keyword, respectively. The "master," upon instantiation, will call the init and exit functions of within each of the other CMODS (make sure these functions are all uniquely named). The special keywords in each will trigger the class definition code to be called, making the classes available to Pike. These INIT and EXIT keywords are not the same as the INIT and EXIT code blocks, and server very different purposes (the latter is to perform initialization tasks for each object instance of the class; the latter is to register the class's definition with pike.

void cmod_init_classn()
{
  INIT
}

void cmod_exit_classn() { EXIT }

Then, back in your "master" CMOD, you'd add the following code to your EXTRA and EXIT blocks, in order to call the initialization functions for each CMOD in your module. Note that you don't have to set up a function for the "master" CMOD, as that's handled by INIT and EXIT already.

EXTRA 
{
  ...

cmod_init_class1(); ... cmod_init_classn();

... }

EXIT { cmod_exit_class1(); ... cmod_exit_classn();

// perform your other exit tasks ... }


Powered by PikeWiki2

 
gotpike.org | Copyright © 2004 - 2009 | Pike is a trademark of Department of Computer and Information Science, Linköping University