First Plugin

Learn how to create your first plugin with the C++ language module, including basic syntax and setup.

Welcome to the Plugify C++ Language Module Plugin Development Guide. This guide will walk you through the process of creating your first plugin using C++ within the Plugify framework. Whether you’re a game modder, software engineer, or plugin developer, this tutorial will help you understand the fundamental concepts and steps necessary to build a fully functional plugin.

What is Plugify?

Plugify is a modular plugin framework that enables developers to extend applications by integrating external plugins dynamically. It provides a structured approach to plugin development with clear guidelines on plugin structure, dependency management, and API exposure.

Why Use the C++?

The C++ Language Module allows developers to create high-performance plugins using C++. By leveraging dynamic libraries (shared objects), developers can extend applications while maintaining efficiency and flexibility. Plugify ensures that your plugin integrates smoothly with the core framework by following a standardized plugin structure and API.

What You’ll Learn

In this guide, you will:

  1. Set up the directory structure for your plugin.
  2. Define a plugin manifest (.pplugin file) to register your plugin in the Plugify ecosystem.
  3. Write the C++ code for your plugin using the provided API.
  4. Manage dependencies to ensure correct plugin initialization.
  5. Compile and package your plugin using CMake.

By the end of this tutorial, you’ll have a working C++ plugin that can be loaded into the Plugify framework. Let’s get started!

Directory Structure

