Foobar2000:Development:Overview: Difference between revisions

From Hydrogenaudio Knowledgebase
No edit summary
Line 19: Line 19:
== Getting a component listed on foobar2000.org/components ==
== Getting a component listed on foobar2000.org/components ==


Register a forum account and contact Peter with info about your component:
Register a forum account and contact [https://hydrogenaud.io/index.php?action=profile;u=84 Peter] with info about your component.
https://hydrogenaud.io/index.php?action=profile;u=84


== Services ==
== Services ==

Revision as of 11:39, 22 June 2022

What is a component

A foobar2000 component is a Windows Dynamic Link Library (DLL) extending the functionality of the foobar2000 application.

A foobar2000 component implements one or more entrypoint services and interacts with services provided by foobar2000 core or other components.

Each component has a single exported DLL function, foobar2000_get_interface(), called on startup to initialize and discover entrypoint services your component. This function is generated automatically by the foobar2000_component_client module. Do not implement it yourself.

Packaging a component

Components are delivered to the user in form of fb2k-component file.

This is simply a renamed zip of your DLL.

If your component requires additional files, include them in the zip; foobar2000 will extract them to the folder containing your DLL upon installation.

Getting a component listed on foobar2000.org/components

Register a forum account and contact Peter with info about your component.

Services

A service type is an interface class, deriving directly or indirectly from service_base class. A service type class must not have any data members; it can only provide virtual methods (to be overridden by service implementation), helper non-virtual methods built around virtual methods, static helper methods, and constants / enums. Each service interface class must have a static class_guid member, used for identification when enumerating services or querying for supported functionality. A service type declaration should declare a class with public virtual/helper/static methods, and use FB2K_MAKE_SERVICE_INTERFACE() / FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard service behaviors for the class; additionally, class_guid needs to be defined outside class declaration (e.g. const GUID someclass::class_guid = {….}; ). Note that most of components will not declare their own service types, they will only implement existing ones declared in the SDK.

A service implementation is a class derived from relevant service type class, implementing virtual methods declared by service type class. Note that service implementation class does not implement virtual methods declared by service_base; those are implemented automatically.

You cannot directly instantiate a service implementation object because it's still missing virtual methods of service_base.

class myworker : public threaded_process_callback { /* threaded_process_callback methods overridden here */ };
auto obj = new myworker; // error
auto obj = fb2k::service_new<myworker>(); // good
service_ptr_t<myworker> obj = new service_impl_t<myworker>; // longer equivalent of fb2k::service_new<>, commonly used in older code, not auto-friendly

fb2k::service_new<> takes care of implementing common methods (service_base) needed by all services and returns an autopointer object that manages reference counting for you.

Note that service_base methods:

  • service_add_ref()
  • service_release()
  • service_query()

... are only intended for autopointer classes and should never be called directly in your code.

See also: Auto Pointers

Entrypoint services

An entrypoint service type is a special case of a service type that can be registered using service_factory templates, and then accessed from any point of service system (excluding DLL startup/shutdown code, such as code inside static object constructors/destructors). An entrypoint service type class must directly derive from service_base.

Registered entrypoint services can be accessed using:

  • For services types with variable number of implementations registered:
  • enumerate() static methods of the service class>
for( auto ptr : someclass::enumerate() ) { /* do stuff with ptr */ }
  • Or, service_enum_t<>, commonly used in older code:
service_enum_t<someclass> e; service_ptr_t<someclass> ptr; while(e.next(ptr)) ptr->dosomething();
  • For services types with a single always-present implementation registered - such as core services like playlist_manager - using someclass::get(), e.g.:
auto api = someclass::get(); api->dosomething(); api->dosomethingelse();

Using per-service-type defined static helper functions, e.g. someclass::g_dosomething() - those use relevant service enumeration methods internally. An entrypoint service type must use FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT() macro to implement standard entrypoint service behaviors, as opposed to all other service types that use FB2K_MAKE_SERVICE_INTERFACE() macro instead.

You can register your own entrypoint service implementations using FB2K_SERVICE_FACTORY() macro. It creates a static instance of your object and makes it available for everyone else to enumerate and access. Some services need to be registered differently, see documentation of individual services for details.

A typical entrypoint service implementation looks like this:

class myinitquit : public initquit {
public:
	void on_init() override {};
	void on_quit() override {};
};
FB2K_SERVICE_FACTORY(myinitquit);

Exception use

Most of API functions use C++ exceptions to signal failure conditions. All used exception classes must derive from std::exception; this design allows various instances of code to use single catch() line to obtain human-readable description of the problem to display.

Additionally, special subclasses of exceptions are defined for use in specific conditions, such as exception_io for I/O failures. As a result, you must provide an exception handler whenever you invoke any kind of I/O code that may fail, unless in specific case calling context already handles exceptions (e.g. input implementation code - any exceptions should be forwarded to calling context, since exceptions are a part of input API).

Implementations of global callback services such as playlist_callback, playback_callback or library_callback must not throw unhandled exceptions; behaviors in case they do are undefined (app termination is to be expected).

Thread safety

User interface related services are main thread only, unless explicitly documented otherwise.

Low-level audio decoding, processing and file access related services can be called from any thread.

Any method that takes an abort_callback& argument can be assumed to be meant for use in worker threads rather than main thread.

If you have created an instance of some object, such as file or input_decoder, you should call its methods from one thread. If you must talk to one such object from multiple threads, synchronize access.

When in doubt about whether some API can be called off-main-thread, just try it. Methods that are designed for main thread use only will instantly crash (bugcheck) if called from another thread.

Main thread

You can defer any code to be executed in foobar2000's main thread using main_thread_callback service.

Instead of using main_thread_callback directly, use fb2k::inMainThread() helper function to queue a function/lambda to be executed in main thread. This function returns immediately without blocking; if you need to wait for your main thread payload to execute, use other means (such as wait for an event).

If called in main thread, fb2k::inMainThread() queues your code to be executed later in main thread. This can be used for an example to escape from the current context, avoiding race conditions when calling other foobar2000 API methods in response to global callbacks.

Abortability

Various potentially timeconsuming methods take an abort_callback& parameter to allow you to cancel them anytime.

When implementing such function, pass caller's abort_callback& down to any other functions expecting one; poll the object often enough to react to user cancelling the operation without disturbing delays.

In case of user aborting the operation (such as stopping playback, cancelling ReplayGain scan, etc), the abort_callback will become set and the next function to poll it will throw exception_aborted, which then should be handled by the context that owns the abort_callback object.

If you must call a function taking abort_callback& and have no means to abort it, pass fb2k::noAbort, the shared dummy abort_callback object that never becomes aborted.


Storing configuration

In order to create your entries in the foobar2000 configuration files, you must instantiate some objects that derive from cfg_var class. Those can be either predefined classes (cfg_int, cfg_string, etc) or your own classes implementing relevant methods.

Each cfg_var instance has a GUID assigned, to identify its configuration file entry. The GUID is passed to its constructor (which implementations must take care of, typically by providing a constructor that takes a GUID and forwards it to cfg_var constructor).

Note that cfg_var objects can only be instantiated statically (either directly as static objects, or as members of other static objects).

It is encouraged to create your own configuration files in foobar2000 profile for more complex data to allow easy import / export / backup.

Tracking foobar2000 lifetime events

See: Global Callbacks