Writing REALbasic Plug-Ins

REALbasic plugins are code resources that extend the features available to a REALbasic user.

The things that can be written with plugins are:

Each of these will be explained in more detail below:

  1. Plugin File Structure
  2. Plugin Code Requirements
  3. Registering New Methods
  4. Registering New Classes
  5. Registering Class Extensions
  6. Registering New Controls
  7. Plugin API Functions
  8. Incorporating Resources
  9. Frequently Asked Questions
  10. Technical details
  11. Version History

Plugin File Structure

A plugin is a resource file with a Creator type of 'RBv2' and a file type of 'RBPl'. There is an icon associated with this by REALbasic (though if you upgraded from a previous version, you may need to rebuild your desktop to see the icon).

Plugin code goes into the following resource types:

The first plugin resource in a file must have id 128, the second plugin 129, etc. Usually, you'll put all your code into a single plugin resource (since it may contain any number of classes and functions).

Plugin Code Requirements

All plugins must have a routine called PluginEntry - this routine should notify REALbasic about all of the features that the plugin provides.

A simple PluginEntry routine might be something like:

void PluginEntry(void)
{
	REALRegisterMethod(&add5defn);
}

This plugin has defined a single method, and it calls the REALRegisterMethod function to define it.

The currently supported registration routines are:

Registering New Methods

You add a new global method to REALbasic with the REALRegisterMethod function. This takes a single parameter, which is a REALmethodDefinition structure. That structure contains three elements:

  1. function -- REALProc (address) of the function (i.e., in your C code)
  2. setterFunction -- REALProc (address) of the function when it appears as an lvalue (i.e., on the left hand side of an assignment)
  3. declaration -- a string specifying the method name, parameters, and return type
You may pass REALnoImplementation for the second parameter when it does not apply.

Example. To register the following function:

static int add5func(int v)
{
	return v + 5;
}
...you would need a REALmethodDefinition as follows:
REALmethodDefinition add5def = {
	(REALproc) add5func,
	REALnoImplementation,
	"Add5(num as Integer) as Integer" };
And then, in your PluginEntry function, you'd simply call REALRegisterMethod( add5def ). Note that the definition ("add5def" in the example above) must be either global or static, i.e., it must not go out of scope when PluginEntry exits, since RB depends on it sticking around for the lifetime of the application.

Registering New Classes

The REALRegisterClass function requires a reference to a REALclassDefinition structure. For example:

REALclassDefinition ShellClass = {
	kCurrentREALControlVersion,
	"Shell",                                // name of class
	nil,                                    // no superclasses
	sizeof(ShellData),                      // size of our data
	0,
	(REALproc) ShellConstructor,            // constructor
	(REALproc) ShellDestructor,             // destructor
	ShellProperties,                        // properties
	sizeof(ShellProperties) / sizeof(REALproperty),
	ShellMethods,                           // methods
	sizeof(ShellMethods) / sizeof(REALmethodDefinition),
	// other fields left nil -- no events etc.
};

The fields of the REALclassDefinition structure are as follows:

  1. version the version of the plugin architecture that the control was built under; just pass the constant kCurrentREALControlVersion
  2. name: the name of the class as used in REALbasic
  3. superName: the name of the class's superclass; must be a built-in class or a plug-in class registered prior to this one
  4. dataSize: the size of the custom data structure allocated for each object. NOTE: this data structure must be the same size on all platforms (use padding if necessary)!
  5. forSystemUse: reserved (set to zero)
  6. constructor: function to call to initialize your custom data
  7. destructor: function to call to destroy your custom data
  8. properties: a reference to the array of property declarations for the class
  9. propertyCount: the number of properties for the class
  10. methods: a reference to the array of method declarations for the class
  11. methodCount: the number of method declarations for the class
  12. events: a reference to the array of event declarations for the class
  13. eventCount: the number of event declarations for the class
  14. eventInstances: a reference to an array of event handlers for the class
  15. eventInstanceCount: the number of event handlers there are
  16. interfaces: a comma-delimited string of names of the interfaces supported by this control
  17. bindDescriptions: a reference to an array of binding descriptions for this control
  18. bindDescriptionCount: the number of binding descriptions there are

