Export Functions

Guide to export functions from your C++ plugin to be used by other language modules within Plugify.

In the Plugify ecosystem, C++ plugins can export functions to make them accessible to other plugins. This guide explains how to export functions in C++ and provides examples to help you integrate your plugins seamlessly.

Basic Type Mapping

The following table lists how types are exposed to the C++ API:

C++ TypePlugify AliasRef Support ?
voidvoid
boolbool
charchar8
char16_tchar16
int8_tint8
int16_tint16
int32_tint32
int64_tint64
uint8_tuint8
uint16_tuint16
uint32_tuint32
uint64_tuint64
uintptr_tptr64
uintptr_tptr32
floatfloat
doubledouble
void*function
plg::stringstring
plg::anyany
plg::vector<bool>bool[]
plg::vector<char>char8[]
plg::vector<char16_t>char16[]
plg::vector<int8_t>int8[]
plg::vector<int16_t>int16[]
plg::vector<int32_t>int32[]
plg::vector<int64_t>int64[]
plg::vector<uint8_t>uint8[]
plg::vector<uint16_t>uint16[]
plg::vector<uint32_t>uint32[]
plg::vector<uint64_t>uint64[]
plg::vector<uintptr_t>ptr64[]
plg::vector<uintptr_t>ptr32[]
plg::vector<float>float[]
plg::vector<double>double[]
plg::vector<plg::string>string[]
plg::vector<plg::any>any[]
plg::vector<plg::vec2>vec2[]
plg::vector<plg::vec3>vec3[]
plg::vector<plg::vec4>vec4[]
plg::vector<plg::mat4x4>mat4x4[]
plg::vec2vec2
plg::vec3vec3
plg::vec4vec4
plg::mat4x4mat4x4

Exporting Functions in C++

To export a function in a C++ plugin, you need to ensure that the function is visible to other plugins. This is typically done by marking the function with the PLUGIN_API macro, which ensures the function is exported when the plugin is compiled as a dynamic link library (DLL).

Key Points

  • Static Functions: Exported functions should generally be static to avoid requiring an object instance for invocation.
  • C Linkage: The PLUGIN_API macro does not automatically include extern "C" to prevent name mangling. We need ensure the function can be found just by name by adding extern "C".
  • Parameter and Return Types: Use Plugify's native types (e.g., plg::string, plg::vector) for seamless integration.

Generating the PLUGIN_API Macro

The PLUGIN_API macro is generated using CMake. Here’s how you can set it up in your CMakeLists.txt file:

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

This generates a header file (module_export.h) that defines the PLUGIN_API macro. Include this header in your plugin source files to mark functions for export.

Basic Example

Here’s a simple example of exporting a function in a C++ plugin:

Function Definition

plugin.cpp
#include <plugify/plugify.h>
#include "module_export.h" // Include the generated export header

extern "C" PLUGIN_API int32_t AddNumbers_Exported_Name(int32_t a, int32_t b) {
    return a + b;
}

Exporting the Function

When the plugin is loaded, the function AddNumbers will be exported and can be called by other plugins.

Plugin Manifest Example

All exported functions must be described in the plugin's manifest file under the methods section. Here’s an example manifest for a plugin that exports the AddNumbers function:

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "AddNumbers",
      "funcName": "AddNumbers_Exported_Name",
      "paramTypes": [
        {
          "type": "int32",
          "name": "a"
        },
        {
          "type": "int32",
          "name": "b"
        }
      ],
      "retType": {
        "type": "int32"
      }
    }
  ]
}

Advanced Example: Exporting Complex Functions

Here’s an example of exporting a function with complex parameter and return types:

Function Definition

plugin.cpp
#include <plugify/plugify.h>
#include "module_export.h"

extern "C" PLUGIN_API plg::vector<plg::string> ProcessData_Exported_Name(const plg::vector<double>& data, const plg::string& prefix) {
    plg::vector<plg::string> result;
    for (double value : data) {
        result.push_back(prefix + std::to_string(value));
    }
    return result;
}

Plugin Manifest

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "ProcessData",
      "funcName": "ProcessData_Exported_Name",
      "paramTypes": [
        {
          "type": "double[]",
          "name": "data"
        },
        {
          "type": "string",
          "name": "prefix"
        }
      ],
      "retType": {
        "type": "string[]"
      }
    }
  ]
}

