Foobar2000:Development:Overview: Difference between revisions

From Hydrogenaudio Knowledgebase
No edit summary
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
[[Category:Foobar2000 Development]]
[[Category:Foobar2000]]
== What is a component ==
== What is a component ==


A foobar2000 component is a Windows Dynamic Link Library (DLL) extending the functionality of the foobar2000 application.
A foobar2000 component is a Windows Dynamic Link Library (DLL), or a Mac OS bundle with "component" extension, 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.
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, <code>foobar2000_get_interface()</code>, 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.
Each component has a single exported DLL function, <code>foobar2000_get_interface()</code>, called on startup to initialize and discover entrypoint services of your component. This function is generated automatically by the foobar2000_component_client module. Do not implement it yourself.
 
foobar2000 components are made using [https://www.foobar2000.org/SDK foobar2000 SDK] and [https://visualstudio.microsoft.com Microsoft Visual Studio] or [https://developer.apple.com/xcode Xcode].
 
See also: [[Foobar2000:Development:Visual_Studio_Compatibility|Visual Studio Compatibility]], [[Foobar2000:Development:SDK Contents|SDK Contents]].


== Packaging a component ==
== Packaging a component ==
Line 11: Line 17:
Components are delivered to the user in form of fb2k-component file.
Components are delivered to the user in form of fb2k-component file.


This is simply a renamed zip of your DLL.
In old 32-bit foobar2000, this was originally a renamed zip of a component 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 ==
In modern foobar2000, fb2k-component archive can contain multiple subdiretories with payloads for different processor architectures or operating systems.


Register a forum account and contact Peter with info about your component:
If your component requires additional files, include them in the zip; foobar2000 will extract them to the folder containing your component DLL upon installation.
https://hydrogenaud.io/index.php?action=profile;u=84


== Services ==
=== Different architectures ===
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 <code>FB2K_MAKE_SERVICE_INTERFACE() / FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT()</code> macro to implement standard service behaviors for the class; additionally, class_guid needs to be defined outside class declaration (e.g. <code>const GUID someclass::class_guid = {….};</code> ). Note that most of components will not declare their own service types, they will only implement existing ones declared in the SDK.
Delivering as straightforward zip-of-a-DLL still works, but that leaves it up to the user to download the correct binary.


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.
To support multiple CPU architectures with one package, put files meant for legacy x86 foobar2000 in top level and create subdirectories named after the architectures, with files that are specialized for those architectures.  


You cannot directly instantiate a service implementation object because it's still missing virtual methods of service_base.
For an example, x64 foobar2000 will extract whole component archive, then overwrite files in root with those in x64 subfolder:


<pre>
<pre>
class myworker : public threaded_process_callback { /* threaded_process_callback methods overridden here */ };
foo_sample.dll  <= this is for legacy x86 foobar2000
auto obj = new myworker; // error
x64\foo_sample.dll  <= for x64 and arm64ec
auto obj = fb2k::service_new<myworker>(); // good
arm64ec\foo_sample.dll  <= arm64ec foobar2000 will prefer this over x64 if provided
service_ptr_t<myworker> obj = new service_impl_t<myworker>; // longer equivalent of fb2k::service_new<>, commonly used in older code, not auto-friendly
mac\foo_sample.component  <= Mac OS bundle
</pre>
</pre>


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.
Components packaged this way will let foobar2000 choose the right thing to load.


Note that service_base methods:
The components site now accepts such components and detects supported CPU architectures by listing zip contents.
* <code>service_add_ref()</code>
* <code>service_release()</code>
* <code>service_query()</code>
... are only intended for autopointer classes and should never be called directly in your code.


== Entrypoint services ==
=== Windows ARM support ===
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.
foobar2000 for ARM is built in ARM64EC (Emulation Compatible) mode.


Registered entrypoint services can be accessed using:
Because ARM64 Windows is still a niche platform, we cannot expect every developer to compile and test ARM64 components. foobar2000 for ARM64 is capable of seamlessly loading x64 components and transparently running them side by side with native components.
 
* For services types with variable number of implementations registered:
* <code>enumerate()</code> static methods of the service class>
<pre>
for( auto ptr : someclass::enumerate() ) { /* do stuff with ptr */ }
</pre>
* Or, <code>service_enum_t<></code>, commonly used in older code:
<pre>
service_enum_t<someclass> e; service_ptr_t<someclass> ptr; while(e.next(ptr)) ptr->dosomething();
</pre>
* For services types with a single always-present implementation registered - such as core services like playlist_manager - using someclass::get(), e.g.:
<pre>
auto api = someclass::get(); api->dosomething(); api->dosomethingelse();
</pre>


Using per-service-type defined static helper functions, e.g. <code>someclass::g_dosomething()</code> - those use relevant service enumeration methods internally.
For non-performance-critical components, emulation of verified working x64 code is more than good enough, likely better than running untested ARM64 code if the developer has no means to test it.
An entrypoint service type must use <code>FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT()</code> macro to implement standard entrypoint service behaviors, as opposed to all other service types that use <code>FB2K_MAKE_SERVICE_INTERFACE()</code> macro instead.


You can register your own entrypoint service implementations using <code>FB2K_SERVICE_FACTORY()</code> macro. It creates a static instance of your object and makes it available for everyone else to enumerate and access.
See also: [[Foobar2000:Foobar2000 for ARM]]
Some services need to be registered differently, see documentation of individual services for details.


A typical entrypoint service implementation looks like this:
== Getting a component listed on foobar2000.org/components ==
<pre>
class myinitquit : public initquit {
public:
void on_init() override {};
void on_quit() override {};
};
FB2K_SERVICE_FACTORY(myinitquit);
</pre>
 
 
== Auto pointers ==
When performing most kinds of service operations, <code>service_ptr_t<T></code> template should be used rather than working with service pointers directly; it automatically manages reference counter calls, ensuring that the service object is deleted when it is no longer referenced.
 
For convenience, all service classes have <code>myclass::ptr</code> typedef'd to <code>service_ptr_t<myclass></code>.
 
When working with pointers to core fb2k services, just use C++11 auto keyword and <code>someclass::get()</code>, e.g. <code>auto myAPI = playlist_manager::get();</code>
 
service_ptr_t<> provides additional convenience operators:
* <code>operator&=</code>
Attempts to obtain a pointer to another interface, if the passed object supports it. Returns true on success, false on failure.
 
<pre>
service_ptr_t<service1> A;
service_ptr_t<service2> B;
A = create_service1();
if ( B &= A ) {
  // do stuff with B
} else {
  // A does not implement service2, or is null
}
</pre>
 
* <code>operator^=</code>
Forced cast. Use to obtain a valid pointer to another interface in a scenario where you know that the object supports it.
 
There's no error return, the process crashes if the interface you ask for is not implemented by this object.
 
<pre>
service_ptr_t<service1> A;
service_ptr_t<service2> B;
A = create_service1();
B ^= A;
// if you get here, B is valid (or null if A was null)
</pre>
 
Use the above operators to safely cast from parent service class to derived service class. Do not use C++ <code>dynamic_cast<> / static_cast<> / reinterpret_cast<></code> on foobar2000 service classes.
 
 
== Exception use ==
Most of API functions use C++ exceptions to signal failure conditions. All used exception classes must derive from <code>std::exception</code>; this design allows various instances of code to use single <code>catch()</code> 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 <code>exception_io</code> 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 <code>playlist_callback</code>, <code>playback_callback</code> or <code>library_callback</code> 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 callsed from any 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 <code>main_thread_callback</code> service.
 
Instead of using <code>main_thread_callback</code> directly, use <code>fb2k::inMainThread()</code> 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, <code>fb2k::inMainThread()</code> 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 <code>abort_callback&</code> parameter to allow you to cancel them anytime.
 
When implementing such function, pass caller's <code>abort_callback&</code> 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 <code>abort_callback</code> will become set and the next function to poll it will throw <code>exception_aborted</code>, which then should be handled by the context that owns the <code>abort_callback</code> object.
 
If you must call a function taking <code>abort_callback&</code> and have no means to abort it, pass <code>fb2k::noAbort</code>, the shared dummy <code>abort_callback</code> 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 <code>cfg_var</code> class. Those can be either predefined classes (<code>cfg_int</code>, <code>cfg_string</code>, etc) or your own classes implementing relevant methods.
 
Each <code>cfg_var</code> 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 <code>cfg_var</code> constructor).
 
