Interoperability with C¶
View interopWithC.chpl on GitHub
By default, Chapel programs are compiled as the main program. Sometimes the program is intended as a library whose contents can be called from other sources. This primer will demonstrate how to call Chapel functions from C as well as how to use C symbols in Chapel.
Chapel as a Library¶
Symbol Availability¶
When creating a library file from Chapel code, only those symbols with
export
attached to them will be available from outside the library.
module interopWithC {
export proc foo() { // foo will be available to outside code
writeln("Called foo()");
}
proc bar() { // bar will not be available to outside code
writeln("Called bar()");
}
At present, only functions can be exported from Chapel code.
Compiling the Chapel Library¶
To generate a library from a Chapel code file, compile the Chapel file with
the --library
flag. For example:
chpl --library interopWithC.chpl
There are various flags and settings that can be used to modify the library
that gets created and create additional helper files. These are listed in
detail in the Calling Chapel Code from Other Languages Technical Note. For this primer, we
will focus on the default settings and --static
compilation - this will
rely on the generated library being named libinteropWithC.a
, with a
generated header file named interopWithC.h
, both of which live in a
generated lib
directory.
Since --static
is not supported on MacOS you will have to dynamically link
the generated library. Please refer to Calling Chapel Code from Other Languages for instructions
on how to do that.
Using the Chapel Library¶
To access the symbols in the generated Chapel library from a C program,
#include
the generated header file:
// Include the generated header file
#include "lib/interopWithC.h"
Before calling any of the functions defined by the library, the Chapel
runtime and internal libraries must be initialized. This is done by calling
chpl_library_init
, which is defined in
$CHPL_HOME/runtime/include/chpl-init.h
and accessible with the above
#include
.
int main(int argc, char* argv[]) {
// Initialize the Chapel runtime and internal libraries
chpl_library_init(argc, argv);
Then, if the exported functions rely upon any global variables, the module initialization function declared in the header file must be called. If there is uncertainty about whether doing so is necessary, it is recommended to call this function. In this example, this call occurs like this:
// Initialize the compiled Chapel library and any used modules
chpl__init_interopWithC(0, 0);
After that, the exported functions may be called:
foo();
alsoCallsBaz();
callUseMyType();
We will see how we exported alsoCallsBaz()
and callUseMyType()
as
well as their definitions later.
When ending the C program, the user must explicitly shut down the Chapel
runtime and module code. This is done by calling chpl_library_finalize
:
// Shutdown the Chapel runtime and standard modules
chpl_library_finalize();
return 0;
} /* main */
Note
Once shut down, the Chapel runtime cannot be restarted in the current C program.
Compiling C Code with the Chapel Library¶
Compiling C code with the generated Chapel library file is generally complex,
but can be made simpler through the use of the --library-makefile
flag
as described in Compiling C Code with the Library.
An example of compiling a C program with a generated Chapel library using the generated Makefile can be found under the interopWithC target in the Makefile for the primers directory, to build this source file. An example of using the generated Makefile can be seen in Makefile.cClient. To build the C client, first run make interopWithC then run make -f Makefile.cClient.
Using C Code in Chapel¶
Chapel has support for C code but we need to tell the compiler
about the C symbols using the extern
keyword.
Using the extern
keyword for declaring external
functions can be done as follows:
extern proc baz(): int;
The function can then be called as normal:
proc callsBaz() {
writeln(baz());
}
Such functions can even be called in exported functions:
export proc alsoCallsBaz() {
writeln(baz());
callsBaz();
}
You can tell the chapel compiler where to look for these C functions by adding a require statement:
require "cHelper.h", "cHelper.c";
When requiring a C file, object file, or archived library, the appropriate header file must also be required.
Chapel also supports require statements for .o
files and
for archived libraries using the -l
flag:
require "bar.o", "-lfoo";
Alternatively you can also include their names while invoking the chapel compiler:
chpl cHelper.h cHelper.c foo.h -lfoo interopWithC.chpl
Unlike export
, extern
can also be applied to global variables,
struct definitions, or even typedefs.
extern var x: int(32);
extern var y: uint(32) = 3;
extern type myType = int;
extern proc useMyType(arg: myType): int; // an extern function using the typedef
export proc callUseMyType() {
var blah: myType = 17;
writeln(useMyType(blah));
}
Chapel also has a standard module named CTypes
(located in
$CHPL_HOME/modules/standard/
).
This module defines a few C types which align with the C compiler
specification and do not require the extern
keyword,
such as c_int
and c_char
. For more information about these types see
the C Interoperability Technical Note.
You can include CTypes using a simple use statement:
use CTypes;
We must always make sure the types align as the C compiler specification allows for different sizes for the same type depending on the compiler.
In order to pass an array to C, we use the following declaration:
extern proc sumArray(arr: [] int, size: c_int): c_int;
Where an array would be
var arr : [0..9] int = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Keep in mind that since Chapel has 64 bit ints, so is the array we created above.
Therefore, the C function must also accept an array of type int64_t
.
writeln("Sum of Array: ", sumArray(arr, 10:c_int));
While dealing with extern proc
s, you can also assign default arguments to them
which then can be omitted at the call site as usual. For example:
extern proc sum(a: c_int, b: c_int = 1): int;
writeln(sum(10, 10));
writeln(sum(10));
A C struct can be used in Chapel by declaring it as an extern record
.
extern record rec {
var a: c_int;
}
extern proc giveRec(): rec; // returns an instance of the struct
You do not have to inform chapel about all the fields of a record, only the ones that you wish to directly manipulate using Chapel code.
For example, a record with no declared fields is possible even though the actual C struct might have a nonzero number of fields.
extern record notReallyEmpty{
}
This means that the type is just reduced to being able to be passed around to other functions and without the ability to directly manipulate it.
As of now the struct must be defined completely in the included header file
and must also be typdef’d.
In order to include a struct which is not typedef’d or if you want to import it
under another name simply state its external name after the extern
keyword:
extern "struct person" record person{
var name: c_ptrConst(c_char);
var age: c_int;
}
extern proc
s can also be renamed in a similar fashion.
Since most functions dealing with structs often return pointers, you can use the ref intent for function arguments when their C counterparts are dealing with pointers.
require "fact.c", "fact.h";
extern record data{
var x: c_int;
}
extern proc getNewData() ref : data;
ref d :data;
d = getNewData();
extern proc fact_d(ref x: data) ref : data;
ref f : data;
f = fact_d(d);
writeln(d);
writeln(f);
extern proc cFree(ref x);
cFree(d);
cFree(f);
If you do not care about the type for a certain variable or argument, you can
use the opaque
keyword to indicate to the compiler that you do not know about
the type.
Such a variable will not be much use except for the ability to pass it to different routines
which accept the same underlying type. (Be careful here as it may lead to unmatched types.)
extern proc getDataStructPtr() ref : opaque;
ref structPtr: opaque = getDataStructPtr();
cFree(structPtr);
For the ability to use C code in Chapel without an external C file, you can also use extern blocks which allow you to put C code directly into Chapel files. To avoid cluttering your namespace you can also put these inside a module.
Warning
As of now, chapel must be used with LLVM support to use the extern block syntax. See LLVM Support on how to build chapel with LLVM support.
module CDemo {
extern {
#include <math.h>
static double square(double num){
return pow(num,2);
}
}
}
writeln(CDemo.square(3));
} // interopWithC