Exporting Functions with References

C++ allows you to export functions that take parameters by reference, enabling the function to modify the caller's values.

Function Definition

plugin.cpp
#include <plugify/plugify.h>
#include "module_export.h"

extern "C" PLUGIN_API void IncrementValue_Exported_Name(int32_t& value) {
    value += 1;
}

Plugin Manifest

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "IncrementValue",
      "funcName": "IncrementValue_Exported_Name",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value",
          "ref": true
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ]
}

Note: In the manifest, set "ref": true for parameters that should be passed by reference. This tells Plugify to pass the parameter as a mutable reference.

Handling Callbacks

Plugify allows you to export functions that accept callbacks as parameters. Here’s an example:

Function Definition

#include <plugify/plugify.h>
#include "module_export.h"

using CallbackFunction = plg::string(*)(int32_t, const plg::string&);

extern "C" PLUGIN_API void ExecuteWithCallback_Exported_Name(int32_t value, const plg::string& input, CallbackFunction callback) {
    plg::string result = callback(value, input);
    // Process the result
}

Plugin Manifest

plugin_name.pplugin
{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    {
      "name": "ExecuteWithCallback",
      "funcName": "ExecuteWithCallback_Exported_Name",
      "paramTypes": [
        {
          "type": "int32",
          "name": "value"
        },
        {
          "type": "string",
          "name": "input"
        },
        {
          "type": "function",
          "name": "callback",
          "prototype": {
            "name": "ExampleCallback",
            "funcName": "ExampleCallback_Exported_Name",
            "paramTypes": [
              {
                "type": "int32",
                "name": "value"
              },
              {
                "type": "string",
                "name": "input"
              }
            ],
            "retType": {
              "type": "string"
            }
          }
        }
      ],
      "retType": {
        "type": "void"
      }
    }
  ]
}

Automating Manifest Generation

Instead of manually writing the manifest JSON for each exported function, you can use automated tools to extract function signatures, types, parameter descriptions, and even enum definitions directly from your C++ source code.

Benefits of Automated Generation

  1. No Manual JSON Writing: Function signatures are automatically extracted from your code
  2. Complete Type Information: Enum structures and function typedefs are included automatically
  3. Documentation Integration: Doxygen comments are parsed and included in the manifest
  4. Reduced Errors: Eliminates typos and type mismatches between code and manifest
  5. Easy Maintenance: Changes to function signatures are automatically reflected

Setup: Adding Documentation Generation to CMake

Add the following to your CMakeLists.txt to generate documentation and manifest JSON:

# Find clang-doc (usually comes with LLVM/Clang installation)
find_program(CLANG_DOC clang-doc)

