Pass (to Julia) a C struct which contains a pointer to another struct

Dear experts,
assume the module below:

module PointsModule

struct Point
	x::Cdouble
	y::Cdouble
	z::Cdouble
end

struct Points
	n_of_points::Cint
	all_points::Vector{Point}
end

function sum_points(my_points::Points)::Cdouble
	println("Received ", my_points.n_of_points)
	println("x[1] = ", my_points.all_points[1].x, " x[2] = ", my_points.all_points[2].x)
	return my_points.all_points[1].x + my_points.all_points[2].x
end

function show_point(my_point::Point)
	println("x = ", my_point.x)
	println("y = ", my_point.y)
	println("z = ", my_point.z)
end

if abspath(PROGRAM_FILE) == @__FILE__
	a_p = Point(1.0,2.0,3.0)
	b_p = Point(4.0,5.0,6.0)
	c_p = Point(7.0,8.0,9.0)

	show_point(a_p)

	my_points = Points(3,[a_p, b_p, c_p])

	res = sum_points(my_points)
	println("Res is: ", res)

end

end

I would like to call the sum_points from my C++ code.
Here is my C++ code:

#include <iostream>
#include <string>

#include <unistd.h>

#include <julia.h>

JULIA_DEFINE_FAST_TLS()

typedef struct
{
	double x;
	double y;
	double z;
}Point;

typedef struct 
{
	size_t nr_of_points;
	Point* all_points;
}Points;


void fill_struct(Points* my_points)
{
	const size_t nr_of_points(600*600);
	my_points->nr_of_points = nr_of_points;
	my_points->all_points = new Point[nr_of_points];

	for(size_t i=0; i<nr_of_points; i++)
	{
		my_points->all_points[i].x = 1.5;
		my_points->all_points[i].y = 2.7;
		my_points->all_points[i].z = 3.9;
	}
}

int main()
{
	jl_options.image_file = JULIAC_PROGRAM_LIBNAME;
    julia_init(JL_IMAGE_JULIA_HOME);

	Points my_points;

	fill_struct(&my_points);

	jl_value_t* mod = (jl_value_t*) jl_eval_string("PointsModule");

	jl_value_t** jl_point_ptr = (jl_value_t**) malloc (my_points.nr_of_points * sizeof( (jl_value_t*) jl_get_function((jl_module_t*)mod, "Point") ));

	for(size_t i=0; i<my_points.nr_of_points; i++)
	{
		jl_value_t* jl_x = NULL;
		jl_value_t* jl_y = NULL;
		jl_value_t* jl_z = NULL;
		JL_GC_PUSH3(jl_x, jl_y, jl_z);
		jl_x = jl_box_float64(my_points.all_points[i].x);
		jl_y = jl_box_float64(my_points.all_points[i].y);
		jl_z = jl_box_float64(my_points.all_points[i].z);

		jl_point_ptr[i] = jl_new_struct((jl_datatype_t*)jl_get_function((jl_module_t*)mod, "Point"),
		                                          jl_x,
		                                          jl_y,
		                                          jl_z);
	}


	jl_function_t* jl_show_point = jl_get_function((jl_module_t*)mod, "show_point");
	jl_call1(jl_show_point, jl_point_ptr[0]);
	jl_call1(jl_show_point, jl_point_ptr[1]);

	jl_value_t* jl_nr_of_points = NULL;
	jl_array_t* jl_point_array = NULL;
	JL_GC_PUSH2(jl_nr_of_points, jl_point_array);
	jl_nr_of_points = jl_box_int64(my_points.nr_of_points);
	jl_point_array = jl_ptr_to_array_1d(jl_apply_array_type((jl_value_t*)jl_get_function((jl_module_t*)mod, "Point"), 1),
		                                                               jl_point_ptr,
		                                                               my_points.nr_of_points,
		                                                               0);

	jl_value_t* jl_my_points = jl_new_struct((jl_datatype_t*)jl_get_function((jl_module_t*)mod, "Points"),
											 jl_nr_of_points,
											 jl_point_array);

	jl_function_t* jl_sum_points = jl_get_function((jl_module_t*)mod, "sum_points");
	jl_value_t* res = jl_call1(jl_sum_points, jl_my_points);

	std::cout << "Res is: "	<< jl_unbox_float64(res) << std::endl;
	JL_GC_POP();

	jl_atexit_hook(0);

return 0;
}

