A Practical Guide to Plugin Architecture

Table of Contents

A Practical Guide to Plugin Architecture

Have you ever cranked up the bass on your favorite song, or fine-tuned the treble to make the vocals really pop? If you’ve used a music player on your phone, computer, or even in your car, you’ve almost certainly interacted with an audio equalizer to apply different audio effects of your choice.

Here’s a thought: You’ve probably enabled or disabled an equalizer effect without even pausing the song. How does the application let you do that?

To answer that, we need to dive into the fascinating world of plugin architecture using our familiar audio equalizer as a guide. We will understand these flexible and extensible systems using demonstrations of simple core mechanisms in C++ that allow the main program to “plug in” external pieces of code. By the end of this read, you will have opened the door to the world of extensible plugin-based applications for you to experiment and build yourself.

Key Concepts

Before we dive further, we need to introduce three core ideas that make plugin architecture possible for any application.

1. Interfaces: The Universal Connector

This acts as a ‘contract’ or blueprint, specifying the functions that every plugin must implement. The interface itself doesn’t do anything; it only specifies what needs to be done.

2. Dynamic Loading: On-The-Fly Loader

This lets the core application discover and load plugins at runtime. Instead of building all plugin codes directly into your main program, plugins are compiled into separate files (like .dlls on Windows or .so files on Linux/macOS).

3. Factory Functions: The Plugin’s Self-Introduction

Each plugin needs a way to introduce itself to the core application and create an instance of its own kind. A factory function is a special, publicly accessible function within a plugin that allows a core application to obtain an instance of the plugin.

With these three ideas introduced, we can now dig deeper into how each idea is used within the context of a practical application.

The Plugin Contract

Imagine you’re designing a universal headphone jack. Every headphone (the plugin) needs to have the exact same shape as the plug to fit into your device (the core application). This “shape” is defined by an interface. In C++, this is done by using an abstract class.

An abstract class is not a fully built object, but a blueprint that lays out the functions any plugin must implement. This ensures that regardless of who develops the plugin, the core application can interact with it reliably, enabling a seamless “plug and play” experience. The most critical part of this contract is usually a pure virtual function.

 

The Plugin Contract

Let us look at a code snippet that implements an interface – IAudioEffectPlugin.

 

Considering the processAudio function, this line has two powerful keywords.

1. virtual: This keyword tells us that this function is meant to be overridden by classes that are inherited from IAudioEffectPlugin.

Think of it like this: your interface says, “Every plugin will have a processAudio function.” The virtual keyword allows each plugin to define its own unique way of processing audio, while still conforming to the requirement that such a function must exist.

2. = 0: This is the “magic” part that makes the function pure virtual, and consequently, makes IAudioEffectPlugin an abstract class. Equating 0 tells the C++ compiler broadly two things about the virtual function we created.

  • This function has no implementation here in the IAudioEffectPlugin itself.
  • Any class that inherits from IAudioEffectPlugin must provide its own concrete implementation for processAudio.

If a class tries to inherit IAudioEffectPlugin without its own processAudio function, that class itself becomes abstract restricting any objects to be created from it. It’s the ultimate enforcement of our plugin contract.

Together, these two keywords ensure that any plugin loaded onto the application always has a processAudio function, guaranteeing our music player can always tell a loaded plugin to do its job.

The Plugin Itself

Now that we have our universal headphone jack (IAudioEffectPlugin), it’s time to build a specific headphone that fits it perfectly. Let’s call our first plugin GainPlugin. This plugin’s sole purpose is to take in the audio data, make the overall volume louder, and then send the modified sound forward.

To make our GainPlugin compatible with our music player, it must follow the rules defined in our interface. In C++, this is achieved through inheritance. Our GainPlugin class inherits the IAudioEffectPlugin and implements all its defined pure virtual functions.

 

The Plugin Itself

 

1. class GainPlugin : public IAudioEffectPlugin: This line declares our GainPlugin and tells the C++ compiler that it publicly inherits the IAudioEffectPlugin interface and adheres to the contract defined by it.

2. void processAudio(float* audioData, int numSamples) override { … }: This is the concrete implementation for the processAudio function. Remember, our IAudioEffectPlugin only says that there is a processAudio function. The GainPlugin implements that processAudio with its own algorithm.

NOTE: The override keyword here lets the compiler know that the GainPlugin intends to override the processAudio virtual function from the interface. This avoids subtle bugs and ensures you’re correctly fulfilling the interface contract.

3. getName: Lastly, this function lets the main application ask any loaded plugin its name.Our GainPlugin is a fully compliant “headphone” that fits into our universal “jack.” It’s a self-contained unit ready to be loaded by our music player.

The Factory

We’ve built our GainPlugin that perfectly implements our IAudioEffectPlugin contract. But how does our main music player actually get an instance of this plugin?

This is where the factory function comes in. When our music player wants a new plugin, it doesn’t try to directly create a GainPlugin object. Instead, it asks the plugin, “Hey, can you give me an instance of your plugin?” The factory functions know how to create that specific plugin object and hand it back to the core application. This keeps the system much more flexible and loosely coupled with the core application.

 