Use of event handlers, interfaces, and bindings is not yet documented.

Note that the class definition ("ShellClass" in the example above) must be either global or static, i.e., it must not go out of scope when PluginEntry exits, since RB depends on it sticking around for the lifetime of the application. The same applies to all the subordinate data structures (properties, methods, etc.).

Declaring properties

A REALproperty has the following fields:

  1. group: the name of the property group that the property should be displayed in
  2. name: the name of the property
  3. type: the name of the type of the property
  4. flags: any special property flags; could be 0, or any sum of:
  5. propertyOffset: the offset of the property in the auxiliary structure for the form
  6. getter: a REALproc to be invoked for getting the value, or REALstandardGetter
  7. setter: a REALproc to be invoked for setting the value, or REALstandardSetter
  8. param: arbitrary parameter, or if you're using REALstandardGetter or REALstandardSetter, this should be the location of the data value within the control data structure
  9. editor: not yet documented
  10. enumCount: quantity of enumerated values
  11. enumEntries: an array of enumeration strings
Note that the initial value of param will be set at compile time, using the version of your plugin which matches the IDE. That is, if the user is running the PowerPC IDE, then the value of param given in the PowerPC version of the plugin will be passed to the setter when the control is created in a built app, even if that app is built for Windows. In short: always use the same param value for all platforms, which may mean being careful to use the same struct alignment and data sizes on all platforms.

Example:

REALproperty boxProperties[] = {
	{ "Appearance", "BackColor", "Color", REALpropInvalidate, 
	   REALstandardGetter, REALstandardSetter, FieldOffset(facelessData, fillColor) }
};

Declaring class (or control) methods

Class or control methods are declared exactly the same as new global methods. E.g.,

REALmethodDefinition boxMethods[] = {
	{ (REALProc) makeRed, REALnoImplementation, "MakeRed()" },
	{ (REALProc) colorComponent, REALnoImplementation, "ColorComponent(v as Integer) as Integer" }
};

Declaring events

REALevent boxEvents[] = {
	{ "Action" }
};

A REALevent structure merely has the declaration for the event, which is in the same form as a method declaration. Adding an event to a class or control allows you to provide "callbacks" to your users; the user writes RB code for an event you've declared, and you can invoke that code via REALGetEventInstance.

Registering Class Extensions

The REALRegisterClassExtension function also uses a reference to a REALclassDefinition structure. So declaring an extension to an existing class is just like declaring a new class, with the following exceptions:

  1. For the class name, use the name of the RB class you're extending.
  2. Leave the superclass name blank (nil).
  3. The constructor and destructor functions for class extensions are not called.
  4. Use of a class extension alone is not enough for RB to recognize that the plugin has been used.

The third point is especially important; it means that if your class extension must not contain custom data that requires explicit allocation or deallocation. (RB will, however, initialize all your custom data to zero and release the data itself when the object dies; but it will not release any additional memory you may have allocated.)

The last point is a bug in REALbasic which causes the following: if the user uses nothing from your plugin except class extensions, RB will not recognize that your plugin has been used at all and will therefore fail to copy your plugin code into built apps. This is something we hope to fix someday, but until then, add a global method (InitFoo or IsFooAvailable or whatever) and require your users to call it at some point in their code.

Registering New Controls

The REALRegisterControl function is passed a reference to a REALcontrol structure.

e.g.,