On the above example, I created the jl_point_ptr which is an array of jl_new_struct objects ( = jl_value_t* ).
Then, inside the for loop I am assigning each element of the jl_point_ptr to an allocation jl_new_struct object.
Once all the points are created, I am creating a “pointer to an array” as I would do if I had an array of primitive types.
Although the jl_point_array is created fine, when I pass it in the jl_sum_points function, all the elements are empty.

Could you help me please?

And this is exactly why I recommend against it in Passing a C struct into Julia .

Unless you are completely familar with all the layout, you should use the C types to pass data around, rather than trying to create julia arrays etc. Even if you do, it’s still less efficient to create those julia types in C. (and you can create the necessary C layout compatible julia types using “native” C or generic julia C api and it’s much better to use the native one.)

And again, if you have a pointer in your C struct, use a pointer in your julia struct as well.

Hi yuyichao,
thanks for swift answer!
Then, if I would change to:

struct Point
	x::Cdouble
	y::Cdouble
	z::Cdouble
end

struct Points
	n_of_points::Cint
	all_points::Ptr{Point}
end

I am not managing to create successfully the Ptr within Julia:

module PointsModule

struct Point
	x::Cdouble
	y::Cdouble
	z::Cdouble
end

struct Points
	n_of_points::Cint
	all_points::Ptr{Point}
end

function sum_points(my_points::Points)::Cdouble
	println("Received ", my_points.n_of_points, "; Check memory")
	println("x[1] = ", unsafe_load(my_points.all_points,1), " x[2] = ", unsafe_load(my_points.all_points,2).x)
	return 0.1
end

function show_point(my_point::Point)
	println("x = ", my_point.x)
	println("y = ", my_point.y)
	println("z = ", my_point.z)
end

if abspath(PROGRAM_FILE) == @__FILE__
	a_p = Point(1.0,2.0,3.0)
	b_p = Point(4.0,5.0,6.0)
	c_p = Point(7.0,8.0,9.0)

	my_point_array = [a_p, b_p, c_p]
       my_ptr = Ptr{Point}(pointer_from_objref(my_point_array))

	show_point(a_p)

	my_points = Points(3,my_ptr)

	res = sum_points(my_points)
	println("Res is: ", res)

end

end

As you see, in the function sum_points I am simply printing the data.
the printed data is:
x[1] = Main.PointsModule.Point(6.8985861740936e-310, 1.5e-323, 2.812953e-318) x[2] = 1.5e-323
which means that the pointer is not pointing to the proper version.
What am I doing wrong?

pointer

If you are creating the C pointer in jula, you also need to make sure the object remains valid, with GC.@preserve my_point_array over the duration the pointer is used (rest of the if). If it’s just a test it’s ok for now since it’s in global scope.

thanks!!!
It worked!

a bit later I will try the call from C :slight_smile:

Hi Yuyichao,
now the problem is when I call it from C.
Here is the code:

#include <iostream>
#include <string>
#include <vector>

#include <unistd.h>

#include <julia.h>

JULIA_DEFINE_FAST_TLS()

typedef struct
{
	double x;
	double y;
	double z;
}Point;

typedef struct 
{
	size_t nr_of_points;
	Point* all_points;
}Points;


void fill_struct(Points* my_points)
{
	const size_t nr_of_points(600*600);
	my_points->nr_of_points = nr_of_points;
	my_points->all_points = new Point[nr_of_points];

	for(size_t i=0; i<nr_of_points; i++)
	{
		my_points->all_points[i].x = 1.5;
		my_points->all_points[i].y = 2.7;
		my_points->all_points[i].z = 3.9;
	}
}

