Wasabi

From Winamp Developer Wiki
Jump to: navigation, search

Winamp Service Architecture Binary Interface

Rationale

The plugin struct definition API is effective, but it would be cumbersome to create a new plugin type for every possible scenario. As a result, a new generic plugin system was created named Wasabi (Winamp Service Architecture Binary Interface). Instead of hard-coded lists of function pointers to be defined, plugins create C++ objects implementing known interfaces identified with some unique ID (GUID). Rather than a specific filename having one specific plugin type (in_*.dll for Input plugins), Wasabi components 'register' the services they provide. This allows one plugin to provide multiple services, allows for new plugin types to be created without new code, and allows for "specialized" plugin types. For example, playlist.w5s loads the Playlist Manager API for loading and analyzing playlist files, as well as individual playlist loading objects for each type (M3U, PLS, B4S), and various helper services involving playlists.

Plugins can communicate with each other by requesting each other's services via the Service Manager. They can also query for API's that Winamp itself registers such as the System Callbacks API (generic notification system) and the Application API (allows plugins to query for version number, settings path, etc)

Wasabi relies on a common base class called "Dispatchable". Dispatchable implements a single virtual method which consists of a "function ID" integer value, an array of parameters and a parameter count. Child class implementations override this function with logic which "dispatches" each ID to the appropriate function. In some regards, it is similar to the old SendMessage API. However, a set of inline helper methods defined inside each interface removes the complexity of using Wasabi interfaces, and most of the complexity from implementing Wasabi interfaces. They can be treated like any other C++ object.

Components of Wasabi

  • api definitions - formalizes the interfaces for various objects, APIs, interfaces and services
  • wasabi helper classes - accesses the service manager to find services to perform required tasks. e.g. FileReaderApi enumerates the available file reader services to find an appropriate one and read from it. Used to hide complexity from the programmer. Some of these exist as shared services, to cut down on duplicate code across plugins.
  • general purposes helper classes / functions - called BFC (brennan foundation class) currently.

What is Wasabi intended to solve?

  1. C++ does not define a binary interface to share objects between modules
  2. The existing winamp 2 plugin architecture only defines a limited set of interfaces
  3. Existing winamp 2 plugins have limited interaction with Winamp.

Dispatchable

The Dispatchable class is the base class for many of the classes within Wasabi. It contains a single virtual method, _dispatch(), which is passed a dispatch ID msg, a pointer to store a return value retval, a pointer to the first element of an array of parameters params, and the parameter count nparam.

The class is defined as:

class Dispatchable
{
public:
 // this is virtual so it is visible across modules
 virtual int _dispatch(int msg, void *retval, void **params = NULL, int nparam = 0)=0;
};

In many ways, it is similar to the Invoke() portion of COM's IDispatch class. It is also conceptually similiar to a Win32 Window Procedure.


When classes are used by different plugins, the Dispatchable base class is used instead of abstract classes with pure virtual functions. Why? Because it makes it easier to add additional methods, without requiring "version 2" classes such as those that COM uses. (See for example http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform95/htm/readerobject.asp). And since the handling of an unknown "msg" is uniform, it allows a limited form of forwards compatibility as well.

Interfaces

Interfaces that are intended to be shared amongst plugins are defined specially in Wasabi. An interface is derived from Dispatchable and defines a set of dispatch messages which make up the particular interface. Depending on its use, an interface's classname will be given one of the following prefixes

  • api - For singleton APIs. Examples of APIs are the Service Manager and the System Callbacks API.
  • ifc - For interfaces. These are given to Dispatchable classes which are not involved with the service manager. These are typically used for passing objects to other objects or for accepting objects as return values.
  • svc - For collective services. These are services which are one of many classes which implement an interface. Examples include image loading services and playlist loading services.
  • obj - For objects. These are classes which are created by the service factory on getInterface() and destroyed on releaseInterface(). Examples of objects are instances of the XML Parser and the JNetLib http retriever.

Example

The interface for a "Region" (a shape that defines a portion of a window, usually used for painting)