REALcontrol boxControl = {
	kCurrentREALControlVersion,
	"box",
	sizeof(boxData),
	REALcontrolAcceptFocus | REALcontrolFocusRing,
	169,
	170,
	32,
	32,
	boxProperties,
	sizeof(boxProperties) / sizeof(REALproperty),
	boxMethods,
	sizeof(boxMethods) / sizeof(REALmethodDefinition),
	boxEvents,
	sizeof(boxEvents) / sizeof(REALevent),
	&boxBehaviour
};

The fields of the REALcontrol structure are as follows:

  1. version the version of the plugin architecture that the control was built under; just pass the constant kCurrentREALControlVersion
  2. name: the name of the control as used in REALbasic
  3. dataSize: the size of the auxiliary data structure allocated for the control. NOTE: this data structure must be the same size on all platforms (use padding if necessary)!
  4. flags: any special control flags. In the given example, the control indicates that it accepts focus, and it wants REALbasic to automatically draw a focus ring when it has the focus.
  5. toolbarPICT: the resource id of a PICT resource of the toolbar icon for the control
  6. toolbarDownPICT: the resource id of a PICT resource of the depressed version of the toolbar icon
  7. defaultWidth: initial width of the control, in pixels
  8. defaultHeight: initial height of the control, in pixels
  9. properties: a reference to the array of property declarations for the control
  10. propertyCount: the number of properties for the control
  11. methods: a reference to the array of method declarations for the control
  12. methodCount: the number of method declarations for the control
  13. events: a reference to the array of event declarations for the control
  14. eventCount: the number of event declarations for the control
  15. behaviour: a reference to the control behaviour table
  16. (reserved, set to 0)
  17. eventInstances: a reference to an array of event handlers for the control
  18. eventInstanceCount: the number of event handlers there are
  19. interfaces: a comma-delimited string of names of the interfaces supported by this control
  20. bindDescriptions: a reference to an array of binding descriptions for this control
  21. bindDescriptionCount: the number of binding descriptions there are
The declaration of properties, methods, and events is exactly the same for a control as it is for a class -- see "Registering New Classes" for details.

Use of event handlers, interfaces, and bindings is not yet documented.

Note that the control definition ("boxControl" in the example above) must be either global or static, i.e., it must not go out of scope when PluginEntry exits, since RB depends on it sticking around for the lifetime of the application. The same applies to all the subordinate data structures (properties, methods, etc.).

Control behaviour

The REALcontrolBehaviour structure references the REALexport entry points that define the behaviour for the control. For example:
REALcontrolBehaviour boxBehaviour = {
	boxInit,
	nil,
	boxDraw
	/* other fields left nil */
};

The entry points are as shown in the following table (in this order).

functiondescriptionprototype
initFunctioncalled when the control is initializedvoid initFunction(REALcontrolInstance)
disposeFunctioncalled when the control is disposedvoid disposeFunction(REALcontrolInstance)
redrawFunctioncalled when the control needs to be redrawnvoid redrawFunction(REALcontrolInstance) (MacOS)
void redrawFunction(REALcontrolInstance instance, REALgraphics context) (Win32)
clickFunctioncalled when the control is clicked onBoolean clickFunction(REALcontrolInstance, int x, int y, int modifiers)
mouseDragFunctioncalled while the mouse is held down after clickFunction returned truevoid mouseDragFunction(REALcontrolInstance, int x, int y)
mouseUpFunctioncalled when mouse is released after clickFunction returned truevoid mouseUpFunction(REALcontrolInstance, int x, int y)
gainedFocusFunctioncalled when the control gains the focus void gainedFocusFunction(REALcontrolInstance)
lostFocusFunctioncalled when the control loses the focus void lostFocusFunction(REALcontrolInstance)
keyDownFunctioncalled when the user presses a key while the control has the focusBoolean keyDownFunction(REALcontrolInstance, int charCode, int keyCode, int modifiers)
openFunctioncalled when the control is opened void openFunction(REALcontrolInstance)
closeFunctioncalled when the control is closed void closeFunction(REALcontrolInstance)
backgroundIdleFunctioncalled periodically when nothing much is going onvoid backgroundIdleFunction(REALcontrolInstance)
drawOffscreenFunctioncalled to draw the control into an offscreen graphics contextvoid drawOffscreenFunction(REALcontrolInstance, REALgraphics context)
setSpecialBackgroundcalled to paint a special background for the controlvoid setSpecialBackground(REALcontrolInstance)
constantChangingcalled when the definition of a named constant changesvoid constantChanging(REALcontrolInstance, REALstring constantName)
droppedNewInstancecalled on a newly cloned control in the IDEvoid droppedNewInstance(REALcontrolInstance)

