Please enable JavaScript to view this site.

Navigation: Reference > Plugins

.NET Plugins

Scroll Prev Top Next More

Writing .NET Plugins for TCC

 

A guide for .NET developers who want to extend TCC (Take Command Console) with custom commands, variables, and functions using C# or other .NET languages.

 

Overview

 

TCC supports plugins written as .NET class libraries. A C++/CLI bridge DLL (`TC-DotNetPluginHost64.dll`) loads your .NET assembly at runtime via reflection. Your plugin class implements the `ITCCPlugin` interface and exposes methods that TCC invokes by name.

 

How It Works

 

1. TCC inspects the DLL's PE headers. If a CLR metadata directory is present, TCC treats the DLL as a .NET assembly.

2. TCC loads the bridge DLL (once) and calls `TCCDotNetPlugin_Load` to load your assembly.

3. The bridge finds your plugin type (by `ITCCPlugin` interface or by convention), creates an instance, and caches all public methods.

4. TCC calls `Initialize`, `GetPluginInfo`, and later `Shutdown` through the bridge.

5. When a user invokes a command, variable, or function that matches your `Functions` list, TCC calls the corresponding method on your plugin instance via the bridge.

 

Quick Start

 

1. Create a Class Library Project

 

Create a **.NET Framework 4.8** (or **.NET Standard 2.0**) class library project in Visual Studio.

 

2. Add the SDK

 

Either:

- Include `DotNetPluginSDK.cs` directly in your project, *or*

- Reference the compiled `TakeCommand.Plugin.SDK.dll`

 

The SDK provides two types in the `TakeCommand.Plugin` namespace:

 

| Type            | Purpose

|-----------------|-------------------------------------------

| `TCCPluginInfo` | Data class holding plugin metadata

| `ITCCPlugin`    | Interface your plugin class must implement

 

3. Implement `ITCCPlugin`

 

C#

 

using System;

using System.Text;

using TakeCommand.Plugin;

 

public class MyPlugin : ITCCPlugin

{

   private static readonly TCCPluginInfo _info = new TCCPluginInfo

   {

       Name        = "MyPlugin",

       Author      = "Your Name",

       Email       = "[email protected]",

       WWW         = "https://example.com",

       Description = "A sample TCC plugin written in C#",

       Functions   = "HELLO,_MYPLUGINVER,@REVERSE",

       Major       = 1,

       Minor       = 0,

       Build       = 1

   };

 

   public TCCPluginInfo GetPluginInfo() => _info;

   public bool Initialize()            => true;

   public bool Shutdown(bool end)       => true;

 

   // Command: HELLO

   public int HELLO(StringBuilder args)

   {

       Console.WriteLine("Hello from .NET plugin!");

       return 0;

   }

 

   // Internal variable: %_MYPLUGINVER

   public int _MYPLUGINVER(StringBuilder args)

   {

       args.Clear();

       args.Append("1.0.1");

       return 0;

   }

 

   // Variable function: @REVERSE[text]

   public int f_REVERSE(StringBuilder args)

   {

       char[] chars = args.ToString().ToCharArray();

       Array.Reverse(chars);

       args.Clear();

       args.Append(chars);

       return 0;

   }

}

 

4. Build and Deploy

 

Build your project and copy the resulting `.dll` file into TCC's `PlugIns` directory (typically a subdirectory of the TCC installation directory). TCC loads all DLLs from this directory on startup, or you can load it manually:

 

PLUGIN /L C:\Path\To\MyPlugin.dll

 

The `ITCCPlugin` Interface

 

C#

 

public interface ITCCPlugin

{

   TCCPluginInfo GetPluginInfo();

   bool Initialize();

   bool Shutdown(bool endProcess);

}

 

`GetPluginInfo()`

 

Called once after loading. Return a `TCCPluginInfo` object describing your plugin and the features it provides.

 

`Initialize()`

 

Called after the assembly is loaded and the plugin type is instantiated. Perform any one-time setup here. Return `true` on success, `false` on failure (TCC will unload the plugin).

 

`Shutdown(bool endProcess)`

 

Called when the plugin is being unloaded. The `endProcess` parameter is `true` when the entire command processor is shutting down, `false` when only your plugin is being unloaded. Return `true` on success.

 

The `TCCPluginInfo` Class

 

C#

 

public class TCCPluginInfo

{

   public string Name { get; set; }         // Short name (shown in PLUGIN listings)

   public string Author { get; set; }       // Author's name

   public string Email { get; set; }        // Author's email

   public string WWW { get; set; }          // Author's website

   public string Description { get; set; }  // Brief description

   public string Functions { get; set; }    // Comma-delimited feature list

   public int Major { get; set; }           // Major version number

   public int Minor { get; set; }           // Minor version number

   public int Build { get; set; }           // Build number

}

 

The `Functions` String

 

The `Functions` property is a **comma-delimited** list of the commands, variables, and functions your plugin provides. The prefix on each name determines its type:

 

| Prefix   |    Type            | TCC Syntax       | Method Signature                          |

|----------|--------------------|------------------|-------------------------------------------|