To ensure seamless integration with the Plugify framework, your plugin must follow a specific directory structure. Each plugin should be placed inside its own folder within the plugins/ directory. The folder name must match the plugin’s name and follow these rules:

  1. Allowed Characters: Alphanumeric (A-Z, a-z, 0-9), special characters ($, #, @, -).
  2. Spaces are NOT allowed in the folder name.
  3. The .pplugin configuration file must have the same name as the plugin folder.

Example Directory Layout

Breakdown of the Structure

  • res/plugins/ – The main directory where all plugins are stored.
  • plugin_name/ – Each plugin has its own dedicated folder. The folder name must match the .pplugin file name.
  • bin/ – This subfolder contains the compiled plugin binaries (.dll for Windows, .so for Linux, ect.).
  • plugin_name.pplugin – The configuration file that defines metadata about the plugin.

By following this structure, Plugify can correctly detect, load, and manage plugins across different platforms.

The Plugin Manifest

Each plugin in the Plugify framework requires a manifest file with the .pplugin extension. This file is a JSON-based configuration that provides essential metadata about the plugin, ensuring that it can be properly identified, loaded, and managed.

Key Responsibilities of the Manifest File:

  • Defines the plugin version and author details.
  • Specifies the entry point for execution.
  • Lists dependencies required by the plugin.
  • Declares exported methods available for external interaction.

Example of a Manifest File

plugin_name.pplugin
{
  "$schema": "https://raw.githubusercontent.com/untrustedmodders/plugify/refs/heads/main/schemas/plugin.schema.json",
  "fileVersion": 1,
  "version": "0.1.0",
  "friendlyName": "PluginCPP",
  "description": "An example of a plugin. This can be used as a starting point when creating your own plugin.",
  "createdBy": "untrustedmodders",
  "createdByURL": "https://github.com/untrustedmodders/",
  "docsURL": "https://github.com/orgs/untrustedmodders/README.md",
  "downloadURL": "https://github.com/orgs/untrustedmodders/example-repo.zip",
  "updateURL": "https://github.com/untrustedmodders/plugify/issues",
  "entryPoint": "bin/example_plugin",
  "supportedPlatforms": [],
  "languageModule": {
    "name": "cpp"
  },
  "dependencies": [],
  "exportedMethods": []
}

Key Fields Explained

  • entryPoint: Specifies the location of the compiled plugin binary.
  • languageModule: Should be set to cpp for C++ plugins.
  • dependencies: Lists other required plugins, ensuring correct load order.
  • exportedMethods: Functions exposed by the plugin for external interaction.

Why is the Manifest File Important?

  • Ensures Compatibility – Defines supported versions and platforms.
  • Enables Modularity – Lists dependencies for structured plugin loading.
  • Facilitates Integration – Allows other plugins to call exposed methods.

By following this manifest structure, Plugify can efficiently load and manage plugins, ensuring seamless functionality across different projects.

Writing the Plugin Code

Creating a plugin for Plugify is straightforward. You can either use the pre-built C++ plugin template available in our repository or write your plugin from scratch.

Using the Plugin Template

The easiest way to get started is by downloading the C++ plugin template from our repository. It contains all the necessary files, including:

  • A preconfigured build system
  • A sample implementation
  • The required Plugify headers

Just clone the repository, and your environment will be ready for development.

Writing a Plugin from Scratch

If you prefer to build your plugin manually, follow these steps:

Setting Up Your Plugin

Each plugin must implement the IPluginEntry interface and expose the required methods for Plugify to recognize and execute it.

Plugin Code Structure

Here is a basic example of a C++ plugin implementation:

plugin.cpp
#include <iostream>
#include <plugify/cpp_plugin.hpp>
#include <plugin_export.h>

class ExamplePlugin : public plg::IPluginEntry {
public:
    void OnPluginStart() override {
        std::cout << "Example Start!" << std::endl;
    }
    
    void OnPluginUpdate(float dt) override {
        std::cout << "Example Update!" << std::endl;
    }
    
    void OnPluginEnd() override {
        std::cout << "Example End!" << std::endl;
    }
} g_examplePlugin;

EXPOSE_PLUGIN(PLUGIN_API, &g_examplePlugin)

Understanding Plugin Lifecycle Methods

Each plugin can define the following lifecycle methods, which Plugify will call at specific times:

MethodDescriptionRequired?
Plugify_InitInitializes the plugin and sets up required resources.✅ Yes
Plugify_StartCalled when the plugin is loaded and ready to run.❌ Optional
Plugify_Update(float dt)Called every frame, allowing periodic updates.❌ Optional
Plugify_EndCalled when the plugin is unloaded or shuts down.❌ Optional

Using the Expose Macro

To register your plugin with Plugify, you must use the EXPOSE_PLUGIN macro. This macro:

  • Defines the plugin entry point.
  • Connects your class instance (g_examplePlugin) to the Plugify system.
  • Ensures that Plugify_Init, Plugify_PluginStart, Plugify_PluginUpdate, and Plugify_PluginEnd are correctly exposed.

Including the Plugify Headers

To simplify plugin development, Plugify provides a set of header files in the C++ language module repository. These files can be found in: includes/plugify/

They contain essential definitions and utilities required to interact with Plugify.

cpp_plugin.hpp header file provides core functionality for writing plugins. It includes:

  • Plugin lifecycle management (OnPluginStart, OnPluginUpdate, etc.).
  • Plugin metadata access (GetName(), GetVersion(), etc.).
  • Plugin resource handling (FindResource(), ect.).

Summary

  • Use the C++ plugin template to get started quickly.
  • Implement the IPluginEntry interface to define plugin behavior.
  • Expose your plugin using the EXPOSE_PLUGIN macro.
  • Use the Plugify headers from the includes/plugify/ folder to access necessary utilities.

By following these steps, you’ll have a fully functional Plugify plugin ready to run!

Dependency Management

Dependency management in Plugify ensures that plugins are loaded in the correct order based on their dependencies. The system uses Topological Sorting to determine the appropriate sequence, preventing initialization issues when plugins rely on other plugins.

How Dependencies Work

Each plugin can declare its dependencies inside the dependencies field of its plugin manifest (.pplugin file). The Plugify core will:

  • Analyze the dependencies listed in each plugin's manifest.
  • Sort the plugins using Topological Sorting, ensuring dependencies are loaded before dependent plugins.
  • Validate platform compatibility and requested versions.

Dependency Representation

Dependencies are declared using the following JSON format inside the dependencies field of the plugin manifest:

plugin_name.pplugin
"dependencies": [
    {
        "name": "polyhook",
        "optional": false,
        "supportedPlatforms": ["windows", "linux"],
        "requestedVersion": "0.1.0"
    }
]

Explanation of Fields

FieldTypeRequired?Description
namestring✅ YesThe unique name of the dependency plugin.
optionalboolean❌ No (default: false)If true, the plugin can still load even if the dependency is missing.
supportedPlatformsarray of string❌ NoSpecifies the platforms the dependency supports (e.g., windows, linux).
requestedVersioninteger❌ NoSpecifies a required version of the dependency. If omitted, any compatible version is allowed.

Example Plugin with Dependencies

Here’s an example plugin manifest (.pplugin) that declares multiple dependencies:

plugin_name.pplugin
{
  "version": "1.0.0",
  "friendlyName": "MyPlugin",
  "entryPoint": "bin/my_plugin",
  "dependencies": [
    {
      "name": "polyhook",
      "optional": false,
      "supportedPlatforms": ["windows", "linux"],
      "requestedVersion": "2.0.0"
    },
    {
      "name": "json-parser",
      "optional": true
    }
  ]
}

In this example:

  • polyhook is mandatory and must be version 2.0.0.
  • json-parser is optional, meaning the plugin will still load even if it's missing.

How Plugify Uses Dependencies

  1. Dependency Validation
    • If a mandatory dependency is missing, the plugin won't load.
    • If an optional dependency is missing, the plugin continues to load.
  2. Version Matching
    • If requestedVersion is set, Plugify will ensure the dependency meets or exceeds that version.
    • If requestedVersion is omitted, Plugify will load any available version of the dependency.
  3. Platform Compatibility
    • If supportedPlatforms is set, Plugify will check if the current OS is supported.
    • If the platform is unsupported, the dependency won't be loaded.

Key Takeaways

  1. Ensure mandatory dependencies are available before loading your plugin.
  2. Use optional: true for dependencies that enhance functionality but aren’t critical.
  3. Specify supportedPlatforms if a dependency isn’t cross-platform.
  4. Define requestedVersion if your plugin requires a specific version of a dependency.

By properly managing dependencies, your plugin will load efficiently and avoid unexpected failures due to missing or incompatible dependencies.

Building the Plugin with CMake

To build your plugin, you need a build system that compiles the source code into a shared library (.dll on Windows, .so on Linux, ect.). We use CMake as our build system due to its flexibility and cross-platform support.

You can use any modern IDE that supports CMake, such as:

Setting Up CMake

Create a CMakeLists.txt file in your project directory with the following content:

CMakeLists.txt
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

if(POLICY CMP0092)
    cmake_policy(SET CMP0092 NEW) # Don't add -W3 warning level by default.
endif()

project(example_plugin VERSION 1.0.0.0 
    DESCRIPTION "C++ Example Plugin" 
    HOMEPAGE_URL "https://github.com/untrustedmodders/plugify-module-cpp/test/example_plugin" 
    LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

#
# Plugin Source Files
#
set(PLUGIN_SOURCES "plugin.cpp")

add_library(${PROJECT_NAME} SHARED ${PLUGIN_SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

# Compiler-specific warning flags
if(MSVC)
    target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
else()
    target_compile_options(${PROJECT_NAME} PRIVATE -Wextra -Wshadow -Wconversion -Wpedantic -Werror)
endif()

# Ensure compatibility on Linux
if(UNIX AND NOT APPLE)
    target_link_libraries(${PROJECT_NAME} PRIVATE -static-libstdc++ -static-libgcc)
endif()

target_compile_definitions(${PROJECT_NAME} PRIVATE
    PLUGIFY_IS_DEBUG=$<STREQUAL:${CMAKE_BUILD_TYPE},Debug>)

include(GenerateExportHeader)
generate_export_header(${PROJECT_NAME} EXPORT_MACRO_NAME PLUGIN_API EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/plugin_export.h)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/exports)

Building the Plugin

Using the Command Line (Windows/Linux/macOS)

mkdir build
cd build
cmake ..
cmake --build . --config Release
  • On Windows, the compiled .dll file will be in the Release/ folder.
  • On Linux/macOS, the compiled .so file will be in build/.

Using Visual Studio 2022

  1. Open Visual Studio 2022
  2. Click Open Folder and select your plugin directory
  3. Visual Studio will automatically detect CMakeLists.txt
  4. Set the CMake configuration to Release
  5. Click Build → Build All

Using CLion

  1. Open CLion
  2. Open your CMakeLists.txt project
  3. Click Build
  4. Your shared library will be generated in the output directory

Key Takeaways

  1. CMake simplifies cross-platform plugin development
  2. You can use Visual Studio, CLion, or any CMake-compatible IDE
  3. Use cmake --build . to compile your plugin from the command line

Important Note for Linux Plugins

For some games, we recommend linking the C++ Standard Library (STL) statically on Linux. This helps avoid potential issues with memory allocators, which can occur when a plugin dynamically links to a different version of the C++ runtime than the game engine itself.

Static Linking in CMake

To ensure static linking of the STL, modify your CMakeLists.txt as follows:

CMakeLists.txt
if(UNIX AND NOT APPLE)
    target_link_libraries(${PROJECT_NAME} PRIVATE -static-libstdc++ -static-libgcc)
endif()

Why Static Linking?

  • Prevents conflicts between different versions of the STL that the game and your plugin might use.
  • Avoids memory allocator mismatches, which can cause crashes or unexpected behavior.
  • Ensures compatibility across different Linux distributions, even if they have different C++ runtimes.

This is particularly important for game engines that heavily rely on memory allocation optimizations.

Running and Testing the Plugin

Once you have built your plugin, the next step is to run and test it within the Plugify system. To do this, follow these steps:

Placing the Plugin in the Correct Directory

Ensure your plugin is correctly structured inside the plugins/ folder. Your plugin directory should contain:

Once the plugin folder and manifest file are correctly placed, Plugify will automatically detect and attempt to load the plugin.

Verifying Plugin Load Status

You can check if your plugin loaded successfully by using terminal commands provided by Plugify. These commands allow you to query the status of plugins and troubleshoot potential issues.

  • List all loaded plugins:
plg plugins

This will display all currently loaded plugins. If your plugin is listed, it means Plugify successfully recognized and initialized it.

  • Query specific plugin information:
plg plugin example_plugin

This command retrieves detailed information about a specific plugin, including its version, dependencies, and supported platforms.

Handling Plugin Load Failures

If your plugin manager fails to load, Plugify will provide error messages in the console. You can also explicitly query the plugins status using:

plg list

This command will show whether the plugin failed to initialize and, if so, provide a reason (e.g., missing dependencies, incorrect entry point, or runtime errors).

Debugging Issues

If your plugin isn't working as expected:

  • Check the console logs for detailed error messages.
  • Ensure all dependencies are properly installed and compatible with the game/platform.
  • Verify the entry point in the .pplugin manifest matches the actual plugin binary location.
  • Enable debug logging if necessary, to get more details about the issue.

Once your plugin loads and functions correctly, it's ready for use! Would you like additional information on debugging techniques?

Conclusion

This guide covered the essential steps to create a C++ plugin for C++ Language Module, including setting up the project, writing the plugin code, configuring the manifest, and building the plugin using CMake. Following these guidelines ensures smooth integration into the Plugify ecosystem.