|
|
|
08-27-2018, 07:06 AM
|
#1
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
REQ: Example of minimal extension for adding Reascript API
Several years ago, Xenakios provided code for a minimal extension that adds actions to REAPER.
Is there any equivalent example for an extension to add API functions? Or any documentation about adding API functions?
(I have tried to whittle SWS down to a minimal extension that only adds some API functions, but so far without success.)
|
|
|
08-27-2018, 10:53 AM
|
#3
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
Thanks for the link!
I am relieved to see that the code doesn't seem overly complicated. However, there are still many pieces of the puzzle missing:
* How do I get REAPER to call registerAPI() and removeAPI() when initializing and unloading the extension?
* What should my headers include for cross-platform builds? I have checked SWS's headers in stdafx.h and other files, but IIRC Justin mentioned that the SWS headers are actually outdated, and that extensions should use "swell_provided_by_app".
* How do extensions import functions such as GetExtState from REAPER? Xenakios's example and SWS both use the IMPAPI macro, but they place it in a weird extern "C" function that involve lots of other code that I don't understand and that don't seem relevant to exporting API functions.
(Justin and Schwa should visit the Developer Forum more often...)
|
|
|
08-27-2018, 11:51 AM
|
#4
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by juliansader
they place it in a weird extern "C" function that involve lots of other code that I don't understand and that don't seem relevant to exporting API functions.
|
If you mean the plugin entrypoint function, it's absolutely crucial for extensions to have. Reaper calls that function when it wants to load and unload the extension plugin. You should do much of your initialization and uninitialization work there. (Including things like registering new API functions as well as importing existing ones.)
Here's a very minimal implementation of the entrypoint function, that will still do something :
Code:
#define IMPAPI(x) if (!errcnt && !((*(void **)&(x)) = (void *)rec->GetFunc(#x))) errcnt++;
extern "C"
{
REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
if (rec != nullptr)
{
int errcnt = 0;
IMPAPI(ShowConsoleMsg);
ShowConsoleMsg("Loading extension plugin\n");
return 1; // plugin succesfully loaded, return 0 here if could not initialize properly
}
else
{
// plugin is being unloaded when Reaper quits
ShowConsoleMsg("Unloading extension plugin\n"); // you will never get to see this message, though...
return 0;
}
}
}
Note : New extension plugin code should not really use the horrible IMPAPI macro to import API functions. There's a new way to load all the available Reaper provided API functions in one go.
Extension plugins can only export API functions dynamically via code. (Via the rec->Register calls.) Which brings in the "interesting" problem, what happens when several plugins depend on functions between each other. There is no easy and clean solution for that. AFAIK Reaper loads the plugin DLLs in alphabetical file name order.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 08-27-2018 at 12:08 PM.
|
|
|
08-27-2018, 06:16 PM
|
#5
|
Human being with feelings
Join Date: May 2015
Location: Québec, Canada
Posts: 4,937
|
I made a minimal extension example here and an API loader here. (Also, reaper_plugin_functions.h provides REAPERAPI_LoadAPI for importing *everything* from that file.)
The documentation about how to create API functions is in reaper_plugin_functions.h above the declaration of plugin_register (it's incomplete as it doesn't mention APIvararg for ReaScripts at all!).
Quote:
Originally Posted by juliansader
* How do I get REAPER to call registerAPI() and removeAPI() when initializing and unloading the extension?
|
REAPER_PLUGIN_ENTRYPOINT (ReaperPluginEntry) is the one function REAPER calls when either initializing or unloading the extension. REAPER calls it with a NULL rec when it's time to unload.
Quote:
Originally Posted by juliansader
* What should my headers include for cross-platform builds? I have checked SWS's headers in stdafx.h and other files, but IIRC Justin mentioned that the SWS headers are actually outdated, and that extensions should use "swell_provided_by_app".
|
SWELL_PROVIDED_BY_APP is a macro that must be defined (eg. in the compiler flags) to enable that mode. When using that you don't have to build the used SWELL functions' source into your binary, just a loader (swell-modstub).
The API exporting code in ReaPack might be useful too (in the src/api_* files with initialization in src/main.cpp). I use RAII to register the functions and preprocessor macros (with the help of boost mpl) to make writing the actual functions easier.
Last edited by cfillion; 08-28-2018 at 12:53 AM.
|
|
|
08-27-2018, 09:28 PM
|
#6
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
|
Reaper provides an action that writes a header file showing the complete current API available by C code.
-Michael
|
|
|
08-28-2018, 12:19 PM
|
#7
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
Things are slowly starting to make sense...
Except, the vararg stuff discussed in Export function from C++ plugin to be used with Lua?. I posted this question:
Quote:
Originally Posted by juliansader
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.)
|
Last edited by juliansader; 08-28-2018 at 02:11 PM.
|
|
|
09-03-2018, 05:06 AM
|
#8
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
My extension is working!
Another question: is an extension required to do any REAPER-related cleanup and uninitialization when unloading (when the REAPER_PLUGIN_ENTRYPOINT function is called with rec=NULL)?
Code:
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec)
{
if(!rec) {
// cleanup code here
return 0;
}
|
|
|
09-03-2018, 05:53 AM
|
#9
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by juliansader
My extension is working!
Another question: is an extension required to do any REAPER-related cleanup and uninitialization when unloading (when the REAPER_PLUGIN_ENTRYPOINT function is called with rec=NULL)?
|
As far as I know, not really. The entrypoint is called with a null pointer when Reaper is about to quit anyway. Of course if your own code has crucial things to clean up, you should do that.
If you want to be pedantic, I suppose you could unregister all the things you previously registered but that would likely just make quitting Reaper slower without any benefits.
All these things might change in the future if Reaper for example gets the ability to unload and reload extension plugins while it is running.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
09-03-2018, 07:53 AM
|
#10
|
Human being with feelings
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
|
SWS at least seems to unregister everything when unloading.
https://github.com/reaper-oss/sws/bl...nsion.cpp#L632
But maybe that's just a precautious thing as Xenakios has said.
|
|
|
09-19-2018, 02:07 PM
|
#11
|
Human being with feelings
Join Date: Jul 2009
Posts: 3,714
|
Thanks to everyone's help, my extension is building OK on Windows and Linux.
Here is my own example of a minimal extension that adds one ReaScript API function:
Code:
// WARNING: Apparently, the file names of user extensions MUST start with "reaper_".
#define REAPERAPI_IMPLEMENT
#define REAPERAPI_MINIMAL // Only load the API functions #define'd by REAPERAPI_WANT_...
#define REAPERAPI_WANT_plugin_register
#idndef SWELL_PROVIDED_BY_APP
#error "SWELL_PROVIDED_BY_APP must be defined for entire project. (If using the command line, use -DSWELL_PROVIDED_BY_APP.)"
#endif
// reaper_plugin_functions.h #include's reaper_plugin.h, which in turn #include's either windows.h or swell.h, depending on platform.
// So probably only necessary to #include reaper_plugins_functions.h
#include "reaper_plugin_functions.h"
#include <cstdio>
#include <cstring>
// Example of a function that will be exported to the ReaScript API
bool TEST_Window_GetRect(void* windowHWND, int* leftOut, int* topOut, int* rightOut, int* bottomOut)
{
RECT r{ 0, 0, 0, 0 };
bool isOK = !!GetWindowRect((HWND)windowHWND, &r);
*leftOut = (int)r.left;
*rightOut = (int)r.right;
*topOut = (int)r.top;
*bottomOut = (int)r.bottom;
return (isOK);
}
// Apparently, under the hood, REAPER converts all ReaScript API functions to this standard format:
// void* func(void** arglist, int numparams)
// The names and types of the parameters and return values that the user see in the IDE are registered by plugin_register(APIdef_...)
// So all functions must either be in this format, or get a wrapper function such as this:
static void* __vararg_TEST_Window_GetRect(void** arglist, int numparms)
{
return (void*)(INT_PTR)TEST_Window_GetRect((void*)arglist[0], (int*)arglist[1], (int*)arglist[2], (int*)arglist[3], (int*)arglist[4]);
}
// (This struct is copied from SWS.)
// Struct to store info such as function name and help text for each function that the extensions intends to expose as API.
// This info will be used by REAPER's plugin_register functions to register the functions.
// NOTE: REAPER requires the return values, paramaters and help text to be one long \0-separated string.
// The parm_names, parm_types and help fields will therefore have to be concatenated before registration, and stored in the defstring field.
// If compatilibity with SWS is not an issue, I would remove parm_names, parm_types and help, and simply define one \0-separated string from the start.
struct structAPIdef
{
void* func; // pointer to the function that other extensions use
const char* func_name;
void* func_vararg; // pointer to the wrapper function that ReaScript API calls
const char* regkey_vararg; // "APIvararg_funcName" - for
const char* regkey_func; // "API_funcName"
const char* regkey_def; // "APIdef_funcName"
const char* ret_val; // return value type, as string
const char* parm_types;
const char* parm_names;
const char* help;
char* defstring; // \0-separated string for APIdef... Will be concatenated and assigned while registering function
};
// (This macro is copied from SWS.)
// Macro to construct a comma-separated list of all the variants of a function name that are required for plugin_register(), in the order required by structAPIdef in which these variants are stored.
// APIFUNC(funcName) becomes (void*)funcName, "funcName", (void*)__vararg_funcName, "APIvararg_funcName", "API_funcName", "APIdef_funcName"
#define APIFUNC(x) (void*)x,#x,(void*)__vararg_ ## x,"APIvararg_" #x "","API_" #x "","APIdef_" #x ""
// Array of function info structs
structAPIdef arrayAPIdefs[] =
{
{ APIFUNC(TEST_Window_GetRect), "bool", "void*,int*,int*,int*,int*", "windowHWND,leftOut,topOut,rightOut,bottomOut",
"Retrieves the coordinates of the bounding rectangle of the specified window. The dimensions are given in screen coordinates relative to the upper-left corner of the screen.\nNOTE: The pixel at (right, bottom) lies immediately outside the rectangle.", }
};
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
// If rec !- nil, the extenstion is being loaded. If rec == nil, the extension is being UNloaded.
if (rec)
{
if (REAPERAPI_LoadAPI(rec->GetFunc) != 0)
{
// This is the WIN32 / swell MessageBox, not REAPER's API MB. This should create a separate window that is listed in the taskbar,
// and more easily visible behind REAPER's splash screen.
MessageBox(NULL, "Unable to import default API functions.\n\nNOTE:\nThis extension requires REAPER v5.965 or later.", "ERROR: js_ReaScriptAPI extension", 0);
return 0;
}
else
{
// functions imported, continue initing plugin...
// Each function's defstring will temporarily be contructed in temp[]
char temp[10000];
for (structAPIdef& f : arrayAPIdefs)
{
// REAPER uses a \0-separated string. sprintf cannot print \0, so must temporarily print \r and replace later.
snprintf(temp, 10000, "%s\n%s\n%s\n%s", f.ret_val, f.parm_types, f.parm_names, f.help);
// Create permanent copy of temp string, so that REAPER can access it later again.
f.defstring = strdup(temp);
// Replace the three \n with \0.
int i = 0; int countZeroes = 0; while (countZeroes < 3) { if (f.defstring[i] == '\n') { f.defstring[i] = 0; countZeroes++; } i++; }
// Each function must be registered in three ways:
// APIdef_... provides for converting parameters to vararg format, and for documentation in the auto-generated REAPER API header and ReaScript documentation.
plugin_register(f.regkey_def, (void*)f.defstring);
// API_... for exposing to other extensions, and for IDE to recognize and color functions while typing .
plugin_register(f.regkey_func, f.func);
// APIvarag_... for exporting to ReaScript API.
plugin_register(f.regkey_vararg, f.func_vararg);
}
return 1; // I'm not sure the relevance is of REAPER_PLUGIN_ENTRYPOINT's return values.
}
}
// Not sure what clean-up needs to be done when REAPER quits.
// Perhaps freeing allocated memory blocks, or mouse cursors created by CreateCursor?
else
{
return 0;
}
}
Last edited by juliansader; 01-01-2019 at 03:01 PM.
|
|
|
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 11:36 AM.
|