Hello,
I may be missing the obvious here, hopefully someone can point it out to me: when calling a custom julia function from C++ passing an array of doubles, the array values should be changed in place but is not.
For example, the Julia function definition
Base.@ccallable function square_array(x::Ptr{Cdouble}, N::Cintmax_t)::Cint
x = array_wrap(x, (N))
x .*= 2
0
end
The corresponding call in C++
#include <iostream>
#include <julia.h>
JULIA_DEFINE_FAST_TLS()
int main()
{
jl_init_with_image__threading(NULL, "/path/to/image.so");
{
jl_function_t* func = jl_get_function(jl_main_module, "square_array");
if(func) {
jl_value_t** args;
const int dataN = 2;
const int nargs = 2;
JL_GC_PUSHARGS(args, nargs);
jl_value_t* double_type_1d = jl_apply_array_type((jl_value_t*)jl_float64_type, 1);
jl_array_t* x = jl_alloc_array_1d(double_type_1d, dataN);
double* xData = (double*)jl_array_data(x);
xData[0] = 1.3;
xData[1] = 4.6;
for(int i = 0; i < dataN; ++i) {
std::cout << i << ": " << xData[i] << std::endl;
}
args[0] = (jl_value_t*)x;
args[1] = jl_box_int64(dataN);
std::cout << "starting test..." << std::endl;
jl_value_t* val = jl_call(func, args, nargs);
JL_GC_PUSH1(&val);
std::cout << "test finished: " << val << std::endl;
for(int i = 0; i < dataN; ++i) {
std::cout << i << ": " << xData[i] << std::endl;
}
// cleanup
JL_GC_POP();
} else {
std::cout << "Function square_array() not found!" << std::endl;
}
}
jl_atexit_hook(0);
}
However looking at a similar example in C (from C/eval_julia_personal_function_array_args/embed_example.c · master · Jamil_Projects / Embedded_Julia / Julia_embed · GitLab), the following will make the change in place.
#include <julia.h>
//JULIA_DEFINE_FAST_TLS() // only define this once, in an executable (not in a shared library) if you want fast code.
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("THIS CODE IS TO SQUARE ALL ELEMENTS IN AN ARRAY AND RETURN THE NEW ARRAY IN A JULIA MODULE");
/* required: setup the Julia context */
jl_init();
/* create a 1D array of length 100 */
double length = 5;
double *existingArray = (double*)malloc(sizeof(double)*length);
/* create a *thin wrapper* around our C array */
jl_value_t* array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 1);
jl_array_t *x = jl_ptr_to_array_1d(array_type, existingArray, length, 0);
JL_GC_PUSH1(&x);
/* fill in values */
double *xData = (double*)jl_array_data(x);
for(size_t i=0; i<jl_array_len(x); i++)
xData[i] = i;
printf("x = [");
for(size_t i=0; i<jl_array_len(x); i++)
printf("%i ", (int)xData[i]);
printf("]\n");
/* import `jamil_math` module from file jamil_math.jl */
jl_eval_string("Base.include(Main, \"jamil_math.jl\")");
/* load the module into the current stack*/
jl_eval_string("using .jamil_math");
/* convert the module into a C module*/
jl_module_t* jamil_math_module = (jl_module_t *)jl_eval_string("module_math");
/* get `square_array` function from module */
jl_function_t *square_array = jl_get_function(jamil_math_module, "square_array");
/* call the function */
jl_call1(square_array, (jl_value_t*)x);
/* print new values of x */
printf("new values after being passed into julia function");
printf("\nx = [");
for (int i = 0; i < length; i++)
printf("%.1f ", xData[i]);
printf("]\n");
JL_GC_POP();
/* exit */
jl_atexit_hook(0);
printf("\n\n\n");
return 0;
}
If I call the function from Python (similarly loading dynamically the .so/.dll), the values are changed in place as expected. Here it is in Python
from ctypes import c_long, c_void_p
import numpy as np
julia_lib = "/path/to/julia/lib.so"
custom_image = "/path/to/custom/lib.so"
internal_jl = ctypes.CDLL(julia_lib)
api = ctypes.CDLL(custom_image, ctypes.RTLD_GLOBAL)
internal_jl.jl_init_with_image__threading.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
internal_jl.jl_init_with_image__threading(None, bytes(custom_image, 'utf-8'))
x = 2 * np.ones(3)
api.square_array(c_void_p(x.ctypes.data), c_long(3))
print(x)
# => prints [4., 4., 4.]
So I can’t get my head around why the first c++ snippet code doesn’t change the value in place although it should, any idea?
thanks!