Old 02-08-2015, 11:29 AM   #1
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default 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.
Xenakios is offline   Reply With Quote
Old 02-08-2015, 03:06 PM   #2
Jeffos
Mortal
 
Jeffos's Avatar
 
Join Date: Dec 2008
Location: France
Posts: 1,969
Default

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)
Jeffos is offline   Reply With Quote
Old 02-08-2015, 03:11 PM   #3
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

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.
Xenakios is offline   Reply With Quote
Old 12-13-2015, 06:48 AM   #4
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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.
Argitoth is offline   Reply With Quote
Old 12-13-2015, 07:39 AM   #5
kenz
Human being with feelings
 
Join Date: Aug 2013
Posts: 339
Default

Quote:
Originally Posted by Xenakios View Post
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?
kenz is offline   Reply With Quote
Old 12-13-2015, 08:25 AM   #6
musicbynumbers
Human being with feelings
 
musicbynumbers's Avatar
 
Join Date: Jun 2009
Location: South, UK
Posts: 14,214
Default

Quote:
Originally Posted by Argitoth View Post
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.
musicbynumbers is offline   Reply With Quote
Old 12-14-2015, 06:41 AM   #7
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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
Argitoth is offline   Reply With Quote
Old 12-14-2015, 06:41 AM   #8
kenz
Human being with feelings
 
Join Date: Aug 2013
Posts: 339
Default

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)
kenz is offline   Reply With Quote
Old 12-14-2015, 07:55 AM   #9
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

Quote:
Originally Posted by kenz View Post
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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 07:59 AM   #10
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

Quote:
Originally Posted by Argitoth View Post
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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 11:48 AM   #11
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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
Argitoth is offline   Reply With Quote
Old 12-14-2015, 01:23 PM   #12
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

Quote:
Originally Posted by Argitoth View Post
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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 02:36 PM   #13
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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.
Argitoth is offline   Reply With Quote
Old 12-14-2015, 02:48 PM   #14
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 03:38 PM   #15
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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.
Argitoth is offline   Reply With Quote
Old 12-14-2015, 03:54 PM   #16
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 04:21 PM   #17
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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
Argitoth is offline   Reply With Quote
Old 12-14-2015, 04:55 PM   #18
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

Quote:
Originally Posted by Argitoth View Post
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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 05:06 PM   #19
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

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.
Xenakios is offline   Reply With Quote
Old 12-14-2015, 06:58 PM   #20
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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 {
    
voidv;

    
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; }
};

voidOut(int a) { return (void*)(INT_PTR)a; }
voidOut(bool a) { return (void*)(INT_PTR)a; }
voidOut(const chara) { return (void*)(INT_PTR)a; }
voidOut(double a) { return (void*)(INT_PTR)a; }

/*DEFINE EXPORT FUNCTIONS HERE*/
static voidDoublePointer(void** argint arg_sz) {//return:double parameters:double,double
    
doublen1 In(arg[0]);
    
doublen2 In(arg[1]);
    
doublen3 In(arg[arg_sz-1]);

    *
n3 = *n1 + *n2;

    return 
n3;
}

static 
voidIntPointer(void** argint arg_sz) {//return:int parameters:int,int
    
intn1 In(arg[0]);
    
intn2 In(arg[1]);

    return 
Out(*n1+*n2);
}

static 
voidDoublePointerAsInt(void** argint arg_sz) {//return:int parameters:double,double
    
doublen1 In(arg[0]);
    
doublen2 In(arg[1]);

    return 
Out(*n1 + *n2);
}

static 
voidCastDoubleToInt(void** argint arg_sz) {//return:int parameters:double,double
    
int n1 = (double)In(arg[0]);
    
int n2 = (double)In(arg[1]);

    return 
Out(n1+n2);
}

static 
voidCastIntToDouble(void** argint arg_sz) {//return:double parameters:int,int
    
double n1 = (int)In(arg[0]);
    
double n2 = (int)In(arg[1]);
    
doublen3 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.
Argitoth is offline   Reply With Quote
Old 12-15-2015, 11:52 AM   #21
Argitoth
Human being with feelings
 
Argitoth's Avatar
 
Join Date: Feb 2008
Location: Mesa, AZ
Posts: 2,057
Default

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
Argitoth is offline   Reply With Quote
Old 12-15-2015, 12:20 PM   #22
Lawrence
Human being with feelings
 
Join Date: Mar 2007
Posts: 21,551
Default

Quote:
Originally Posted by Argitoth View Post
Here's my finished code for function export, nice and clean.
Well... now you're just showing off.
Lawrence is offline   Reply With Quote
Old 12-17-2015, 03:28 PM   #23
kenz
Human being with feelings
 
Join Date: Aug 2013
Posts: 339
Default

Quote:
Originally Posted by Argitoth View Post
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 View Post
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.
kenz is offline   Reply With Quote
Old 08-28-2018, 12:15 PM   #24
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

Quote:
Originally Posted by Jeffos View Post
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.
juliansader is offline   Reply With Quote
Old 08-28-2018, 05:37 PM   #25
cfillion
Human being with feelings
 
cfillion's Avatar
 
Join Date: May 2015
Location: Québec, Canada
Posts: 4,937
Default

Quote:
Originally Posted by juliansader View Post
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.
cfillion is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 07:26 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.