Skip to content

Tomcat-42/conan_cmake_rust_integration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Conan/CMake Rust integration example

This is a simple example project to show how to safely call C++ code from Rust using conan-rs and autocxx.

Project Structure

image

cpp

The c++ targets uses conan1 as the package manager and CMake. This is a very common setup in enterprise/legacy c++ codebases.

In this example, we have a static library, that answers the ultimate question to life, universe and everything, and a command line interface to it:

image

  • Static lib:
#include <deep_thought/answer.hpp>

namespace deep_thought {
  int answer() {
    return 42;
  }
} // namespace deep_thought
  • Cli:
#include <deep_thought/answer.hpp>

#include <chrono>
#include <iostream>
#include <thread>

auto main() -> int {
  auto task = []() {
    std::cout << "Thinking..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "The answer is " << deep_thought::answer() << std::endl;
  };

  std::thread thread(task);
  thread.join();
  return 0;
}

After building it, we have the following:

image

Rust

We can take advantage of the many application areas that Rust is more suited than C++ for extending the codebase. For example, Writing C++ desktop applications is a very painful process, so we can resource to the marvelous Rust Tauri Framework for developing a good user experience, while maintaining the existing c++ code:

image

  1. Here we use the conan-rs crate to abstract the deps instalation, building and packaging of our c++ artifacs. Also, we automatically generate Rust bindings "reading" the codebase header files using the wonderful google autocxx crate. Finally, we link the c++ static lib to our Tauri binary to have a working application.
  2. In the "Tauri backend"(in the ./src/app-rs/src/ext.rs file) we actually tell autocxx which c++ functions we want to generate code:
use autocxx::prelude::*;

include_cpp! {
    #include "deep_thought/answer.hpp"
    safety!(unsafe_ffi)
    generate!("deep_thought::answer")
}

pub fn answer() -> i32 {
    ffi::deep_thought::answer().into()
}

And in the main Tauri binary file we can enroll this function to be used as a Tauri command:

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use app_rs::ext;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn answer() -> String {
    ext::answer().to_string()
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![answer])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  1. Finally, we can invoke this file in the Tauri frontend (./src/app-rs/src/app.rsfile):
...
    let answer = {
        let answer_msg_rc = answer_msg_rc.clone();
        Callback::from(move |e: MouseEvent| {
            e.prevent_default();
            let answer_msg = answer_msg_rc.clone();

            spawn_local(async move {
                let new_msg = invoke("answer", JsValue::UNDEFINED)
                    .await
                    .as_string()
                    .unwrap();
                answer_msg.set(new_msg);
            });
        })
    };
...

With this, we have a fully fledged application, combining the strenghts of both Rust and C++:

image

See more

For more details, read the full article.

References

About

simple example project to show how to safely call C++ code from Rust

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published