Learn how to use class wrappers for cleaner, object-oriented plugin APIs in D.
Using Classes in D
Classes in Plugify provide a convenient, type-safe, and RAII-compliant way to work with complex objects exported by plugins. Instead of manually managing raw pointers and calling constructor/destructor functions, you can use generated D struct wrappers that handle resource management automatically.
When a plugin exports functions that create and destroy objects (like Kv1Create and Kv1Destroy), you could call these functions directly:
// Manual approach - error-prone
void* kv = test_keyvalues.Kv1Create("Config");
test_keyvalues.Kv1SetName(kv, "ServerConfig");
auto name = test_keyvalues.Kv1GetName(kv);
test_keyvalues.Kv1Destroy(kv); // Easy to forget!
However, this approach has several problems:
Resource Leaks: If you forget to call Kv1Destroy(), the resource leaks
Exception Unsafety: If an exception is thrown, destructor won't be called
No Type Safety: Raw void* pointers provide no compile-time type checking
Verbose: You must manually pass the handle to every function call
Error-Prone: Easy to use the handle after it's been destroyed
Classes solve all these problems by using D's RAII (Resource Acquisition Is Initialization):
// RAII approach - automatic, safe, exception-safe
auto kv = KeyValues("Config");
kv.SetName("ServerConfig");
auto name = kv.GetName();
// Automatically destroyed when kv goes out of scope!
Resources are automatically destroyed when objects go out of scope:
void processConfig() {
auto kv = KeyValues("ServerConfig");
kv.SetName("Production");
// Resource is automatically destroyed when kv goes out of scope
} // Destructor called here - Kv1Destroy() is invoked automatically
The generated structs disable postblit (@disable this(this)) to prevent accidental copies:
auto kv1 = KeyValues("Config");
// auto kv2 = kv1; // ERROR: postblit is disabled
// Use move semantics instead
import std.algorithm.mutation : move;
auto kv2 = move(kv1); // OK: kv1 is now empty
D supports move semantics through the move constructor:
import std.algorithm.mutation : move;
auto kv1 = KeyValues("Config1");
auto kv2 = move(kv1); // kv1's resources transferred to kv2
// kv1 is now empty (handle is null)
When a method returns a handle that you don't own (marked with "owner": false in manifest):
auto parent = KeyValues("Parent");
auto child = parent.FindKey("ChildKey");
// child is borrowed (ownership = Ownership.Borrowed)
// child's destructor will NOT call Kv1Destroy()
// parent owns the actual child resource
Important: Borrowed objects must not outlive the object they were borrowed from:
// WRONG - Dangling reference
KeyValues child;
{
auto parent = KeyValues("Parent");
child = parent.FindKey("Child");
} // parent is destroyed here, taking child with it
// child now points to destroyed memory!
Some methods take ownership of objects (marked with "owner": true in manifest):
auto parent = KeyValues("Parent");
auto child = KeyValues("Child");
parent.AddSubKey(child);
// child.release() is called internally
// child now has null handle and Borrowed ownership
// parent owns the child resource
// WRONG: Can't use child after transfer
// child.SetName("NewName"); // Throws exception: Empty handle
auto kv = KeyValues("Config");
void* handle = kv.release();
// kv now has null handle, but resource is not destroyed
// You're responsible for calling Kv1Destroy(handle) manually
auto kv1 = KeyValues("Config1");
auto kv2 = KeyValues("Config2");
if (kv1 == kv2) {
writeln("Same handle");
}
if (kv1 < kv2) {
writeln("kv1 handle is less than kv2 handle");
}
// Can be used in sorted containers
import std.container : redBlackTree;
auto tree = redBlackTree(kv1, kv2);
void processConfig() {
auto kv = KeyValues("Config");
kv.SetName("Production");
// If an exception is thrown here...
throw new Exception("Something went wrong!");
// ...the destructor is still called
} // Destructor called during stack unwinding
D's class wrappers provide a safe, efficient, and idiomatic way to work with Plugify classes. The combination of RAII, move semantics, and D's powerful attribute system ensures resource safety while maintaining performance. By following D's ownership principles and the best practices in this guide, you can build robust plugins with confidence.