The Factory

 

Let’s look at a typical factory function that would be added after the GainPlugin class implementation in the same file as seen in the previous section.

Let’s elaborate a few things now:

1. extern “C”: This might look a bit strange if you’re new to C++, but it’s very important! This is required to stop “name-mangling” a function that C++ compilers automatically do to support features like function overloading. When extern “C” is used, the core application can easily find and call any functions from the dynamically created .dll and .so files simply by using their exact names.

2. createPlugin: This is our factory function. When the main application loads our plugin file, it looks specifically for a function named createPlugin. This function’s job is simply to create a new GainPlugin object and then return it to the main application.

Notice that it returns a new pointer to IAudioEffectPlugin. This is important: the main application only cares that it’s getting an object that conforms to the IAudioEffectPlugin interface, not the specific GainPlugin itself.

3. destroyPlugin: Just as we create a plugin, we also need a way to properly destroy it and free up the memory it used. This function provides a clean way for the main application to tell the plugin: “I’m done with this object, please clean it up.”

With these factory functions, our plugin now has a standard, well-defined way to introduce itself and be managed by any application that knows the IAudioEffectPlugin interface.

The Main Application

This is where all the pieces come together, and the true power of plugins becomes clear. Our core music player doesn’t need to know anything about the GainPlugin that we created above. Instead, it uses special system functions to dynamically load the plugin from a separate file (like .dll on Windows or .so on Linux/macOS) while the program is already running. It’s just like when plugging in a new pair of headphones; you don’t need to restart your entire device to use it. The system recognizes it automatically.

 

Block diagram which shows the complete plugin architecture

Figure 1: Block diagram which shows the complete plugin architecture. Main application only needs to use the interface to call the functions defined in Gain Plugin.

 

The Main Application

Conceptually, the main application’s code for loading a plugin looks something like this.

Please note that the real application needs implementation on the following:

  • Equivalent call for LoadLibrary based on Windows or Linux platforms.
  • A new function called GetPluginFactory to load the plugin .dll/.so files onto application memory for plugin management.
  • Read samples of audio data (someAudioData) to send to processAudio.

Once loaded up, our application gets a new instance of the GainPlugin, treating it simply as an IAudioEffectPlugin. From that moment on, the application can send the audio data to the processAudio of this loaded plugin, and send the enhanced sound to the user, all without having had any prior knowledge of the GainPlugin’s existence.

Conclusion

Just like that, you’ve grasped the core of the plugin architecture. By seeing the fundamental C++ concepts in action, you now know how interfaces create contracts, how dynamic loading plugs in new codes, and how factory functions bring plugins to life.

Many of the powerful, customizable applications we use every day are built on a plugin architecture — from web browsers and IDEs to content management systems and even game engines. Understanding plugins empowers us to build more flexible software where applications can grow and adapt without needing complete overhauls.

At eInfochips, we help develop truly dynamic, robust, plugin-driven software solutions that provide exceptional flexibility and scalability, perfectly adapting to the evolving user needs of this world.

Know More: Embedded Software Development Services

Picture of Dibyajyoti Samal

Dibyajyoti Samal

Dibyajyoti Samal is currently a senior engineer at eInfochips primarily focusing on embedded software development in the audio processing domain. Having about 4 years of experience, he has worked on a variety of activities ranging from application development to device driver development. Dibyajyoti graduated with his Masters in Embedded System Design from BITS Pilani. In his free time, he likes to make music and play video games.

Explore More

Talk to an Expert

Subscribe
to our Newsletter
Stay in the loop! Sign up for our newsletter & stay updated with the latest trends in technology and innovation.

Download Sample Report

Download Brochure

Start a conversation today

Schedule a 30-minute consultation with our Automotive Solution Experts

Start a conversation today

Schedule a 30-minute consultation with our Battery Management Solutions Expert

Start a conversation today

Schedule a 30-minute consultation with our Industrial & Energy Solutions Experts

Start a conversation today

Schedule a 30-minute consultation with our Automotive Industry Experts

Start a conversation today

Schedule a 30-minute consultation with our experts

Please Fill Below Details and Get Sample Report

Reference Designs

Our Work

Innovate

Transform.

Scale

Partnerships

Device Partnerships
Digital Partnerships
Quality Partnerships
Silicon Partnerships

Company

Products & IPs

Privacy Policy

Our website places cookies on your device to improve your experience and to improve our site. Read more about the cookies we use and how to disable them. Cookies and tracking technologies may be used for marketing purposes.

By clicking “Accept”, you are consenting to placement of cookies on your device and to our use of tracking technologies. Click “Read More” below for more information and instructions on how to disable cookies and tracking technologies. While acceptance of cookies and tracking technologies is voluntary, disabling them may result in the website not working properly, and certain advertisements may be less relevant to you.
We respect your privacy. Read our privacy policy.