Plugin API Functions

There are roughly 100 functions provided by the header files in the plugin SDK for your use. These are documented in the API Reference. Note that not all API functions are available on all platforms. If you're writing a multi-platform plugin, you may wish to use the macros TARGET_68K, TARGET_PPC, TARGET_WIN32, and TARGET_CARBON to conditionalize your code.

Incorporating Resources

Control plugins should provide a normal and depressed button image for the tool palette as 'PICT' resource 128 and 129, respectively. The normal button image should be based on 'PICT' 297 in the REALbasic application's resource fork. The bevelled button background will appear when your plugin is running in version 2.1.2 (or earlier) of the IDE; in RB 3.0 and later, it will be automatically stripped away to make a nice transparent icon in the tool palette.

A plugin file can specify additional resources to be incorporated into the built Mac OS application via a 'PLRm' resource.

A PLRm resource is just a list of resource type and id pairs - these resources will be incorporated into any built applications that utilise the plugin. Note that your resources (the ones incorporated into built applications) must use resource IDs above 15000; resource IDs below 15000 are reserved for REAL Software and the operating system.

The PLRm resource must have the same id as the associated plugin (usually 128). A ResEdit TMPL resource for the PLRm resource can be found in the GetFolder.rsrc file (also the REALbasic application itself).

When accessing your plugin's resources, remember that you may be operating in an environment where there are many resource forks open. It's important to code defensively (don't make assumptions about the state of the resource chain) and nicely (don't change things more than necessary). You are guaranteed that your resource fork is the current one when PluginEntry is called. After that, you're guaranteed that your resource fork is still open, but it may not be the current one. Either of the following two approaches should be safe:

1. Load your resources in your PluginEntry, then detach them with DetachResource (and store them in global variables). You should not need your resource fork after that. The drawback to this approach is that it consumes memory even when your plug-in is not being used; if your resources are large, consider the next approach.

2. In PluginEntry, store the value of CurResFile() in a global variable:

	gMyResFile = CurResFile();
...and then, in any other plugin method that needs to access your resource fork, save the old current res file, switch to yours, and restore the old one when you're done:
	short oldResFile = CurResFile();
	UseResFile(gMyResFile);
	// ...load something from your resource fork...
	UseResFile(oldResFile);
If you follow these guidelines, your plugin will work robustly, and will also avoid causing any problems for other (less robust) plugins.

Frequently Asked Questions

How do I subclass a control?

Create a Class (not a Control), deriving from the base control class. Users won't see your custom subclass in the toolbar, but it will appear in the Super menu when they select the base control type in a window.

For example, if you create a class called "NiftyText" deriving from StaticText, then when a user selects any StaticText in a window, they'll be able to pick NiftyText from the Super menu.

Can I create plugins using Pascal?

REAL Software does not support creating plugins in Pascal. However, it should be possible provided your Pascal compiler can generate function calls with the correct calling conventions.

How can I make my constructor fail and return Nil?

You can't. Once your constructor function is called, the object has already been created and you're just being asked to fill in the data. There is nothing you can do here to "abort" the construction of the object. You might think to unlock the object (REALUnlockObject) and/or return Nil, but the RB calling code will then just get a reference to an invalid object, which will probably cause a crash.