class ifc_region : public Dispatchable
{
public:
 int _dispatch(int msg, void *retval, void **params=NULL, int nparam=0);

 enum
 {
 REGION_GETOSHANDLE =50,
 REGION_CLONE =100,
 REGION_DISPOSECLONE =110,
 REGION_PTINREGION =120,
 REGION_OFFSET =130,
 REGION_GETBOX =140,
 REGION_SUBTRACTRGN =150,
 REGION_SUBTRACTRECT =160,
 REGION_ADDRECT =170,
 REGION_ADD =180,
 REGION_AND =190,
 REGION_SETRECT =200,
 REGION_EMPTY =210,
 REGION_ISEMPTY =220,
 REGION_EQUALS =230,
 REGION_ENCLOSED =240,
 REGION_INTERSECTRGN =250,
 REGION_DOESINTERSECTRGN =251,
 REGION_INTERSECTRECT =260,
 REGION_ISRECT =270,
 REGION_SCALE =280,
 REGION_DEBUG =290,
 REGION_MAKEWNDREGION =300,
 REGION_GETNUMRECTS =310,
 REGION_ENUMRECT =320,
 };
};

Typically, the Dispatchable classes define non-virtual methods corresponding to each dispatch message. The implementation of these methods are to simply pack the parameters into an array and call _dispatch.

For example:

inline void Region::offset(int x,int y)
{
 void *params[2] = {&x, &y};
 _dispatch(REGION_OFFSET, NULL, params,2);
}

The purpose of these methods are to allow the interface to be used without having to think about the Dispatchable interface. Once the interface is defined and the helper methods are written you can totally forget about Dispatchable.

Interfaces allow for an object to be shared amongst Winamp and plugins. For example, since api_region is defined, you may call the function api_region *GetUpdateRegion(); and use the returned object safely. Services

Services

In Wasabi, a service is an object that implements functionality that Winamp or other plugins may require. For example, the image loader service loaded decompresses an image. The memory manager service acts as a common malloc/free, so that memory allocated in one plugin can be freed safely in another.

Services use Interfaces. The GetUpdateRegion example in the Interfaces section described using interfaces to define an object returned from a function. But how does a plugin call a function in Winamp in the first place? That's where services come in.

Services are retrieved through api_service, an interface which is passed to each plugin as it is loaded. api_service acts as a "global store" of services. Plugins can retrieve other services and register their own services.

There are two basic kinds of services, unique services and collective services.

A collective service is an interface which implements a particular service type. Typically, this is a service that has to be implemented differently depending on use. For example, a PNG loading service would be part of the WaSvc::IMAGELOADER service type, and would be a different object from other WaSvc::IMAGELOADER services (such as a JPG loader). File readers are another example: direct file access could be implemented separately from HTTP access, ZIP file access, etc. Clients (Winamp or plugins) can enumerate through all the services of a particular type, to find one that fits its needs.

Unique services are one-off interfaces. They have a service type of WaSvc::UNIQUE. Rather than checking for a particular service type, a client asks for a particular service by GUID. A memory manager service would be a unique service. Typically, unique services are used to provide some sort of system access, or to perform some sort of very specific task.

Service Factories

Services are retrieved via Service Factories. api_service operates with service factories, and plugins must provide service factories for the services they implement. Service Factories create objects of your service whenever requested by a client.

Although the factory is annoying to deal with (just another layer to get to what you want), it allows for the developer to control object creation. Most unique services (and some collective services) are singletons. That is, there only needs to be one of them in existence at any point in time. The same object pointer may be returned every time from the factory.

Other services might be need to be uniquely created for each client. Each file reader, for example, will need a separate object, since state is stored in the file reader object.

Services are released through the factory that created them, allowing the factory to control destruction as well as construction.

System Callbacks

Not technically part of the the api/interface/services infrastructure within Wasabi. System Callbacks (api_syscb), is a service which broadcasts messages to any callback receiver (a class derived from SysCallback) that has registered itself with the System Callbacks service.

Service registration and deregistration messages are broadcasted by api_service, so api_syscb is guaranteed to be an available service (because it must be created for api_service to operate).