Go Back   Cockos Incorporated Forums > REAPER Forums > ReaScript, JSFX, REAPER Plug-in Extensions, Developer Forum

Reply
 
Thread Tools Display Modes
Old 08-27-2018, 07:06 AM   #1
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default 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.)
juliansader is offline   Reply With Quote
Old 08-27-2018, 07:38 AM   #2
nofish
Human being with feelings
 
nofish's Avatar
 
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
Default

cfillion (he'll probably show up here soon anyway ) wrote about it here:

https://forum.cockos.com/showpost.ph...&postcount=273
nofish is offline   Reply With Quote
Old 08-27-2018, 10:53 AM   #3
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

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...)
juliansader is offline   Reply With Quote
Old 08-27-2018, 11:51 AM   #4
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
Default

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

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 View Post
* 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 View Post
* 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.
cfillion is offline   Reply With Quote
Old 08-27-2018, 09:28 PM   #6
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
Default

Reaper provides an action that writes a header file showing the complete current API available by C code.

-Michael
mschnell is offline   Reply With Quote
Old 08-28-2018, 12:19 PM   #7
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

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 View Post
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.)

Last edited by juliansader; 08-28-2018 at 02:11 PM.
juliansader is offline   Reply With Quote
Old 09-03-2018, 05:06 AM   #8
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

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;
}
juliansader is offline   Reply With Quote
Old 09-03-2018, 05:53 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 juliansader View Post
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.
Xenakios is offline   Reply With Quote
Old 09-03-2018, 07:53 AM   #10
nofish
Human being with feelings
 
nofish's Avatar
 
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
Default

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.
nofish is offline   Reply With Quote
Old 09-19-2018, 02:07 PM   #11
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

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.
juliansader 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 11:36 AM.


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