Your best bet is to simply store whatever data in your object means (to you) "invalid", and check for this state on all subsequent function calls. But if you really need to be able to make a constructor that can fail, please submit a feature request with REALbugs.

How can I get the Win32 "HINSTANCE" (application instance)?

Many control functions in Windows require an HINSTANCE parameter, which is a global that refers to the running application. There is no API call to get this directly, but given a control instance, you can get it via the control's window (HWND) as follows:

When should I lock and unlock an object?

In general, you should lock an object whenever your code or the RB caller is going to keep a reference to that object. The one exception is an object freshly created with REALnewInstance, REALNewPicture, or REALNewMemoryBlock; these functions lock the newly created object (once) for you automatically.

So, if you have a factory function that creates and returns an object, just call REALnewInstance and return the result. Or, if you have a class method that creates a sub-object and stores it internally, just call REALnewInstance and store the result. But if you have a method that both returns the object to the caller, and stores it away in your class data, then you must call REALLockObject (or one of the type-specific locking functions) since there are now two references -- one in the caller's code, and one in your class data.

Similarly, if you have an object reference in your class data, and you provide a "getter" function that gives the caller a reference to that object, you must remember to lock it before returning the reference.

The one other time you may need to lock an object is if you're doing some complex operation on an object passed to your code, and something in those operations may invoke user code (e.g., invoking an event handler). Keep in mind that you don't know what the user's code may do; it may in fact release its reference to the object you're working on, causing it to be destroyed right out from under you. To deal with this danger, lock the object while you're working on it, and unlock it when done. This will ensure that the object does not get destroyed, even if the user code releases all references to it.

Call REALUnlockObject (or similar) to unlock an object whenever you reduce the number of references to the object. E.g., if you have a REALobject variable which you're about to set to nil or some other value, and assuming you locked the previous reference at some point, unlock it before assigning the new value. You'll also need to unlock all object references in your class destructor. Basically, every where you get a lock, you must somewhere unlock it.

To summarize: the lock count must match the number of references to the object. If you fail to lock, the application will crash; if you fail to unlock, it will leak memory.

Why does my Win32 control keep crashing?

One possible cause is that the auxiliary data structure for your control has a different size when compiled for Win32 than it does for MacOS. All the plugin registration info -- data size, property offsets, etc. -- is used by the REALbasic IDE at compile/link time to link your plugin into the output. That means that even when building a Win32 application, the IDE will use the values from the MacOS version of the plugin. For that matter, the same thing can happen between 68k, PowerPC, and Carbon versions; if you're running the IDE on a PowerPC using Classic MacOS, the link values will come from the PPC plugin code, even if compiling for 68k or Carbon.

As a result, it is essential that your custom data structures have the same size on all platforms. Use padding if necessary to make them the same. And if you use the FieldOffset macro to fill in parameters for your property definitions, you must also make sure that the offset of each such element within your custom data structure is the same on all platforms.

Another reason your Win32 classes or controls might crash is due to a bug in Win32 plugin support: if you have a class B that derives from class A, the data for class B ought to be stored after all the data from class A. This works properly on the Mac, but on Win32, the data from class B actually overlaps that of class A, which obviously causes problems. We hope to fix this soon (for REALbasic 3.1).

Technical details

This final section presents some details on the plugin interface which most plugin developers can safely ignore.

Calling conventions

There are several ways in which C functions can be compiled, with regard to the way parameters and results are handled. This is especially important where Mac 68k code is concerned, as when 68k code is called from PowerPC code, the parameters and results must be shuffled around by the system. REALbasic plugins must be compiled with Think C calling conventions. Think C conventions are nearly the same as the Metrowerks standard conventions, except that when a function result is a pointer, it must be returned in register D0 as with any other result, rather than in A0 as Metrowerks is wont to do.

The correct calling conventions are ensured by specifying the following pragmas:

