Calling Julia From C/C++

1,967 views
Skip to first unread message

Stephen Chisholm

unread,
Dec 6, 2013, 2:55:56 PM12/6/13
to julia...@googlegroups.com
Using Julia's cfunction and the libjulia.so as well as following the code written to call Julia from Python, I've been able to come up with a working example of calling a function written in Julia from C/C++.  The example below gives the C/C++ program access to Julia's svd(A) function.  I wanted to start this discussion to help anyone else attempting the same, as well I'm hoping to get some feedback on what I have so far (Disclaimer: I'm a C/C++ developer and am very new to Julia).  A few questions I'm still working out:

1. The best way to copy array data from a julia array to a pointer to an array allocated by C/C++ (I'm currently using unsafe_copy!)?
2. How to have a void return type on a function?
3. If it's possible/safe to pass a pointer to memory allocated by Julia back to C/C++

julia_example.cpp:
// build:
//   g++ -std=c++11 -g -L</path/to/libjulia-debug.so> julia_example.cpp
//       -ljulia-debug -o julia_example
// run:
//   JULIA_ENV_PATH=</path/to/julia/environment> \
//   LD_LIBRARY_PATH=</path/to/julia/lib>:$LD_LIBRARY_PATH \
//   julia_example </path/to/julia/source.jl>


#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <cerrno>
#include <cstdio>

#include <boost/spirit/include/karma.hpp>

// -----------------------------------------------------------------
// Provide external definitions of the functions we are using from
// the libjulia.so
struct jl_value_t;

extern "C" {
  void jl_init(char *julia_home_dir);
  void *jl_eval_string(char *str);
  void *jl_unbox_voidpointer(jl_value_t *v);
}

// ------------------------------------------------------------------
// Print a vector<double> as an (m x n) matrix assuming it is stored
// in the vector in column major order.
void printMatrix(std::vector<double> &A, size_t m, size_t n)
  for (int i = 0; i < m; ++i)
    for (int j = 0; j < n; ++j)
      if (j == n - 1)
        std::printf("% .5f\n", A[i + (m * j)]);
      else
        std::printf("% .5f  ", A[i + (m * j)]);
}

// ------------------------------------------------------------------
// Read the entire contents of a file into a character vector.
void fileToString(char *filename, std::vector<char> &buf)
  std::streampos fileLength;
  std::ifstream file(filename);

  if (not file) {
    std::cerr << "Error opening file: "
              << std::strerror(errno) << std::endl;
    exit (1);
  }

  file.seekg(0, std::ios::end);
  fileLength = file.tellg();
  file.seekg(0, std::ios::beg);

  buf.resize(fileLength);
  file.read(&buf[0], fileLength);
}

int main(int argc, char **argv)
  namespace karma = boost::spirit::karma;

  void *ans;
  void *svdPtr;
  char *juliaEnvPath;
  std::vector<char> juliaCodeBuf;

  // ----------------------------------------------------------------
  // get the julia enviroment path from env variables

  juliaEnvPath = getenv("JULIA_ENV_PATH");

  if (NULL == juliaEnvPath) {
    std::cerr << "JULIA_ENV_PATH not found in Env Vars" << std::endl;
    exit (1);
  }

  // ----------------------------------------------------------------
  // read in Julia source from file passed as arg 1
  fileToString(argv[1], juliaCodeBuf);

  // ----------------------------------------------------------------
  // instanciate the Julia environment and evaluate source

  jl_init(juliaEnvPath);
  ans = jl_eval_string(&juliaCodeBuf[0]);

  if (NULL == ans) {
    std::cerr << "Error parsing julia source file" << std::endl;
    exit (1);
  }

  // ----------------------------------------------------------------
  // unbox the pointer to the function and cast with appropriate sig
  // int svd(double *A, size_t *m, size_t *n,
  //         double *U, double *S, double *V);

  svdPtr = jl_unbox_voidpointer((jl_value_t*)ans);
  auto svd = (int (*)(double*, size_t*, size_t*,
                      double*, double*, double*)) svdPtr;

  // ----------------------------------------------------------------
  // test that svd is working.

  size_t m = 3, n = 2;
  std::vector<double> A = {1.0, 1.0, sqrt(3.0), -1.0, -1.0, 0.0};
  std::vector<double> U(m*n);
  std::vector<double> S(1*n);
  std::vector<double> V(n*n);

  svd(&A[0], &m, &n, &U[0], &S[0], &V[0]);

  std::cout << "A:" << std::endl;
  printMatrix(A, m, n);
  std::cout << "U:" << std::endl;
  printMatrix(U, n, n);
  std::cout << "S:" << std::endl;
  printMatrix(S, 1, n);
  std::cout << "V:" << std::endl;
  printMatrix(V, n, n);

  return (0);
}

