Extension System (CAMAPI)
For a practical guide on choosing the right entry point and implementing each one, see Extension Entry Points.
All extension management in ENCY is performed through the interfaces defined in CAMAPI.Extensions. The central interface is IExtensionManager, which ENCY exposes via CAMAPI.ExtensionManager.dll.
Obtaining the manager (.NET helper):
using CAMAPI.DotnetHelper;
using var managerCom = ExtensionManagerHelper.GetInstance();
// or from an application context:
using var managerCom = appCom.GetExtensionManager();
Obtaining the manager (direct IDL):
ICamApiApplication.GetExtensionManager(out TResultStatus, return IExtensionManager*)
IExtensionManager
Registration
RegisterLibrary
Registers a JSON description file into the specified storage. Returns library metadata on success.
// .NET helper
ComWrapper<IExtensionLibraryInfo> info = managerCom.RegisterLibrary(
TStorageType.stCurrentUser,
@"C:\MyPlugins\MyExtension.json");
// IDL
IExtensionManager.RegisterLibrary(
in StorageType: TStorageType,
in JsonFilePath: string,
out TResultStatus,
return IExtensionLibraryInfo*)
RegisterLibraryFromFolder
Reads a plugin folder, finds its settings.json, copies the folder into the storage's own
location, then registers it. Convenient for installing a self-contained plugin folder in one
call. The needReload out-flag reports whether a storage reload is required to pick it up.
// IDL
IExtensionManager.RegisterLibraryFromFolder(
in StorageType: TStorageType,
in FolderPath: string,
out TResultStatus,
out needReload: boolean,
return IExtensionLibraryInfo*)
UnRegisterLibrary
Removes a library from all storages by its DLL path or JSON path.
managerCom.UnRegisterLibrary(@"C:\MyPlugins\MyExtension.dll");
// IDL
IExtensionManager.UnRegisterLibrary(in LibraryPath: string, out TResultStatus)
ReloadStorage
Forces a re-read of a specific storage tier (e.g. after an external change to the JSON files).
managerCom.ReloadStorage(TStorageType.stCurrentUser);
// IDL
IExtensionManager.ReloadStorage(in StorageType: TStorageType, out TResultStatus)
State
GetLibraryDisabled / SetLibraryDisabled
Query or set the disabled flag for a library in a specific storage tier.
bool disabled = managerCom.GetLibraryDisabled(TStorageType.stCurrentUser, libraryPath);
managerCom.SetLibraryDisabled(TStorageType.stCurrentUser, libraryPath, disabled: true);
GetLibraryDisabledFinal
Returns the effective disabled state after all storage tiers have been merged (highest-priority tier wins).
bool effectivelyDisabled = managerCom.GetLibraryDisabledFinal(libraryPath);
GetExtensionDisabled / SetExtensionDisabled
Same as the library variants but per individual extension type ID.
bool disabled = managerCom.GetExtensionDisabled(TStorageType.stCurrentUser, extensionTypeId);
managerCom.SetExtensionDisabled(TStorageType.stCurrentUser, extensionTypeId, disabled: true);
GetExtensionDisabledFinal
Effective disabled state for an individual extension after merging all storage tiers.
bool effectivelyDisabled = managerCom.GetExtensionDisabledFinal(extensionTypeId);
GetLibraryLoaded
Returns whether a library DLL is currently loaded in memory.
bool loaded = managerCom.GetLibraryLoaded(libraryPath);
GetFieldInherited / SetFieldInherited
Checks or marks whether a property (TStorageField) of a library or extension is inherited from a lower-priority storage tier rather than explicitly set in the current one.
bool inherited = managerCom.GetFieldInherited(
TStorageType.stCurrentUser, entityIdent, TStorageField.sfLibraryDisabled);
managerCom.SetFieldInherited(
TStorageType.stCurrentUser, entityIdent, TStorageField.sfAll);
GetLibraryStorages
Returns a list of TStorageType values representing the storage tiers in which the library has non-inherited (explicitly set) values.
using var storagesCom = managerCom.GetLibraryStorages(libraryPath);
AddExtension / RemoveExtension
Explicitly tracks an extension within a storage tier so its disabled flag can be managed independently of the library-level flag.
managerCom.AddExtension(TStorageType.stCurrentUser, libraryPath, extensionTypeId);
managerCom.RemoveExtension(TStorageType.stCurrentUser, libraryPath, extensionTypeId);
Extension settings
Named key/value settings that belong to an extension type, stored by the extension manager instead of by the extension itself. Use them for the configuration a deployment provides — a server URL, a license key, a shared folder — where the value has to survive reinstallation and may be pinned centrally for all users.
Settings are kept in an extension_settings.json per storage tier, next to that tier's
extension list, and they are resolved as a cascade that is independent of the
disabled/inherited machinery above:
stCurrentUser → stAllUsers → stDealer → stSystem → manifest default
The highest tier that defines the key wins; if no tier does, the default declared by whoever
shipped the extension is used. A key does not have to be defined anywhere, so every read
reports a found flag.
| Method | Description |
|---|---|
GetExtensionSetting(extensionTypeId, key, out found, out ret) → string |
Effective value after the whole cascade. found is false when neither a tier nor a default defines the key |
GetExtensionSettingSourceLevel(extensionTypeId, key, out found, out fromDefault, out ret) → TStorageType |
Where the effective value came from. When fromDefault is true the value is the manifest default and the returned tier is meaningless |
GetExtensionSettingKeys(extensionTypeId, out ret) → IListString* |
Union of all keys defined for the extension across every tier and the defaults — use it to enumerate settings without knowing their names |
SetExtensionSetting(storageType, extensionTypeId, key, value, out ret) |
Write a value into one tier and persist that tier |
RemoveExtensionSetting(storageType, extensionTypeId, key, out ret) |
Drop the key from one tier, so the next tier down (or the default) becomes effective again |
There is no ExtensionManagerHelper wrapper for these — call them through Invoke:
using var managerCom = ExtensionManagerHelper.GetInstance();
const string extensionTypeId = "My.Company.MyExtension";
// read the effective value
var found = false;
string url = managerCom.Invoke(m =>
{
var value = m.GetExtensionSetting(extensionTypeId, "serverUrl", out var wasFound, out var ret);
if (ret.Code == TResultStatusCode.rsError)
throw new Exception(ret.Description);
found = wasFound;
return value;
});
if (!found)
throw new Exception("The 'serverUrl' setting is not configured for this installation");
// remember the user's own choice
managerCom.Invoke(m =>
{
m.SetExtensionSetting(TStorageType.stCurrentUser, extensionTypeId, "serverUrl", url, out var ret);
if (ret.Code == TResultStatusCode.rsError)
throw new Exception(ret.Description);
});
Which tier to write to. Write stCurrentUser for a choice the user made, stAllUsers when
provisioning a whole machine. Writing to a tier that has no storage in this installation —
stDebugMode outside a debug session — fails with rsError rather than silently doing nothing,
so check the status instead of assuming the write landed.
Secret values. A key may be marked secret, and then its value is kept in memory for the session only and never written to any tier's file. Reading and writing look exactly the same, so an extension cannot tell a secret key from a normal one — but a secret value does not survive a restart of ENCY, and the user (or the caller) has to supply it again.
Over IPC the same five calls are available with .NET helper wrappers — see ../ipc/application.md.
Querying Metadata
GetExtensionTypeInfo
Returns the IExtensionTypeInfo for a single extension by its unique type ID.
using var typeInfoCom = managerCom.GetExtensionTypeInfo("My.Extension.Ident");
GetExtensionTypeInfos
Returns all IExtensionTypeInfo objects belonging to a group (the Group field in the JSON).
using var listCom = managerCom.GetExtensionTypeInfos("Extension.Util.Common");
GetExtensionTypeInfosFromLibrary
Returns all extension type infos from a specific library DLL path.
using var listCom = managerCom.GetExtensionTypeInfosFromLibrary(libraryPath);
GetExtensionTypeGroups
Returns all known group strings across all registered libraries.
using var groupsCom = managerCom.GetExtensionTypeGroups();
GetExtensionTypeGroup
Returns the structured IExtensionTypeGroup for one group id (richer than the plain string
from GetExtensionTypeGroups).
using var groupCom = managerCom.InvokeAndWrap(m =>
(m.GetExtensionTypeGroup("Extension.Util.Common", out var status), status));
// IExtensionTypeGroup: Id (string), Caption (string), IsSystem (bool)
IListExtensionTypeGroup is the list form (Get(index), Count(), Add, Remove, RemoveAt).
GetLibraryInfo
Returns IExtensionLibraryInfo for the library that contains the specified extension type ID.
using var libInfoCom = managerCom.GetLibraryInfo("My.Extension.Ident");
GetLibrariesInfo
Returns metadata for all currently registered libraries.
using var allLibsCom = managerCom.GetLibrariesInfo();
Each IExtensionLibraryInfo carries, besides its path and UnloadMode, the descriptive
fields taken from the library's settings.json: Version, Id, Author, IconPath,
Tags, and SettingsPath — the path to the *.settings.json the library was registered
from (all read-only strings).
GetPendingDeletionLibraries
Returns the libraries whose unregistration could not finish because their DLL was locked at
the time (UnRegisterLibrary left a _del_ cleanup artifact on disk instead of deleting).
using var pendingCom = managerCom.InvokeAndWrap(m => m.GetPendingDeletionLibraries());
Entries whose artifact is already gone are dropped automatically, so a non-empty result means a real leftover — typically a library that will only disappear after ENCY restarts.
Creation
CreateExtension
Creates and returns a new instance of an extension by its unique type ID. The extension's DLL is loaded if not already in memory.
using var extCom = managerCom.CreateExtension("My.Extension.Ident");
using var solverCom = extCom.InvokeAndWrap(e => e as ICamApiTechOperationSolver);
if (solverCom.IsNull)
throw new Exception("Extension does not implement ICamApiTechOperationSolver");
// IDL
IExtensionManager.CreateExtension(in ExtensionTypeId: string, out TResultStatus, return IExtension*)
CreateExtensionsByGroups
Creates instances of all non-disabled extensions whose Group matches any of the semicolon-separated group strings.
using var listCom = managerCom.CreateExtensionsByGroups("Extension.Util.Common;Extension.Util.Custom");
// IDL
IExtensionManager.CreateExtensionsByGroups(in ExtensionGroups: string, out TResultStatus, return IListExtension*)
GetSingletonExtension
Returns the single shared instance of a singleton extension (creates it on first call). Use for extensions that must exist exactly once across all callers.
using var extCom = managerCom.GetSingletonExtension("My.Singleton.Ident");
// Static shortcut (no manager reference needed):
using var extCom2 = ExtensionManagerHelper.GetSingletonExtension("My.Singleton.Ident");
GetExtension
Returns an already-created extension instance by its numeric instance ID (IExtensionInstanceInfo.Id).
using var extCom = managerCom.GetExtension(instanceId);
// Static shortcut:
using var extCom2 = ExtensionManagerHelper.GetExtension(instanceId);
GetExtensionsByTypeId
Returns all currently live instances of a specific extension type.
using var listCom = managerCom.GetExtensionsByTypeId("My.Extension.Ident");
Lifecycle
FreeExtension
Unloads a single extension instance by its numeric instance ID.
managerCom.FreeExtension(instanceId);
// IDL
IExtensionManager.FreeExtension(in ExtensionInstanceId: hyper, out TResultStatus)
FreeExtensionsByTypeId
Unloads all live instances of a specific extension type.
managerCom.FreeExtensionsByTypeId("My.Extension.Ident");
FreeLibrary
Unloads all extension instances from the specified library and then unloads the DLL.
managerCom.FreeLibrary(libraryPath);
FinalizeExtensions
Unloads all extensions across all libraries. Called by ENCY during application shutdown; plugins do not normally call this directly.
managerCom.FinalizeExtensions();
Diagnostics
Logger
Returns the IExtensionLogger used by the extension system. Prefer accessing it through the static helper:
using var loggerCom = ExtensionManagerHelper.Logger();
if (!loggerCom.IsNull)
loggerCom.Invoke(log => log.Info("My message"));
ApiVersion
Returns the version string of the CAMAPI SDK against which the extension manager was built.
string version = managerCom.ApiVersion();
// or static:
string version = ExtensionManagerHelper.ApiVersion();
IExtensionStorage / TStorageType
IExtensionStorage represents a single tier of a layered configuration store. Tiers are evaluated in priority order; a value set in a higher-priority tier overrides the same value from lower tiers.
| Value | Priority | Scope | Mutable |
|---|---|---|---|
stSystem |
Lowest | All users, machine-wide | No |
stDealer |
2 | Current user only | No |
stAllUsers |
3 | All users, machine-wide | Yes |
stCurrentUser |
4 | Current user only | Yes |
stDebugMode |
5 | Current user, debug sessions | Yes |
stTestMode |
Highest | Automated test runs | Yes |
Typical development workflow: register your DLL with stDebugMode so it does not affect other users and is easy to disable.
Key storage methods (on IExtensionStorage directly):
| Method | Description |
|---|---|
Read |
Load current state from the backing file or database. |
Save |
Persist the current in-memory state. |
RegisterLibrary(LibraryIdent) |
Add a library entry to this tier's tracking list. |
UnRegisterLibrary(LibraryIdent) |
Remove a library entry from this tier. |
GetLibraries |
List all library paths recorded in this tier. |
GetExtensions(LibraryIdent) |
List extension type IDs that have explicit settings in this tier. |
GetLibraryDisabled / SetLibraryDisabled |
Read or write the disabled flag for a library. |
GetExtensionDisabled / SetExtensionDisabled |
Read or write the disabled flag for an individual extension. |
GetLibraryFieldInherited / SetLibraryFieldInherited |
Query or mark a library property as inherited from a lower tier. |
GetExtensionFieldInherited / SetExtensionFieldInherited |
Query or mark an extension property as inherited. |
TStorageField selects which property the inheritance methods act on:
| Value | Meaning |
|---|---|
sfAll |
All fields |
sfLibraryDisabled |
The library-level disabled flag |
sfExtensionDisabled |
The extension-level disabled flag |
Extension type-info kinds
Every extension declares its kind through an IExtensionTypeInfo… marker interface (the same
mechanism behind IExtensionTypeInfoUtility, IExtensionTypeInfoOperationPopup, etc. — see
extension entry points). In addition to the popup and
utility kinds documented there, the API also declares:
| Type-info interface | Injection point |
|---|---|
IExtensionTypeInfoProjectReportPopup |
Context menu of a project report |
IExtensionTypeInfoPostprocessorPopup |
Context menu of a postprocessor |
IExtensionTypeInfoToolsListPopup |
Context menu of the tools list |
IExtensionTypeInfoNCFilesExporter |
Export generated NC files to an external target (pairs with IExtensionNCFilesExporter — see nc-simulation.md) |
IExtensionTypeInfoMacroBuilder |
Builds macros for one macro language (pairs with IExtensionMacroBuilder — see below) |
IExtensionMacroBuilder — adding a macro language
A macro-builder extension teaches ENCY how to turn a recorded command sequence into a
runnable macro in one particular language. The built-in dotnet and spr builders are
exactly such extensions; the macro manager dispatches to one of them by
ICamApiMacroInfo.LanguageId.
This is the producer side. To merely record, build or run macros from a plugin, use
ICamApiMacroManager instead — you do not need this
interface.
| Method | Description |
|---|---|
CheckEnvironment(languageSettings, out ret) |
Verify the toolchain is usable (compiler reachable, references resolvable). rsError + Description on failure |
SetupEnvironment(languageSettings, out ret) |
Try to repair the environment so CheckEnvironment passes; no-op for languages needing no external tooling |
AddCommand(data) |
Feed one recorded command (ICamApiMacroCommandData) into the builder |
AddCommandSetDriveFaceItemProperties(...) |
Record a drive-face item property change. Separate from AddCommand because it carries a string list (Faces), which the scalar command bag cannot hold |
AddCommandAddFixture(operationGuid, addType) |
Record an "add fixture" command (TFixtureAddType: 0 = Chuck, 1 = Vise, 2 = Clamp, 3 = Node) |
CreateMacro(context, mainSettings, languageSettings, out ret) → string |
Generate the macro source from the accumulated commands; returns its path. A new file each call — not yet runnable |
Build(id, caption, description, macroPath, out ret) → string |
Compile the source into something ENCY can execute. Stateless |
OpenInEditor(macroPath, context, out ret) |
Open the source for editing in whatever is native for the language (project folder in VS Code, host scripting IDE, …) |
Reset() |
Drop all accumulated commands |
IExtensionMacroBuilderContext carries a single member — CamApplication
(ICamApiApplication*).
Always
Reset()around a build. The builder instance is shared and long-lived, so a failed build otherwise leaves its commands behind and they leak into the next macro until ENCY restarts.
IExtensionLogger
IExtensionLogger is the unified logging sink for the extension system. Obtain it via ExtensionManagerHelper.Logger() or from IExtensionManager.Logger.
| Method | Level | Notes |
|---|---|---|
Debug(message) |
Debug | Detailed diagnostic output, typically suppressed in production. |
Verbose(message) |
Verbose | Finer-grained than Info, coarser than Debug. |
Info(message) |
Info | Normal operational messages. |
Head(message) |
Head | Section headings or major milestones in a log sequence. |
Warning(message) |
Warning | Recoverable anomalies. |
Error(message) |
Error | Non-recoverable failures. |
Log(event) |
Any | Low-level: supply a LogItem struct with event type and message directly. |
IsEventTypeActive(EventType) |
— | Returns true if the given TLogEventType is currently being collected. Use to skip expensive message construction. |
Notify(eventType, message, title) |
Any | Sends a visible notification to the user in the main application UI in addition to writing to the log. |
Usage example:
using var loggerCom = ExtensionManagerHelper.Logger();
if (loggerCom.IsNull) throw new Exception("Logger unavailable");
loggerCom.Invoke(log =>
{
if (log.IsEventTypeActive(TLogEventType.leDebug))
log.Debug($"Processing operation: {operation.Name}");
log.Info("Toolpath calculation started");
log.Warning("No geometry assigned — using defaults");
log.Error("CLD receiver is null");
log.Notify(TLogEventType.leInfo, "Export complete", "My Extension");
});