Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,11 @@ if(BUILD_RUBY_BINDINGS AND BUILD_PYTHON_BINDINGS)
COMMAND ${Ruby_EXECUTABLE} test_pycall_workaround.rb
)

add_test(
NAME test_pycall_use_cpp_to_convert_pointers
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/ruby
COMMAND ${Ruby_EXECUTABLE} test_pycall_use_cpp_to_convert_pointers.rb
)


endif()
20 changes: 20 additions & 0 deletions Person.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <string>
#include <string_view>
#include <iostream>

namespace Test {

Expand All @@ -23,6 +24,25 @@ std::ostream& operator<<(std::ostream&, const Test::Person&);

// A free-standing function taking an object as argument
std::string personName(const Person& person);
inline void setName(Person &p, const std::string &newname) {
p.setName(newname);
}

// get an integral representation of the pointer that is this Person
inline long long toInt(Person &p) {
std::clog << "original pointer: " << &p << '\n';
const auto result = reinterpret_cast<long long>(&p);
std::clog << "toInt from C++ " << result << '\n';
return result;
}

// take the integer from toInt and reinterpret_cast it back into a Person *, then return that as a reference
inline Person &fromInt(long long i) {
auto *ptr = reinterpret_cast<Person *>(i);
std::clog << "Reclaimed pointer: " << ptr << '\n';
return *ptr;
}


} // Namespace Test

Expand Down
78 changes: 78 additions & 0 deletions ruby/test_pycall_use_cpp_to_convert_pointers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
ruby_lib_path = File.expand_path(File.join(__dir__, '../build/Products/ruby/mylib.so'))

if !File.exist?(ruby_lib_path)
puts "Error, this assumes you already built the project in <root>/build"
exit 1
end

require ruby_lib_path


puts "================== RUBY SIDE ======================"
ruby_p = Mylib::Person.new("John")
puts "ruby_p=#{ruby_p}"
puts "Mylib::personName(ruby_p)= #{Mylib::personName(ruby_p)}"


puts "\n\n"

puts "================== PYTHON SIDE ======================"

require 'pycall/import'
include PyCall::Import


python_lib_path = File.expand_path(File.join(__dir__, '../build/Products/python/mylib.py'))
if !File.exist?(python_lib_path)
puts "Error, this assumes you already built the Python project in <root>/build"
exit 1
end

sys = PyCall.import_module('sys')
sys.path.insert(0, "#{File.dirname(python_lib_path)}")

pyimport 'mylib', as: 'mylib_python'
python_p = mylib_python.Person.new("John")
puts "python_p=#{python_p}"
puts "mylib_python.personName(python_p)= #{mylib_python.personName(python_p)}"


puts "\n\n"

puts "================== PASSING RUBY TO PYTHON ======================"

# We're going to use C++ to marshal between a ruby object and a python object
# We take the already created ruby_p object and get back an int.
#
# What is the int (actually unsigned long long)? it's just the underlying pointer.
# Why an int? Because we need a type that Python, Ruby, and PyCall all recognize
#
# Next we take that int and say "please give me a Python Person object from this"
#
# What does fromInt do? It simply reinterpret_casts the integral value back into a Person &
#
# Note: it is critical that we call toInt from Ruby (because it's a Ruby `Person`)
# and critical that we call fromInt from Python (because we want a Python `Person`)
#
# **** Very Important: there are *all kinds of ways* this can go wrong. we need to provide a
# handy wrapper in ruby that gets a python object back and not directly tell the users
# of either library about these fromInt/toInt functions

python_p2 = mylib_python.fromInt(Mylib::toInt(ruby_p))

# Now ruby_p is still the same object
ruby_name = ruby_p.getName()
puts "ruby_name=#{ruby_name}"

# And python_p2 is a reference to the ruby_p object.
mylib_python.personName(python_p2)

# We verify this reference relationship asking Python to update the name
mylib_python.setName(python_p2, "Bob")

# Then verifying that it's the name we now expect
puts "Ruby name snould now be 'Bob' also: #{ruby_p.getName()}"