Skip to content

Beginner Contributor Guide C Wrappers

Abinash Meher edited this page Aug 20, 2015 · 2 revisions

Beginner Contributor Guide - C Wrappers

Wrapping the features of SymEngine is a two step approach:

  1. Wrapping any data structures the feature depends on, in C.
  2. Writing an interface function that calls the corresponding C++ function internally.

There is a third obvious step that you must do is write Unit Tests. They are nothing other than using your wrapped function in all possible situations and ensuring that they give expected results.

Background

The approach that we use is, we use pointers for the objects of C++ made. Then all the interface functions are called by dereferencing those pointers, where we are free to use the C++ code.

Example

Suppose we have a class Test

//File: test.hpp
class Test {
    public:
        int testfunc();
        Test();

    private:
        int testint;
};
//File: test.cpp
#include <iostream>
#include "test.hpp"

using namespace std;

Test::Test() {
    this->testint = rand()%1000 ;
}

int Test::testfunc() {
    return this->testint;
}

We will have some glue code. This code is something in-between C and C++. We will have one header file (test_cwrapper.h, just .h as it doesn't contain any C++ code)

//File: test_cwrapper.h
typedef struct CTest CTest;

CTest* test_new();
int test_testfunc(CTest *self);
void test_delete(Ctest *self);

and the function implementations (test_cwrapper.cpp, .cpp as it contains C++ code). Since we need to use the functions in C but the compiler is C++, we need to expose the functions through extern "C".

//File: test_cwrapper.cpp
#include "test_cwrapper.h"
#include "test.hpp"

extern "C" {

struct Ctest {
    Test m;
}

CTest* test_new()
{
    return new CTest;
}

int test_testfunc(CTest *self)
{
    return self->m.testfunc();
}

void test_delete(CTest *self)
{
    delete self;
}

} //extern "C"

Now, applying this general rule to every feature than needs to be ported, we can use all the features in SymEngine C++ library in C.

For more information on how we wrap the dependent data types, refer to this post.

Current Implementation

Our current implementation has all the interface functions in the header file. The header file uses C only. Whereas the implementation is in the source file that uses only C++. You can and should use the options given by C++11 in cpp file, if that is any advantageous in terms of readability or performance.

The function declarations (in C) go to the header file (cwrapper.h) and the function definitions or any implementations that use C++, go inside the extern "C"{ } block in the cpp file(cwrapper.cpp). The extern block ensures that function name won't be changed (see name mangling) by C++ compilers and hence C code can link to it, i.e. can be used from C. Since it is compiled by a C++ compiler, we can use C++ inside it. After you have wrapped the feature, you need to write a unit test. The test file for C wrappers is test_cwrapper.c.

The common data structure, for all SymEngine types, in C is basic. When you write

basic a;

You actually get an array of basic_struct of length 1, and a points to the first address of the array. We now say that the space for basic_struct has been allocated but not initialised. Therefore, this step is followed by

basic_new_stack(a);

This initialises the struct. Therefore, doing

basic a;
basic_new_stack(a);

and

basic_struct *a = basic_new_heap();

are the same thing except that in the first case (basic_new_stack()) the memory for the basic_struct is allocated on the stack and in the second case (basic_new_heap()) the memory is allocated on the heap. It gives a significant performance advantage when variables are on the stack.

Accordingly, functions basic_free_stack() and basic_free_heap() are used to free memory respectively in both the cases.

basic actually wraps a pointer (RCP, or a reference counting pointer more precisely) to Basic class in SymEngine, which is the base class for all other classes in C++. So to make an object of another class, you need to declare and define a function that takes the basic (pointer to basic_struct that wraps a RCP to Basic) and calls a constructor within the function, returning a pointer to that newly created class(i.e. copying the new Basic pointer to the location where the basic points to). Now, to know what type of object it is, you can use the

TypeID basic_get_type(const basic s);

There are also interface functions to check types like

//! Return 1 if s is an Integer, 0 if not.
int is_a_Integer(const basic s);

Using the functions

Always use the interface functions given by SymEngine, when you use the library in C. Like to copy the value from one basic to another, use

void basic_assign(basic dest, const basic src)`;

And to compare two basic objects in C

void basic_eq(const basic a, const basic b);

etc.