8
votes

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?
2

2 Answers

3
votes

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.

0
votes

I'm not into Julia, but C definitely allows you to pass around multidimensional arrays:

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

//and the call of the function above:
int width = 7, height = 5, matrix[height][width];
fill_matrix(height, width, matrix);

The point is, that there is no pointer array involved, the expression arr[i][j] will be evaluated as arr[i*col + j] by virtue of the array pointer type used to declare arr. The data is simply contiguous in memory.

Now, I don't know whether Julia allows you to interface with a C function that takes an array pointer argument like this, but you may try to find out. It might also be necessary to swap the array indices, that depends on whether Julia stores its matrices in column-first or row-first order. In either case, it should be easy to find out by trying.