|
|
|
02-08-2015, 11:29 AM
|
#1
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Export function from C++ plugin to be used with Lua?
Maybe the full answer already is somewhere in the prerelease threads (or maybe the SWS plugin source code), but I wasn't able to find it so far...
So, how does one export a function from an extension plugin so that it can be used with Lua ReaScript? A full C(++) code example would be appreciated. I don't care about Python or Eel, but if the functions can be exported the same way from an extension plugin to work with those, it's a nice bonus.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
02-08-2015, 03:06 PM
|
#2
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
To export a function "funcname" to both Lua and EEL, you have to define your function with plugin_register("APIdef_funcname", etc) and a variable argument function with plugin_register("APIvararg_funcname", faddr_vararg) where "faddr_vararg" has the following prototype:
Code:
void* faddr_vararg(void** arglist, int numparms);
Let's consider this SWS function example :
Code:
int SNM_GetIntConfigVar(const char* _varName, int _errVal) {
if (int* pVar = (int*)(GetConfigVar(_varName)))
return *pVar;
return _errVal;
}
In SWS, there's a php script that will parse a function definition table and will automatically generate a variable argument wrapper func like:
Code:
static void* __vararg_SNM_GetIntConfigVar(void** arglist, int numparms) {
return (void*)(INT_PTR)SNM_GetIntConfigVar((const char*)arglist[0], (int)(INT_PTR)arglist[1]);
}
^ in your case (w/o python export and w/o export to other C++ plugins), you can probably directly write funcs you want to export in a "vararg fashion".
To export the function SNM_GetIntConfigVar:
Code:
plugin_register("APIdef_SNM_GetIntConfigVar", (void*)"int\0const char*,int\0varname,errvalue\0[S&M] Returns an integer preference (look in project prefs first, then in general prefs). Returns errvalue if failed (e.g. varname not found).");
plugin_register("APIvararg_SNM_GetIntConfigVar", (void*)__vararg_SNM_GetIntConfigVar);
BTW, here are the supported return/parameter types ATM:
Code:
// At the moment (REAPER v5pre6) the supported parameter types are:
// - int, int*, bool, bool*, double, double*, char*, const char*
// - AnyStructOrClass* (handled as an opaque pointer)
// At the moment (REAPER v5pre6) the supported return types are:
// - int, bool, double, const char*
// - AnyStructOrClass* (handled as an opaque pointer)
|
|
|
02-08-2015, 03:11 PM
|
#3
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Thanks for the infos!
Is there any chance full range 64 bit ints could be supported at some point? (The hack to use doubles to represent large ints up to a limit isn't entirely convenient for the thing I am just working on...I wrote a function in C++ to get a 64 bit int hash out of a Reaper object which I'd like to use from ReaScript.)
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-13-2015, 06:48 AM
|
#4
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
bump?
Xen should get what Xen wants. Always.
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
Last edited by Argitoth; 12-13-2015 at 06:57 AM.
|
|
|
12-13-2015, 07:39 AM
|
#5
|
Human being with feelings
Join Date: Aug 2013
Posts: 339
|
Quote:
Originally Posted by Xenakios
Is there any chance full range 64 bit ints could be supported at some point? (The hack to use doubles to represent large ints up to a limit isn't entirely convenient for the thing I am just working on...I wrote a function in C++ to get a 64 bit int hash out of a Reaper object which I'd like to use from ReaScript.)
|
Doesn't Lua treat all numbers as doubles just like EEL does?
|
|
|
12-13-2015, 08:25 AM
|
#6
|
Human being with feelings
Join Date: Jun 2009
Location: South, UK
Posts: 14,218
|
Quote:
Originally Posted by Argitoth
bump?
Xen should get what Xen wants. Always.
|
indeed what was that spock saying again..
"the needs of the one, benefit the many" lol
__________________
subproject FRs click here
note: don't search for my pseudonym on the web. The "musicbynumbers" you find is not me or the name I use for my own music.
|
|
|
12-14-2015, 06:41 AM
|
#7
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
wait a minute, can you not return an array from reascript c++ exported function?
Couldn't you point to a memory location with a pointer parameter and also output a length?
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
|
|
|
12-14-2015, 06:41 AM
|
#8
|
Human being with feelings
Join Date: Aug 2013
Posts: 339
|
Well, in case people did not get what I meant (I assume lack of replies is what it means...).
How would you store a 64-bit int in a lua number if Lua uses doubles? A double cannot store an arbitrary 64-bit integer. Same with EEL. That's probably why they don't exist as a type.
And yes you can return an array (aka a 'string' in both EEL and Lua), that's how Reaper APIs do it for GUIDs... but I guess Xen wants to directly manipulate the 64-bit integer as a number!
(i.e take a look at genGuid)
|
|
|
12-14-2015, 07:55 AM
|
#9
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by kenz
How would you store a 64-bit int in a lua number if Lua uses doubles? A double cannot store an arbitrary 64-bit integer.
|
Lua got proper support for 64 bit integers in version 5.3. (And Reaper uses a recent enough version.)
Talk about the topic by the Lua language inventor :
https://youtu.be/bjqNK1jA77M?t=81
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 12-14-2015 at 08:01 AM.
|
|
|
12-14-2015, 07:59 AM
|
#10
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Argitoth
wait a minute, can you not return an array from reascript c++ exported function?
Couldn't you point to a memory location with a pointer parameter and also output a length?
|
Yeah I suppose one could return a pointer (or I think "light user data") from C++ to be used in the Lua script but I fear that would be kind of inconvenient to deal with.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-14-2015, 11:48 AM
|
#11
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
Let's take this simple example:
int AddTwoNumbers(int n1, int n2) { return n1 + n2; }
becomes
static void* __vararg_AddTwoNumbers(void** arglist, int numparms)
{
return (void*)(INT_PTR)AddTwoNumbers((int)(INT_PTR)arglis t[0], (int)(INT_PTR)arglist[1]);
}
What's with all the casting, is there a C++11 or C++14 way of doing things to make this simpler?
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
|
|
|
12-14-2015, 01:23 PM
|
#12
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Argitoth
Let's take this simple example:
int AddTwoNumbers(int n1, int n2) { return n1 + n2; }
becomes
static void* __vararg_AddTwoNumbers(void** arglist, int numparms)
{
return (void*)(INT_PTR)AddTwoNumbers((int)(INT_PTR)arglis t[0], (int)(INT_PTR)arglist[1]);
}
What's with all the casting, is there a C++11 or C++14 way of doing things to make this simpler?
|
I think (but I am not sure hahah) that code relies on the fact that pointers will be integers on the architecture. So the casting mangles the void*'s into integers directly instead of them being pointers pointing to the integers.
I have to say I don't especially like what I am looking at with that code. I don't think there are many opportunities with that to make anything clearer or more encapsulated.
I still don't fully understand this way Reaper is handling the adding of the Lua functions to begin with. Things seemed at least a bit simpler when I've embedded Lua myself into my C++ code. Maybe the problem is that Cockos doesn't want to expose the Lua state object for the 3rd party extensions and that makes things more convoluted...?
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-14-2015, 02:36 PM
|
#13
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
I would always create a new functions for the export reascript function, so how about lambda? (may not be written correctly)
Code:
static void* AddTwoNumbers(void** args, int numparms)
{
return (void*)[&]()->INT_PTR{ return MyIntConverter(args[0]) + MyIntConverter(args[1]); }
}
soooomethiiiing like thaaat???? edit: the return syntax is probably wrong, but you get the jist?
edit: wait...
Code:
static void* AddTwoNumbers(void** args, int numparms)
{
return MyIntConverter(args[0]) + MyIntConverter(args[1]);
}
edit: more spelled out
Code:
static void* AddTwoNumbers(void** args, int numparms)
{
int n1 = MyIntConverter(args[0]);
int n2 = MyIntConverter(args[1]);
return ConvertToVoid(n1+n2);
}
edit: If you setup the right structs or template functions or whathaveyou:
Code:
static void* AddTwoNumbers(void** args, int numparms)
{
int n1 = In(args[0]); // In can convert double int char etc.
int n2 = In(args[1]);
return Out(n1+n2);
}
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
Last edited by Argitoth; 12-14-2015 at 02:56 PM.
|
|
|
12-14-2015, 02:48 PM
|
#14
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Well, whatever works, works...
Don't spend too much time figuring out the integer handling though. I think this casting between a void* and integer directly will only work for integers. Don't forget there are other data types to deal with too...
And where's the damn API support for 64 bit ints for Lua?!
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-14-2015, 03:38 PM
|
#15
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
There's a few things I don't quite understand. Here's what I have so far:
http://pastebin.com/ZKe24nMW NCPDW (not complete probably doesn't work)
Do you always need to return a pointer to an lvalue? (If I'm using the term correctly). I mean...
can you do:
return (void*)3.14;
or do you have to do:
double mydouble = 3.14;
return &mydouble;
int myint = 3;
return &myint;
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
Last edited by Argitoth; 12-14-2015 at 03:48 PM.
|
|
|
12-14-2015, 03:54 PM
|
#16
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
You shouldn't ever do something like :
Code:
double* explode_one_day(double x)
{
double foo = 3.141+x;
return &foo;
}
It returns a pointer to a local variable that will be gone by the time the function returns. (According to the language rules.) It may sometimes work, but will stop working correctly at the most inconvenient moment.
I think you should take a step back and review this stuff very carefully before proceeding further. Scripting language interoperability is a complete pain to deal with during the best of times and Cockos sure didn't make it any easier with their stuff.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-14-2015, 04:21 PM
|
#17
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
Could someone translate this to plain english for me plzzz?
Code:
static void* __vararg_BR_GetMouseCursorContext_Position(void** arglist, int numparms)
{
double* p =(double*)arglist[numparms-1];
double d = BR_GetMouseCursorContext_Position();
if (p) *p=d;
return p;
}
Here's my best attempt:
1. Create pointer to memory location for input variable from script, which is arglist[-1].
2. Create a double and send it the mouse position.
3. if arglist[0] is not nullptr, then assign our value
4. return the memory location.
why are we returning the memory location? if anything we should return null because we already assigned *p to a value... and is arglist[-1] the left side of the equals sign?
LUA:
my_value = BR_GetMouseCursorContext_Position() -- my_value is arglist[-1]?
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
|
|
|
12-14-2015, 04:55 PM
|
#18
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Argitoth
Could someone translate this to plain english for me plzzz?
Code:
static void* __vararg_BR_GetMouseCursorContext_Position(void** arglist, int numparms)
{
double* p =(double*)arglist[numparms-1];
double d = BR_GetMouseCursorContext_Position();
if (p) *p=d;
return p;
}
Here's my best attempt:
1. Create pointer to memory location for input variable from script, which is arglist[-1].
2. Create a double and send it the mouse position.
3. if arglist[0] is not nullptr, then assign our value
4. return the memory location.
why are we returning the memory location? if anything we should return null because we already assigned *p to a value... and is arglist[-1] the left side of the equals sign?
LUA:
my_value = BR_GetMouseCursorContext_Position() -- my_value is arglist[-1]?
|
I don't know how exactly this works but my guess is that the return value memory location is at arglist[0] and numparms will be 1. Thus double* p=(double*)arglist[numparams-1] will point to arglist[0]. The function probably returns the address for reasons of consistency even though the value is already available at arglist[0]. The caller may or may not want to use the returned address. I doubt the buffer index ends up as -1. (Did you check with a debug printout or a breakpoint and the debugger...?)
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 12-14-2015 at 05:02 PM.
|
|
|
12-14-2015, 05:06 PM
|
#19
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
I by the way never even got around to doing any Lua compatible function exporting myself from an extension plugin because this all seems so messy and the 64 bit int support never happened either...
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
12-14-2015, 06:58 PM
|
#20
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
through testing it looks like so far the rules are:
you can only return integers (or memory location to a class/object which is technically an integer!):
Code:
return (int)(INT_PTR)equation;
If you want to actually return a double you have to use args[size-1], so the last argument is the one on the left of the equals sign. You HAVE to return the memory location and you have to assign the value to the memory location.
Code:
double* my_value = arg[sz-1];
*my_value = equation;
return my_value;
So there's basically two modes you have to remember, int or double. You CANNOT handle ints like doubles and you cannot handle doubles like ints. I kept trying different ways... I wish we could just handle everything like doubles instead of having to remember two ways of doing things.
Here's my finished code for function export, nice and clean.
PHP Code:
struct In { void* v;
In(void* const& v) : v(v) {}
operator double() { return *(double*)v; } operator double*() { return (double*)v; }
operator int() { return (int)(INT_PTR)v; } operator int*() { return (int*)(INT_PTR)&v; }
operator bool() { return *(bool*)v; } operator bool*() { return (bool*)v; }
operator char() { return *(char*)v; } operator char*() { return (char*)v; } operator const char*() { return (const char*)v; } };
void* Out(int a) { return (void*)(INT_PTR)a; } void* Out(bool a) { return (void*)(INT_PTR)a; } void* Out(const char* a) { return (void*)(INT_PTR)a; } void* Out(double a) { return (void*)(INT_PTR)a; }
/*DEFINE EXPORT FUNCTIONS HERE*/ static void* DoublePointer(void** arg, int arg_sz) {//return:double parameters:double,double double* n1 = In(arg[0]); double* n2 = In(arg[1]); double* n3 = In(arg[arg_sz-1]);
*n3 = *n1 + *n2;
return n3; }
static void* IntPointer(void** arg, int arg_sz) {//return:int parameters:int,int int* n1 = In(arg[0]); int* n2 = In(arg[1]);
return Out(*n1+*n2); }
static void* DoublePointerAsInt(void** arg, int arg_sz) {//return:int parameters:double,double double* n1 = In(arg[0]); double* n2 = In(arg[1]);
return Out(*n1 + *n2); }
static void* CastDoubleToInt(void** arg, int arg_sz) {//return:int parameters:double,double int n1 = (double)In(arg[0]); int n2 = (double)In(arg[1]);
return Out(n1+n2); }
static void* CastIntToDouble(void** arg, int arg_sz) {//return:double parameters:int,int double n1 = (int)In(arg[0]); double n2 = (int)In(arg[1]); double* n3 = In(arg[2]);
*n3 = n1 + n2;
return n3; }
// Add functions to array APIdef g_apidefs[] = { { APIFUNC(DoublePointer), "double", "double,double", "n1,n2", "Add two numbers and return the value", }, { APIFUNC(IntPointer), "int", "int,int", "n1,n2", "Add two numbers and return the value", }, { APIFUNC(DoublePointerAsInt), "int", "double,double", "n1,n2", "Add two numbers and return the value", }, { APIFUNC(CastDoubleToInt), "int", "double,double", "n1,n2", "Add two numbers and return the value", }, { APIFUNC(CastIntToDouble), "double", "int,int", "n1,n2", "Add two numbers and return the value", }, { 0, } // denote end of table };
/* todo: create macro so you can define functions like this: APIFUNC(EH_AwesomeFunc, "double (double n1, double n2)", "This is the help text for my awesome function") the api-registering functions will be replaced with string stuff to parse correctly. */
edit: I haven't yet experimented with pointers as parameters.
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
Last edited by Argitoth; 12-15-2015 at 04:06 PM.
|
|
|
12-15-2015, 11:52 AM
|
#21
|
Human being with feelings
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
|
Alright, now that I've basically learned how to export functions, how do I prevent python from having an entry in the reascript documentation?
__________________
Soundemote - Home of the chaosfly and pretty oscilloscope.
MyReaperPlugin - Easy-to-use cross-platform C++ REAPER extension template
|
|
|
12-15-2015, 12:20 PM
|
#22
|
Human being with feelings
Join Date: Mar 2007
Posts: 21,551
|
Quote:
Originally Posted by Argitoth
Here's my finished code for function export, nice and clean.
|
Well... now you're just showing off.
|
|
|
12-17-2015, 03:28 PM
|
#23
|
Human being with feelings
Join Date: Aug 2013
Posts: 339
|
Quote:
Originally Posted by Argitoth
If you want to actually return a double you have to use args[size-1], so the last argument is the one on the left of the equals sign. You HAVE to return the memory location and you have to assign the value to the memory location.
|
This works for 'returning' other parameters too right? (i.e modifying them by 'reference' in EEL or returning multiple in Lua).
It's basically returning things by pointers, which is very common in C and other things that need to be universal/standard (there are no references in C, and references are not implicitly defined at machine level, compiler can optimize etc). I don't see what's so complicated about it, other than the return value being the last argument that is.
Quote:
Originally Posted by Argitoth
So there's basically two modes you have to remember, int or double. You CANNOT handle ints like doubles and you cannot handle doubles like ints. I kept trying different ways... I wish we could just handle everything like doubles instead of having to remember two ways of doing things.
|
Because the processor uses different registers for ints and doubles. (in 32-bit probably the x87 FPU registers, in 64-bit the SSE2 registers unless devs override the compiler settings for floats). The generic code that calls your exported function most likely uses the integer registers.
|
|
|
08-28-2018, 12:15 PM
|
#24
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
Quote:
Originally Posted by Jeffos
To export a function "funcname" to both Lua and EEL, you have to define your function with plugin_register("APIdef_funcname", etc) and a variable argument function with plugin_register("APIvararg_funcname", faddr_vararg)
|
Does this mean that non-variadic functions (such as SNM_GetIntConfigVar in the example above) do NOT need to use the complicated APIvararg wrappers?
(It seems that SWS creates __vararg_functionName wrappers in reascript_vararg.h for all API functions, even when they are not variadic. I hope this isn't really necessary.)
P.S. I'm moving this old thread from the pre-release forum to the developer forum.
Last edited by juliansader; 08-28-2018 at 02:12 PM.
|
|
|
08-28-2018, 05:37 PM
|
#25
|
Human being with feelings
Join Date: May 2015
Location: Québec, Canada
Posts: 4,967
|
Quote:
Originally Posted by juliansader
Does this mean that non-variadic functions (such as SNM_GetIntConfigVar in the example above) do NOT need to use the complicated APIvararg wrappers?
|
The vararg functions are the only ones that are invoked from ReaScript.
Last edited by cfillion; 08-29-2018 at 12:43 AM.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 06:19 PM.
|