Note that <code>cfg_var</code> 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: global callbacks ==
 
Multiple service classes presented by the SDK allow your component to receive notifications about various events.
We'll refer to these classes as global callbacks.
 
Essential examples of global callbacks:
* <code>file_operation_callback</code> - tracking file move/copy/delete operations.
* <code>library_callback</code> - tracking Media Library content changes.
* <code>metadb_io_callback</code> - tracking tag read / write operations altering cached/displayed media information.
* <code>play_callback</code> - tracking playback related events.
* <code>playback_statistics_collector</code> - collecting information about played tracks.
* <code>playlist_callback</code>, <code>playlist_callback_single</code> - tracking playlist changes (the latter tracks only active playlist changes).
* <code>playback_queue_callback</code> - tracking playback queue changes.
* <code>titleformat_config_callback</code> - tracking changes of title formatting configuration.
* <code>ui_drop_item_callback</code> - filtering items dropped into the UI.
All of global callbacks operate only within main app thread, allowing easy cooperation with your user interface - for an example, you perform playlist view window repainting directly from your <code>playlist_callback</code> implementation.
 
== Global callback recursion issues ==


There are restrictions on things that are legal to call from within global callbacks. For an example, you can't modify a playlist from inside a playlist callback, because there are other registered callbacks tracking playlist changes that haven't been notified about the change being currently processed yet.
Register a forum account and contact [https://hydrogenaud.io/index.php?action=profile;u=84 Peter] with info about your component.


You must not enter modal message loops from inside global callbacks, as those allow any unrelated code (queued messages, user input, etc.) to be executed, without being aware that a global callback is being processed. Certain global API methods such as <code>metadb_io::load_info_multi</code> or <code>threaded_process::run_modal</code> enter modal loops when called. Use <code>main_thread_callback</code> service to avoid this problem and delay execution of problematic code.
== Further reading ==


You should also avoid firing a cross-thread <code>SendMessage()</code> inside global callbacks as well as performing any operations that dispatch global callbacks when handling a message that was sent through a cross-thread <code>SendMessage()</code>. Doing so may result in rare unwanted recursions - <code>SendMessage()</code> call will block the calling thread and immediately process any incoming cross-thread <code>SendMessage()</code> messages. If you're handling a cross-thread <code>SendMessage()</code> and need to perform such operation, delay it using <code>PostMessage()</code> or main_thread_callback.
* [[Foobar2000:Development:Auto Pointers|Auto Pointers]]
* [[Foobar2000:Development:Debugging|Debugging]]
* [[Foobar2000:Development:Exceptions|Exceptions]]
* [[Foobar2000:Development:Global Callbacks|Global Callbacks: tracking foobar2000 lifetime events]]
* [[Foobar2000:Development:SDK Contents|SDK Contents]]
* [[Foobar2000:Development:Services|Services]]
* [[Foobar2000:Development:Storing Configuration|Storing Configuration]]
* [[Foobar2000:Development:Threads|Threads]]
* [[Foobar2000:Development:Visual_Studio_Compatibility|Visual Studio Compatibility]]

Latest revision as of 10:52, 8 September 2023

What is a component

A foobar2000 component is a Windows Dynamic Link Library (DLL), or a Mac OS bundle with "component" extension, 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 of your component. This function is generated automatically by the foobar2000_component_client module. Do not implement it yourself.

foobar2000 components are made using foobar2000 SDK and Microsoft Visual Studio or Xcode.

See also: Visual Studio Compatibility, SDK Contents.

Packaging a component

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

In old 32-bit foobar2000, this was originally a renamed zip of a component DLL.

In modern foobar2000, fb2k-component archive can contain multiple subdiretories with payloads for different processor architectures or operating systems.

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

Different architectures

Delivering as straightforward zip-of-a-DLL still works, but that leaves it up to the user to download the correct binary.

To support multiple CPU architectures with one package, put files meant for legacy x86 foobar2000 in top level and create subdirectories named after the architectures, with files that are specialized for those architectures.

For an example, x64 foobar2000 will extract whole component archive, then overwrite files in root with those in x64 subfolder:

foo_sample.dll   <= this is for legacy x86 foobar2000
x64\foo_sample.dll   <= for x64 and arm64ec
arm64ec\foo_sample.dll   <= arm64ec foobar2000 will prefer this over x64 if provided
mac\foo_sample.component  <=  Mac OS bundle

Components packaged this way will let foobar2000 choose the right thing to load.

The components site now accepts such components and detects supported CPU architectures by listing zip contents.

Windows ARM support

foobar2000 for ARM is built in ARM64EC (Emulation Compatible) mode.

Because ARM64 Windows is still a niche platform, we cannot expect every developer to compile and test ARM64 components. foobar2000 for ARM64 is capable of seamlessly loading x64 components and transparently running them side by side with native components.

For non-performance-critical components, emulation of verified working x64 code is more than good enough, likely better than running untested ARM64 code if the developer has no means to test it.

See also: Foobar2000:Foobar2000 for ARM

Getting a component listed on foobar2000.org/components

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

Further reading