// Compile with Metrowerks-ish/Think C calling conventions:
// Turn "MPW C" option off, and return pointers in D0 rather than A0.
#pragma d0_pointers on
#pragma mpwc off
These pragmas are specified in the latest version of REALplugin.h, a header file normally included by all plugin code. So if you have the latest SDK, you probably don't need to worry about this.


Version History

3.0.1a2 (24 Jan 2001):

  1. updated winheader.h to undefine nil if it was previously defined
  2. updated PluginMain.cpp to properly initialize global/static objects in 68k plugins
  3. added note in FAQ section about bug in derived classes in Win32

3.0.1a1 (19 Jan 2001):

  1. added note about the need for REALmethodDefinitions (etc.) to stay alive for the lifetime of the app
  2. added note about the bug regarding class extensions and built apps
  3. added guidelines on using MacOS resources
  4. added REALSetAccelerator
  5. Note: since REALbasic 3.0 is nearing completion, we are renumbering this SDK to 3.0.1a1 (indicating release 1 (alpha 1) of the SDK for REALbasic 3.0). However, it has changed from 2.1.1a5 only as indicated above; plug-ins written with this SDK will still work for REALbasic 2.1 as long as you avoid any functions marked "3.0a1" or later.

2.1.1a5 (14 Nov 2000):

  1. added documentation on registering new classes and class extensions
  2. several corrections to description of REALcontrol structure
  3. updated CarbonHeaders.h to work with Apple's latest Universal Interfaces
  4. added note about resource IDs below 15000 being reserved
  5. added FAQ about locking/unlocking objects
  6. added FAQ about keeping data structures the same on all platforms
  7. added several new functions to the API:
  8. updated the API Reference to include information on the version of RB in which each call is supported
  9. plugin glue now accesses QuickTime via a separate namespace, to help avoid name clashes
  10. updated REALstringToOSType to be faster and work without QuickTime

2.1.1a4 (16 June 2000):

  1. winheader.h reorganized to make better use of QuickTime headers
  2. made REALstringToOSType work for all platforms
  3. added REALSetControlEnabled
  4. added REALGetControlPosition
  5. added REALSetControlPosition
  6. fixed bug and updated version info on DLL postlinker plug-ins
  7. added FAQ item about getting HINSTANCE

2.1.1a3 (12 June 2000):

  1. winheader.h now includes QuickTime headers
  2. added REALBuildPictureFromDIB
  3. added Win32 support for REALgetMovieMovie and REALgetMoviePlayerController
  4. removed DCON code from DLL postlinker plug-ins

2.1.1a2 (2 June 2000):

  1. updated REALinRuntime call to work again on all platforms
  2. fixed bug in REALGetControlBounds function (introduced in 31 May 2000 release)
  3. added new Targets document
  4. cleaned & updated the "BoxControl" example project

2.1.1a1 (31 May 2000):

  1. large-scale refactoring of SDK internal code
  2. numerous corrections to above data structures to match the ones actually used (!)
  3. added note about Carbon code resource
  4. added Frequently Asked Questions section
  5. added pointer to new API Reference document

2.1.0 (31 Mar 2000):

  1. updated Read Me file to note that DLL postlinker is written by Thomas Tempelmann
  2. changed pragma in REALplugin.h from #pragma pointers_in_d0 to the apparently-more-compatible #pragma do_pointers on.
  3. miscellaneous updates to SDK files to avoid compiler warnings.
  4. added REALinRuntime function

24 Mar 2000:

  1. added pragmas for Think C calling conventions to REALplugin.h
  2. added "Technical details" section to plugin-doc.html
  3. changed recommended file creator to RBv2

r35a:

  1. added faceless control flag REALinvisibleControl
  2. add more examples
  3. added REALSelectGraphics
  4. added REALFolderItemFromFSSpec
  5. improved performance
  6. removed requirement to use Mixed Mode Manager to handle events
  7. added ability in incorporate additional resources


plugin-doc.html
14 Nov 2000