| *(none)* | Internal command   | `HELLO args`     | `public int HELLO(StringBuilder args)`    |

| `_`      |  Internal variable | `%_MYVAR`        | `public int _MYVAR(StringBuilder args)`   |

| `@`      | Variable function  | `@MYFUNC[args]`  | `public int f_MYFUNC(StringBuilder args)` |

| `*`      | Keystroke handler  | *(not supported) |  €”                                         |

 

 

Example:

 

C#

 

Functions = "GREET,CALC,_VERSION,_AUTHOR,@UPPER,@LOWER"

 

This registers two commands (`GREET`, `CALC`), two variables (`_VERSION`, `_AUTHOR`), and two functions (`@UPPER`, `@LOWER`).

 

> **Important:** Variable function names in the `Functions` list use the `@` prefix (e.g., `@REVERSE`), but the corresponding **method name** uses the `f_` prefix (e.g., `f_REVERSE`). This convention allows a command and a variable function to share the same base name.

 

Method Signatures

 

All plugin feature methods must be `public` and follow one of these signatures:

 

Primary (read-write) — recommended

 

C#

 

public int MethodName(StringBuilder args)

 

The `StringBuilder` contains the arguments passed by TCC. For variables and functions, write your result back into it. The maximum result length is 32,767 characters.

 

Read-only variant (commands only)

 

C#

 

public int MethodName(string args)

 

 

Use this when you only need to read the arguments and don't need to modify them.

 

No-argument variant

 

C#

 

public int MethodName()

 

For commands or variables that take no input.

 

### What `args` Contains

 

| Feature Type      | Contents of `args`                             |

|-------------------|------------------------------------------------|

| Internal command  | The command line minus the command name        |

| Internal variable | Empty (output only — write result into `args`) |

| Variable function | The argument(s) passed inside `@FUNC[...]`     |

 

 

Return Values:

 

| Return Value             | Meaning |

|--------------------------------------|------------------------------------------------------------------------------|

| `0`                      | Success                                                                                  |

| `> 0`                    | Failure — TCC interprets the value as a system error code and displays an error message  |

| `< 0`                    | Failure — your plugin already displayed an error message                                 |

| `0xFEDCBA98` (unchecked) | **Not handled** — TCC continues searching for a matching internal or external command    |

 

The special return value `0xFEDCBA98` is useful when your plugin wants to inspect (and optionally modify) the command line but then pass it on to TCC's built-in handler:

 

C#

 

public int MYCOMMAND(StringBuilder args)

{

   // Optionally modify args here...

   // Then tell TCC we didn't handle it:

   return unchecked((int)0xFEDCBA98);

}

 

 

Plugin Discovery

 

TCC uses two strategies to find your plugin type (in order):

 

1. **Interface match** — Finds the first public, non-abstract class that implements an interface named `ITCCPlugin`.

2. **Convention match** — Finds the first public, non-abstract class that has both a `GetPluginInfo` method and an `Initialize` method.

 

Strategy 1 is recommended. Strategy 2 exists for cases where you cannot reference the SDK assembly directly.

 

 

Complete Example

 

Below is a complete, working plugin that adds a `GREET` command, a `_GREETCOUNT` variable, and an `@UPPER` function.

 

C#

 

using System;

using System.Text;

using TakeCommand.Plugin;

 

namespace SampleTCCPlugin

{

   public class GreetPlugin : ITCCPlugin

   {

       private int _greetCount;

 

       private readonly TCCPluginInfo _info = new TCCPluginInfo

       {

           Name        = "Greet",

           Author      = "Jane Developer",

           Email       = "[email protected]",

           WWW         = "https://example.com/greet",

           Description = "Greeting plugin with variable and function support",

           Functions   = "GREET,_GREETCOUNT,@UPPER",

           Major       = 1,

           Minor       = 2,

           Build       = 0

       };

 

       public TCCPluginInfo GetPluginInfo() => _info;

 

       public bool Initialize()

       {

           _greetCount = 0;

           return true;

       }

 

       public bool Shutdown(bool endProcess)

       {

           // Nothing to clean up

           return true;

       }

 

       /// <summary>

       /// Command: GREET [name]

       /// Usage:   GREET World

       /// Output:  Hello, World!

       /// </summary>

       public int GREET(StringBuilder args)

       {

           string name = args.ToString().Trim();

           if (string.IsNullOrEmpty(name))

               name = "World";

 

           Console.WriteLine($"Hello, {name}!");

           _greetCount++;

           return 0;

       }

 

       /// <summary>

       /// Internal variable: %_GREETCOUNT

       /// Returns the number of times GREET has been called.

       /// </summary>

       public int _GREETCOUNT(StringBuilder args)

       {

           args.Clear();

           args.Append(_greetCount.ToString());

           return 0;

       }

 

       /// <summary>

       /// Variable function: @UPPER[text]

       /// Returns the uppercased version of the input text.

       /// </summary>

       public int f_UPPER(StringBuilder args)

       {

           string input = args.ToString();

           args.Clear();

           args.Append(input.ToUpperInvariant());

           return 0;

       }

   }

}

 