svd.jl:
# A wrapper to provide an interface to the SVD function to C/C++
function svd_c(A_::Ptr{Cdouble}, m_::Ptr{Csize_t}, n_::Ptr{Csize_t},
               U_::Ptr{Cdouble}, S_::Ptr{Cdouble}, V_::Ptr{Cdouble})

  # Load the input data from C
  m::Int64 = unsafe_load(m_)
  n::Int64 = unsafe_load(n_)
  A::Matrix{Float64} = pointer_to_array(A_, (m, n))

  # Get the pointers to the output variables
  U::Matrix{Float64} = pointer_to_array(U_, (m, n))
  S::Vector{Float64} = pointer_to_array(S_, (n,))
  V::Matrix{Float64} = pointer_to_array(V_, (n, n))
  
  # Call SVD
  (u, s, v) = svd(A)

  # This is not ideal, since it would be better if we could pass U, S, and V
  # directly to svd(...) to avoid this copying.
  
  unsafe_copy!(pointer(U), pointer(u), m*n)
  unsafe_copy!(pointer(S), pointer(s), n)
  unsafe_copy!(pointer(V), pointer(v), n*n)

  # I haven't figured out how I can have a void return type, other than if the
  # last line of the file closes a for loop... So return an 'int'
  convert(Cint, 0)
end

cfunction(svd_c, Cint, (Ptr{Cdouble}, Ptr{Csize_t}, Ptr{Csize_t}, 
                        Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}))

Tim Holy

unread,
Dec 6, 2013, 3:38:05 PM12/6/13
to julia...@googlegroups.com
Hi Stephen,

Tobias Knopp has written up some nice documentation that might help:
http://docs.julialang.org/en/latest/manual/embedding/

You didn't overlook anything; it was merged just moments ago. Nice job on
figuring out so much on your own!

Best,
--Tim

Tobias Knopp

unread,
Dec 6, 2013, 7:03:59 PM12/6/13
to julia...@googlegroups.com
Good Timing :-)

I have to add that the documentation I have written is based on a pull request that introduces some convenience functions and exports several symbols from libjulia (see https://github.com/JuliaLang/julia/pull/4997)

In general, there are different ways to bridge Julia and C++. You have taking an approach were you write a wrapper function in Julia around the Julia svd function and call this from C++. In the documentation I have written, the Julia function is directly called from C++ and the wrapping/type conversion happens in C++.

It might be a matter of taste, which approach to prefer and I have not played around enough to tell the pros and cons.

1. There is not neccessary a need to copy data. You can create in C++ a Julia array from a C pointer using the jl_ptr_to_array functions.
3. Yes. See the section about memory management in the embedding docu.

Stephen Chisholm

unread,
Dec 9, 2013, 2:42:04 PM12/9/13
to julia...@googlegroups.com
Thanks for the feedback, this is very helpful.  I'll do some testing around both approaches and let you know if I find anything interesting.

Tobias Knopp

unread,
Dec 9, 2013, 6:43:24 PM12/9/13
to julia...@googlegroups.com
Note that the embedding docu is now in Julia master and Jeff has revised some parts.

Stephen Chisholm

unread,
Dec 10, 2013, 1:38:05 PM12/10/13
to julia...@googlegroups.com
Following the instructions on the 'Embedding Julia' page I would assume the function declarations for jl_init, jl_eval_string, jl_call1, etc. could be found in julia.h?  Have all of the embedding changes not merged into master yet?

I also noticed that https://github.com/JuliaLang/julia/tree/master/examples does not include the file 'embedding.c'. Is this not the examples directory mentioned in the 'Embedding Julia' docs?

Cheers, Steve

Ivar Nesje

unread,
Dec 10, 2013, 1:41:33 PM12/10/13
to julia...@googlegroups.com
You guess correctly https://github.com/JuliaLang/julia/pull/4997 has not been merged yet. Code changes tend to not get merged as fast as documentation updates.

Ivar
Reply all
Reply to author
Forward
0 new messages