Closed GavinRay97 closed 3 years ago
Haha, out of all functions in the reaper-medium
crate you picked exactly the one which is far from being polished and ready ;) I thought once I'm going to need it and then didn't, so I stopped working on it.
However, in your case the problem is another one: ReaperSession
needs to stick around. It uses RAII pattern to automatically clean up everything (including your plugin_register
registrations) when it goes out of scope. And in your code it does go out of scope as soon as your plugin_main
function ends. You need some static to store it in.
The easiest way to do this is to (you didn't hear that from me, okay? ;)) to use the high-level API to bootstrap your plug-in. Just a minimum of it and after that only access reaper-medium
.
#[reaper_extension_plugin(
name = "reaper-rs test extension plug-in",
support_email_address = "info@helgoboss.org"
)]
fn main() -> Result<(), Box<dyn Error>> {
let reaper = Reaper::get();
reaper.medium_session().plugin_register_add_api_and_def(...);
reaper.medium_reaper().blablabla();
Ok(())
}
test/test-extension-plugin
is a working example.
You are a gem of a human, thank you once again Benjamin ❤️
:) Just for understanding: In the medium-level API, Reaper
contains all the stateless functions and functions pointers. This can be freely cloned and is basically the equivalent of the raw C++ API just in a bit more idiomatic Rust. ReaperSession
is an addition that releases you from the burden to find a place in memory for all of the things that you want to register (because REAPER itself won't keep these things for you). That's why it needs to stick around.
Bingo! Thank you again!
Now to figure out whether this ImGui Rust <-> ReaScript <-> ImGui C++ thing will even work 😅
If I could toss ONE last question out there, feel free to answer or not, but this mut* mut* c_void
thing:
unsafe extern "C" fn imgui_rs_test_vararg(
_arglist: *mut *mut c_void,
_numparms: c_int,
) -> *mut c_void {
}
@cfillion was kind enough to divulge that the arglist stores:
Plus apparently another param with memory addresses it'd like results written to.
How do I go about translating an unknown _"mutable-pointer-to-a-mutable-pointer-to-a c_void, that returns a mutable pointer to a cvoid" into everyday Rust 😅?
I would imagine I could usually just write a Struct and write a serializer/deserializer but with this vararg stuff I don't think it can work that way?
Why not use https://github.com/imgui-rs/imgui-rs?
Ah, sorry. You already use that. Mmh, you would need to use structs that have the same layout as what's expected in the arg list. How these structs need to look exactly, I don't know. But basically the same way they would look in C?
It's a little bit complicated -- basically I am trying to see what the experience is like to integrate third-party widgets for cfillion's new "ReaImGui" library which is ImGui for Reaper + ReaScript bindings:
https://github.com/cfillion/reaimgui
However, to manage the REAPER lifecycle and stuff, he has some custom context which wraps the native ImGuiContext*
:
https://github.com/cfillion/reaimgui/blob/e490ca4628b2bbbf3c97b5b7f6ee79e974036958/src/context.cpp
I have some prebuilt widgets in C++ that I ported to it through mostly copy paste (spend the last 2 days learning C++ to get through this haha).
So it's probably pointless -- even Christian has said it's better to just ReaImGui's library than try to integrate prebuilt stuff.
But it sounded like it could be fun and a learning experience.
Also I think exposing ReaScript functions from Rust could be really useful in general, so I was glad to have learned more about this.
This is my current draft idea of what I think I am supposed to do 🤔
extern "C" fn imgui_rs_test(ctx: *mut c_void) {
let ImGuiTextPtr = Reaper::get()
.medium_reaper()
.plugin_context()
.get_func("ImGui_Text");
assert!(!ImGuiTextPtr.is_null());
let ImGuiText: Option<extern "C" fn(ctx: *mut c_void)> =
unsafe { std::mem::transmute(ImGuiTextPtr) };
let ImGuiText = ImGuiText.ok_or("Couldn't restore function from ReaScript API name and pointer. Make sure it exists and has been loaded.");
match ImGuiText {
Ok(method) => method(ctx),
_ => Reaper::get().medium_reaper().show_console_msg("No bueno"),
}
}
unsafe extern "C" fn imgui_rs_test_vararg(
_arglist: *mut *mut c_void,
_numparms: c_int,
) -> *mut c_void {
let args = std::slice::from_raw_parts(_arglist, _numparms.try_into().unwrap());
imgui_rs_test(args[0]);
null_mut()
}
Where args[0]
and ctx
here are being used because of:
-- Everything is passed "context", which is an instance of ReaImGui's custom wrapper ImGui_Context pointer
local ctx = reaper.ImGui_CreateContext("My script", 620, 500)
reaper.ImGui_Text(ctx, "Hello!")
Woo I, got it!!
It seemed the answer to turning *mut *mut c_void
array of arguments into something usable lied in:
std::slice::from_raw_parts(_arglist, _numparms.try_into().unwrap())
I think I will make another issue though -- I was about to give up because of the args syntax.
It must be EXACTLY the C
syntax, with commas and no spaces:
Tried many combinations and it never came out quite right. After reading reaper_functions.h
and the generated docs + your implementation closer, I took a guess on this.
Will try to submit a PR (if I am capable of writing it) to surface a better interface/docs for this =)
Also, I now am curious what can be done if I try to somehow expose and access the direct/underlying ImGuiContext
pointer from C++ DLL in Rust DLL. cfillion says it's a terrible idea.
With your help, this was possible -- how amazing! ❤️ ❤️
use std::{
borrow::Cow,
convert::TryInto,
os::raw::{c_int, c_void},
};
use std::{ffi::CStr, ptr::null_mut};
use std::error::Error;
use reaper_high::Reaper;
use reaper_macros::reaper_extension_plugin;
extern "C" fn imgui_rs_test(ctx: *mut c_void, text: *mut c_void) {
let ImGuiTextPtr = Reaper::get()
.medium_reaper()
.plugin_context()
.get_func("ImGui_Text");
assert!(!ImGuiTextPtr.is_null());
let ImGuiText: Option<extern "C" fn(ctx: *mut c_void, text: *mut c_void)> =
unsafe { std::mem::transmute(ImGuiTextPtr) };
let ImGuiText = ImGuiText.ok_or("Couldn't restore function from ReaScript API name and pointer. Make sure it exists and has been loaded.");
match ImGuiText {
Ok(method) => method(ctx, text),
_ => Reaper::get().medium_reaper().show_console_msg("No bueno"),
}
}
unsafe extern "C" fn imgui_rs_test_vararg(
_arglist: *mut *mut c_void,
_numparms: c_int,
) -> *mut c_void {
let args = std::slice::from_raw_parts(_arglist, _numparms.try_into().unwrap());
imgui_rs_test(args[0], args[1]);
null_mut()
}
#[reaper_extension_plugin(
name = "reaper-rs test extension plug-in",
support_email_address = "ray.gavin97@gmail.com"
)]
fn main() -> Result<(), Box<dyn Error>> {
let reaper = Reaper::get();
unsafe {
reaper
.medium_session()
.plugin_register_add_api_and_def(
"ImguiRS_Test",
imgui_rs_test as _,
imgui_rs_test_vararg,
"bool",
"ImGui_Context*,const char*",
"ctx,input",
"A test REAPER function for calling ImGui from ReaScript given an existing context",
)
.map_err(|err| {
reaper.show_console_msg(format!("Got error registering function: {}", err));
err
})?;
}
Ok(())
}
@GavinRay97 Btw, if you want to use imgui-rs in your Rust VST that also uses reaper-rs, you can use this:
https://github.com/BillyDM/imgui-baseview (You could also combine that with implot-rs
.)
Here is an example VST: https://github.com/DGriffin91/compressor-plugin
There are also round knobs for imgui: https://github.com/DGriffin91/imgui-rs-knobs
@Boscop Thank you! I will definitely use those for VST's =D
In this case, I was trying to do this:
ReaImGui
(C++ ReaScript API wrapper over ImGui to expose ImGui directly to REAPER scripts, with some wrapper for windowing context) - https://forum.cockos.com/showthread.php?p=2419456#post2419456ReaImGui
"ctx" ImGui_Context
is a custom wrapper context over *ImGuiContext
which contains the actual *ImGuiContext
at a private property called ImGui_Context.m_imgui
std::unique_ptr<ImGuiContext, void(*)(ImGuiContext*)> m_imgui;
m_imgui
class property from Rust, then I can use imgui-rs
because the type is the same as the type of ImGuiContext
in the FFI bindings.
Unfortunately I know basically NOTHING about either C++ or FFI or how this low level interop and binding/header stuff works so it turned out to be pretty difficult and not sure it's worth it =/
I've tried running the example to register a ReaScript function from the tests + internal docs, but I'm unable to get this to appear:
Is it meant to only be usable from within the Rust code by getting a direct pointer to it, or is it supposed to show up in the ReaScript docs and IDE signatures that appear, like this?