Using the Plugin in TCC

 

C:\> plugin /l C:\PlugIns\SampleTCCPlugin.dll

C:\> greet Developer

Hello, Developer!

 

C:\> echo %_GREETCOUNT

1

 

C:\> echo @UPPER[hello world]

HELLO WORLD

 

C:\> plugin /i Greet

Module: C:\PlugIns\SampleTCCPlugin.dll
Name:Greet
Author:Jane Developer
Web:https://example.com/greet
Description:Greeting plugin with variable and function support
Implements:GREET,_GREETCOUNT,@UPPER
Version:1.2  Build 0

 

Targeting a Specific Plugin

 

When multiple plugins are loaded, a user can target a specific plugin by prefixing the command, variable, or function name with the plugin name and a `$` delimiter:

 

C:\> Greet$GREET Developer

C:\> echo %Greet$_GREETCOUNT

C:\> echo @Greet$UPPER[text]

 

This is useful when two plugins register the same feature name.

 

Limitations

 

Feature

Native Plugin

.NET Plugin

Internal commands

Yes

Yes

Internal variables

Yes

Yes

Variable functions

Yes

Yes

Keystroke handlers

Yes

Not supported*

Tab completion

Yes

Not supported

Help/Usage callbacks

Yes

Not supported

 

* Keystroke handlers require passing the native `KEYINFO` struct (containing raw pointers) across the bridge, which is not supported. You may list `*` entries in your `Functions` string, but they will never be invoked.

 

 

Project Configuration

 

Target Framework

 

- **.NET Framework 4.8** or **.NET Standard 2.0** class library

- The bridge DLL uses `Assembly.LoadFrom()`, so standard .NET Framework class libraries work best

 

Build Output

 

- Build as **Class Library** (DLL)

- Ensure the output is an **AnyCPU** or platform-matching assembly (x64 for 64-bit TCC, x86 for 32-bit TCC)

- The DLL **must** contain CLR metadata (this is automatic for any .NET assembly)

 

Dependencies

 

If your plugin references additional NuGet packages or assemblies, ensure they are in the same directory as your plugin DLL or in a location resolvable by the .NET runtime. The bridge loads your assembly with `Assembly.LoadFrom()`, which probes the assembly's directory for dependencies.

 

Debugging Tips

 

1. **Attach to TCC** — In Visual Studio, use **Debug → Attach to Process** and attach to the running `tcc.exe` process. Set breakpoints in your plugin code.

 

2. **Console output** — `Console.WriteLine()` writes to TCC's standard output, which is useful for diagnostics.

 

3. **Error reporting** — If `Initialize()` returns `false`, TCC displays an error and unloads your plugin. Check TCC's output for bridge-level error messages.

 

4. **Plugin info check** — Use `PLUGIN /I YourPluginName` from the TCC command line to verify that your metadata and function list are correct.

 

5. **Reload cycle** — Unload and reload during development:

 

  plugin /u MyPlugin

  plugin /l C:\Dev\MyPlugin\bin\Debug\MyPlugin.dll

 

Naming Rules

 

| Item                    | Rule                                                                      |

|-------------------------|---------------------------------------------------------------------------|

| Internal command names  | Alphanumeric, **maximum 12 characters**                                   |

| Variable function names | **Maximum 31 characters** (excluding `@` prefix)                          |

| Internal variable names | Must start with `_`                                                       |

| Plugin `Name`           | Used as the identifier in `PLUGIN` commands and `$`-qualified invocations |

 

 

Exception Handling

 

TCC wraps all plugin invocations in an exception handler. If your method throws an unhandled exception, TCC catches it and returns `0xFEDCBA98` (not handled). Your plugin will not crash TCC, but the user will receive no output. Use try/catch in your methods for graceful error handling:

 

C#

 

public int MYCOMMAND(StringBuilder args)

{

   try

   {

       // your logic here

       return 0;

   }

   catch (Exception ex)

   {

       Console.Error.WriteLine($"MYCOMMAND error: {ex.Message}");

       return 1;

   }

}

 

API Reference

 

`TCCPluginInfo` Properties

 

Property

Type

Description

`Name`

`string`

Short name displayed by TCC (defaults to assembly filename if empty)

`Author`

`string`

Author name

`Email`

`string`

Contact email

`WWW`

`string`

Website URL

`Description`

`string`

Brief description

`Functions`

`string`

Comma-delimited list of features (see [The Functions String](#the-functions-string))

`Major`

`int`

Major version

`Minor`

`int`

Minor version

`Build`

`int`

Build number

 

`ITCCPlugin` Methods

 

Method

Returns

Description

`GetPluginInfo()`

`TCCPluginInfo`

Provide plugin metadata

`Initialize()`

`bool`

One-time setup; return `true` to continue loading

`Shutdown(bool endProcess)`

`bool`

Cleanup before unload

 

Feature Method Signatures

 

Signature

Use Case

`int Method(StringBuilder args)`

Commands, variables, functions (read-write)

`int Method(string args)`

Commands (read-only)

`int Method()`

Commands or variables with no arguments