Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a C function from Julia and passing a 2D array as a pointer of pointers as argument

Tags:

c

julia

The background

I'm trying to use the ccall Julia function to use code written in C. I know how to pass an array as an argument to a function that expects int *arg. For example, trying to use this C function

void sum_one(int *arr, int len)
{
  for (int i=0; i<len; i++){
    arr[i]++;
  }
}

this Julia code works

x = collect(Cint, 1:5)
ccall((:sum_one, "/path/to/mylib.so"), Void, (Ptr{Cint}, Cint), x, 5)

The problem

It doesn't seem to be so straight forward with C functions that expect a pointer to a pointer (int **arg) to be used as a 2-dimensional matrix. Say this one

void fill_matrix(int **arr, int row, int col)
{
  for (int i=0; i<row; i++){
    for (int j=0; j<col; j++){
      arr[i][j] = arr[i][j] + i + j*10;
    }
  }
}

Here, I needed to create an Array of Arrays so that the C code would accept it:

xx = [zeros(Cint, 5) for i in 1:6]
ccall((:fill_matrix, "/path/to/mylib.so"),
       Void, (Ptr{Ptr{Cint}}, Cint, Cint), xx, 6,5)

But this structure structure is not very convenient from the Julia side.

The question(s)

  • Is there any other way to pass a 2-dimensional matrix to a C function that expects an argument of the type int **arg?
  • If not, how can you transform an already existing 2-dimensional array of Julia to the array of arrays structure of C?
  • and the other way around?
like image 791
mgab Avatar asked Oct 07 '15 22:10

mgab


1 Answers

I will try to answer you questions one by one:

Is there any other way to pass a 2-dimensional matrix to a C function that expects an argument of the type int **arg?

Yes. You have to add a method to julia's cconvert function so that it does the conversion from Matrix{Cint} to Ptr{Ptr{Cint}}. So you define:

Base.cconvert(::Type{Ptr{Ptr{Cint}}},xx2::Matrix{Cint})=Ref{Ptr{Cint}}([Ref(xx2,i) for i=1:size(xx2,1):length(xx2)])

(see next question for explanation) and can afterwards directly pass your matrix to ccall:

xx2=zeros(Cint,5,6)
ccall((:fill_matrix, "mylib.so"),Void, (Ptr{Ptr{Cint}}, Cint, Cint), xx2, 6,5)

However, I would suggest to be very conservative in which cconvert methods you overwrite, because other julia code might expect the original behavior.

If not, how can you transform an already existing 2-dimensional array of Julia to the array of arrays structure of C?

The following should work: You generate an Array of pointers to every column of your matrix, so in julia-0.4:

xx2=zeros(Cint,5,6)
refAr=[Ref(xx2,i) for i=1:size(xx2,1):length(xx2)]
ccall((:fill_matrix, "mylib.so"),Void, (Ptr{Ptr{Cint}}, Cint, Cint), refAr, 6,5)

Now the matrix xx2 is filled by the C function. Note that in julia v0.3 you have to replace Ref(xx2,i) with pointer(xx2,i)

and the other way around?

I don't think this is generally possible. In order to construct a julia 2D array, the data must be in a contiguous block of memory. If you are REALLY confident this is the case you can do:

p=pointer(refAr)  # This is a Ptr{Ptr{Cint}} representing the int**
aa=pointer_to_array(p,6,false)
bb=pointer_to_array(aa[1],(5,6),false)

Which returns the original matrix. Here, the last argument to pointer_to_array determines, if Julia takes the ownership of the array and the data should be freed by Julia's gc.

like image 124
meggart Avatar answered Nov 14 '22 22:11

meggart