if(CLANG_DOC)
    # Generate YAML documentation from your exported functions
    add_custom_target(docs
        COMMAND ${CLANG_DOC}
            --executor=all-TUs
            -p ${CMAKE_CURRENT_BINARY_DIR}
            --output=${CMAKE_CURRENT_SOURCE_DIR}/docs
            --extra-arg=-Wno-error
            --format=yaml
            ${CMAKE_SOURCE_DIR}/src/export/*.cpp  # Path to your exported functions
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        COMMENT "Generating documentation with clang-doc"
    )
    
    # Optional: Automatically parse YAML to JSON after docs generation
    find_package(Python3 COMPONENTS Interpreter)
    if(Python3_FOUND)
        add_custom_command(TARGET docs POST_BUILD
            COMMAND ${Python3_EXECUTABLE} 
                    ${CMAKE_SOURCE_DIR}/parser/parser.py
                    ${CMAKE_SOURCE_DIR}/docs/index.yaml
                    ${CMAKE_SOURCE_DIR}/exported_methods.json
            COMMENT "Parsing documentation to JSON"
        )
    endif()
endif()

Using the Parser

Install the Parser

Download parser.py and place it in your project (e.g., in a tools/ directory).

Install dependencies:

pip install pyyaml

Document Your Functions

Add Doxygen comments to your exported functions:

/**
 * @brief Creates a console command as an administrative command.
 * If the command does not exist, it is created. When this command is invoked,
 * the access rights of the player are automatically checked before allowing it to continue.
 * 
 * @param name The name of the console command.
 * @param adminFlags The admin flags that indicate which admin level can use this command.
 * @param description A brief description of what the command does.
 * @param flags Command flags that define the behavior of the command.
 * @param callback A callback function that is invoked when the command is executed.
 * @param mode Whether the hook was in post mode (after processing) or pre mode (before processing).
 * @return A boolean indicating whether the command was successfully added.
 */
extern "C" PLUGIN_API bool AddAdminCommand(
    const plg::string& name,
    int64_t adminFlags,
    const plg::string& description,
    ConVarFlag flags,
    CommandCallback callback,
    HookMode mode
);

Document Enums and Typedefs

Document your enums and function typedefs:

/**
 * @brief Enum representing the type of callback.
 */
enum class HookMode : uint8_t {
    /** Callback will be executed before the original function */
    Pre = 0,
    /** Callback will be executed after the original function */
    Post = 1
};

/**
 * @brief Handles the execution of a command triggered by a caller.
 * This function processes the command, interprets its context, and handles any provided arguments.
 */
using CommandCallback = ResultType (*)(int32_t caller, CommandCallingContext context, const plg::vector<plg::string>& arguments);

Generate Documentation

Build the documentation target:

cmake --build . --target docs

This generates docs/index.yaml with all your function information.

Parse to JSON

Run the parser to convert YAML to the manifest format:

python3 parser/parser.py docs/index.yaml exported_methods.json

Or with filtering:

# Only functions from files starting with "commands"
python3 parser/parser.py docs/index.yaml exported_methods.json --file-prefix "commands"

# Only functions with "Admin" in their name
python3 parser/parser.py docs/index.yaml exported_methods.json --name-filter "Admin"

Use in Your Manifest

Copy the generated methods array from exported_methods.json into your plugin manifest:

{
  "name": "ExamplePlugin",
  "version": "1.0.0",
  "methods": [
    // Paste the content from exported_methods.json here
  ]
}

Advanced: Enum Structures

The parser automatically includes complete enum definitions when a parameter uses an enum type:

Generated Output:

{
  "name": "mode",
  "type": "uint8",
  "description": "Whether the hook was in post mode or pre mode.",
  "enum": {
    "name": "HookMode",
    "description": "Enum representing the type of callback.",
    "values": [
      {
        "name": "Pre",
        "value": 0,
        "description": "Callback will be executed before the original function"
      },
      {
        "name": "Post",
        "value": 1,
        "description": "Callback will be executed after the original function"
      }
    ]
  }
}

Advanced: Function Typedefs

Function pointer typedefs are automatically parsed into complete prototypes:

Generated Output:

{
  "name": "callback",
  "type": "function",
  "description": "A callback function that is invoked when the command is executed.",
  "prototype": {
    "name": "CommandCallback",
    "funcName": "CommandCallback",
    "description": "Handles the execution of a command triggered by a caller.",
    "paramTypes": [
      {
        "name": "param1",
        "type": "int32"
      },
      {
        "name": "param2",
        "type": "int32",
        "enum": {
          "name": "CommandCallingContext",
          "values": [...]
        }
      },
      {
        "name": "param3",
        "type": "string[]"
      }
    ],
    "retType": {
      "type": "int32",
      "enum": {
        "name": "ResultType",
        "values": [...]
      }
    }
  }
}

Best Practices

  1. Use PLUGIN_API: Always use the PLUGIN_API macro to mark functions for export.
  2. Follow Type Conventions: Adhere to Plugify's type conventions for parameters and return values.
  3. Document Your Functions: Use Doxygen-style comments (@brief, @param, @return) to document exported functions.
  4. Automate When Possible: Use the clang-doc parser to automate manifest generation and reduce manual errors.
  5. Document Enums and Typedefs: Include Doxygen comments on enums and typedefs for complete manifest generation.
  6. Test Thoroughly: Test your exported functions to ensure they work as expected when called by other plugins.
  7. Keep Manifests Updated: If using manual manifests, ensure they stay synchronized with your code. If using automation, regenerate after changes.

Conclusion

Exporting functions in C++ plugins is straightforward when you follow Plugify's conventions and best practices. By using the PLUGIN_API macro, adhering to type conventions, and optionally automating manifest generation with clang-doc and the Python parser, you can create robust and interoperable plugins with minimal manual effort. For more advanced use cases, such as handling callbacks or working with enums, the automated parser provides complete type information including enum structures and function prototypes.