How to call C code from julia

This notice presents an example of utilisation of some code written in C language from julia.

More exactly, we consider a small project with the following structure:

Figure 1: Folder structure and project files

Figure 1: Folder structure and project files

In the C part of the project we have the file cc/nc.c

# include "nc.h"

void initmystr(mystr* s){
  (*s).a = 0;
  (*s).p[0]=0;
}

void initmyvec(int* p){
  p[0] = 77;
}

and the corresponding header (cc/include/nc.h):

#include <stdio.h>

// A C struct containing a pointer.
typedef struct mystr {
  int a;
  int* p;
} mystr;

void initmystr(mystr* s);

void initmyvec(int* p);

I build this code in a shared library using the following make file. If you have a look at the makefile, you will observe that I firstly build a static library (for fun) and then linked this library into a shared one.

CC=gcc
CFLAGS=-I./include -fPIC
DEPS = nc.h

all: libnc test
UNAME_S := $(shell uname -s)

%.o: %.c $(DEPS)
      $(CC) -c -o $@ $< $(CFLAGS)

libnc.a: nc.o
      ar rcs $@ $^

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
      LIBEXT=so
      SHARED_BEGIN=-Wl,-whole-archive
      SHARED_END=-Wl,-no-whole-archive
endif
ifeq ($(UNAME_S),Darwin)
      LIBEXT=dylib
      SHARED_BEGIN=-all_load
      SHARED_END=
endif

libnc: libnc.a
      $(CC) -shared $(SHARED_BEGIN) $^ $(SHARED_END) -o $@.$(LIBEXT)

test: libnc
      +$(MAKE) -C test

.PHONY: clean

clean:
      rm *.o *.$(LIBEXT) *.a test/testnc.o test/testnc

A first test is to execute the code in the library from C. This is the purpose of the file test/testnc.c:

#include "nc.h"
#include <stdio.h>

int main(int argc, char *argv[])
{
  int tmp[3];
  tmp[0] = 1;
  tmp[1] = 2;
  tmp[2] = 3;

  mystr s;
  s.a =3;
  s.p = tmp;

  printf("Call initmystr...\n");
  initmystr(&s);

  printf("Data in struct: %d %d %d %d\n", s.a, s.p[0], s.p[1], s.p[2]);
  printf("Data in tmp: %d %d %d\n", tmp[0], tmp[1], tmp[2]);

  printf("Call initmyvec...\n");
  initmyvec(tmp);

  printf("Data in tmp: %d %d %d\n", tmp[0], tmp[1], tmp[2]);
  printf("Data in struct: %d %d %d %d\n", s.a, s.p[0], s.p[1], s.p[2]);
  return 0;
}

After compiling and linking this file we get:

Call initmystr..
Data in struct: 0 0 2 3
Data in tmp: 0 2 3
Call initmyvec...
Data in tmp: 77 2 3
Data in struct: 0 77 2 3

Let us have a look to the julia code (file testloadnc.hjl) for doing the same thing:

using Libdl

push!(DL_LOAD_PATH, "./cc")

mutable struct Mystr
    a::Int32
    p::Ptr{Int32}
end

p = convert(Array{Int32, 1}, [1, 2, 3])
A = Mystr(1, pointer(p))

print("Call initmystr...\n")
ccall((:initmystr, :libnc), Cvoid, (Ref{Mystr},), A)

print("Data in A: ", A.a, "\n")
print("Data in p: ", p, "\n\n")

print("Call initmyvec...\n")
ccall((:initmyvec, :libnc), Cvoid, (Ref{Int32},), p)

print("Data in A: ", A.a, "\n")
print("Data in p: ", p, "\n")

and to the output produced by its execution:

Call initmystr...
Data in A: 0
Data in p: Int32[0, 2, 3]

Call initmyvec...
Data in A: 0
Data in p: Int32[77, 2, 3]

Everything is working as expected. The only tricky part was the way to pass a pointer inside the structure.

 
comments powered by Disqus