int main()
{
	jl_options.image_file = JULIAC_PROGRAM_LIBNAME;
    julia_init(JL_IMAGE_JULIA_HOME);

	Points my_points;

	fill_struct(&my_points);


	jl_value_t* mod = (jl_value_t*) jl_eval_string("PointsModule");

	jl_value_t** jl_point_ptr = (jl_value_t**) malloc (my_points.nr_of_points * sizeof( (jl_value_t*) jl_get_function((jl_module_t*)mod, "Point") ));

	for(size_t i=0; i<my_points.nr_of_points; i++)
	{
		// std::cout << "Entering for i=" << i << std::endl;
		jl_value_t* jl_x = NULL;
		jl_value_t* jl_y = NULL;
		jl_value_t* jl_z = NULL;
		JL_GC_PUSH3(jl_x, jl_y, jl_z);
		jl_x = jl_box_float64(my_points.all_points[i].x);
		jl_y = jl_box_float64(my_points.all_points[i].y);
		jl_z = jl_box_float64(my_points.all_points[i].z);

		jl_point_ptr[i] = jl_new_struct((jl_datatype_t*)jl_get_function((jl_module_t*)mod, "Point"),
		                                          jl_x,
		                                          jl_y,
		                                          jl_z);
	}


	jl_function_t* jl_show_point = jl_get_function((jl_module_t*)mod, "show_point");
	jl_call1(jl_show_point, jl_point_ptr[0]);
	jl_call1(jl_show_point, jl_point_ptr[1]);

	jl_value_t* jl_nr_of_points = NULL;
	jl_array_t* jl_point_array = NULL;
	JL_GC_PUSH1(jl_nr_of_points);
	jl_nr_of_points = jl_box_int64(my_points.nr_of_points);
	jl_point_array = jl_ptr_to_array_1d(jl_apply_array_type((jl_value_t*)jl_get_function((jl_module_t*)mod, "Point"), 1),
	 	                                                               jl_point_ptr,
	 	                                                               my_points.nr_of_points,
	 	                                                               0);

	jl_value_t* jl_my_points = jl_new_struct((jl_datatype_t*)jl_get_function((jl_module_t*)mod, "Points"),
											 jl_nr_of_points,
											 jl_point_array);

	jl_function_t* jl_sum_points = jl_get_function((jl_module_t*)mod, "sum_points");
	jl_value_t* res = jl_call1(jl_sum_points, jl_my_points);

	std::cout << "Res is: "	<< jl_unbox_float64(res) << std::endl;

	JL_GC_POP();

	jl_atexit_hook(0);

return 0;
}

The jl_my_points is created without problem, but when it calls the jl_sum_points I am getting the following error:

signal (11): Segmentation fault
in expression starting at none:0
unsafe_load at ./pointer.jl:105 [inlined]
sum_points at /home/eparadas/tests/Julia/memory_tests/PointsModule.jl:16
jfptr_sum_points_65770 at /home/eparadas/tests/Julia/memory_tests/libpoints_sys.so (unknown line)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2214 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2398
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1690 [inlined]
jl_call1 at /buildworker/worker/package_linux64/build/src/jlapi.c:226
main at ./call_points_feval (unknown line)
__libc_start_main at /lib64/libc.so.6 (unknown line)
_start at ./call_points_feval (unknown line)
Allocations: 2543 (Pool: 2532; Big: 11); GC: 0
Segmentation fault

As it points out, it is related to the unsafe_load.

How could it be solved?

You just need to store the pointer to the struct, you are again trying to store an julia array instead. Also, the array layout is wrong but that’s not the problem here…

If I understood correctly, you mean this:

jl_value_t* jl_my_points = jl_new_struct((jl_datatype_t*)jl_get_function((jl_module_t*)mod, "Points"),
											 jl_nr_of_points,
											 jl_point_ptr);

where the jl_point_ptr is defined as:

jl_value_t** jl_point_ptr = (jl_value_t**) malloc (my_points.nr_of_points * sizeof( (jl_value_t*) jl_get_function((jl_module_t*)mod, "Point") ));

Doing that, it fails to construct the struct, with the error:

signal (11): Segmentation fault
in expression starting at none:0
jl_assign_bits at /buildworker/worker/package_linux64/build/src/datatype.c:697 [inlined]
set_nth_field at /buildworker/worker/package_linux64/build/src/datatype.c:1075 [inlined]
jl_new_struct at /buildworker/worker/package_linux64/build/src/datatype.c:883
main at ./call_points_feval (unknown line)
__libc_start_main at /lib64/libc.so.6 (unknown line)
_start at ./call_points_feval (unknown line)
Allocations: 2544 (Pool: 2533; Big: 11); GC: 0
Segmentation fault

The jl_new_struct returns a jl_value_t*
Therefore, the jl_point_ptr should be jl_value_t**, right?

No.

  1. You already have your pointer, you don’t need to allocate a new one.
  2. You are using C sizeof on a pointer, that’s not going to give you the correct result.
  3. The type is not jl_value_t** and simply Point*.
  4. If you really want to use jl_new_struct, which I still don’t recommend, you should box the pointer to a julia object.

And again, you already have the C object and declared a compatible julia one, you just need to copy the content of the C object to a julia object and you’ll be done.

indeed

if so, what would be the julia object then?

It’s a julia Ptr{Point}, exactly as you declared in julia code.

1 Like

Thanks for all the help Yuyichao!
Much appreciated!