Transport: DLL¶
In DLL transport mode, HydroSym loads your plugin as a native DLL into its own process using LoadLibrary. Communication happens through a single exported C function and a callback function pointer.
Entry point signature¶
Your DLL must export exactly one function:
__declspec(dllexport) void __cdecl HydroSymPlugin_Invoke(
const char* method,
const char* requestJson,
void (*callback)(int resultCode, const char* responseJson, void* ctx),
void* ctx
);
C# equivalent (the SDK generates this automatically from HydroSymPluginBase):
[UnmanagedCallersOnly(EntryPoint = "HydroSymPlugin_Invoke", CallConvs = [typeof(CallConvCdecl)])]
public static unsafe void Invoke(
byte* method,
byte* requestJson,
delegate* unmanaged[Cdecl]<int, byte*, void*, void> callback,
void* ctx)
{
PluginHost.Invoke(method, requestJson, callback, ctx);
}
The SDK's PluginHost handles the dispatch. You never write this code manually.
Callback signature¶
typedef void (*ResponseCallback)(
int resultCode, // 0=OK, 1=ERROR, 2=NOT_SUPPORTED
const char* responseJson,
void* ctx // same value passed to Invoke
);
The plugin must call the callback exactly once per Invoke call, before Invoke returns.
Memory ownership rules¶
┌────────────────────────────────────────────────────┐
│ HydroSym owns: │
│ method → valid until Invoke returns │
│ requestJson → valid until Invoke returns │
│ ctx → opaque, pass back unchanged │
├────────────────────────────────────────────────────┤
│ Plugin owns: │
│ responseJson → must remain valid until │
│ the callback returns │
└────────────────────────────────────────────────────┘
Do not hold pointers to method or requestJson after Invoke returns. Copy them if you need them later. The SDK copies both before returning from Invoke.
The response JSON buffer must outlive the callback call. The SDK manages this with a pinned buffer that is released after the callback returns.
Threading model¶
- HydroSym calls
Invokefrom its main UI thread. - The callback must be called from the same thread that called
Invoke(i.e., beforeInvokereturns). - Async operations (database calls, HTTP requests) are run on thread pool threads internally, but the result is marshaled back to satisfy the synchronous callback requirement.
- Do not hold any UI lock (e.g.,
Application.DoEventsloops) that could cause a deadlock while waiting for an async operation.
Calling convention¶
The entry point uses __cdecl calling convention (not stdcall). This is the default for C functions on x64 Windows. NativeAOT with [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] produces the correct convention.
stdcall mismatch
Using stdcall (the old TFMS_ convention) with the new entry point causes a stack corruption crash on 64-bit Windows. Always use cdecl / UnmanagedCallersOnly.
String encoding¶
All strings (method, requestJson, responseJson) are null-terminated UTF-8. JSON strings within the payload may contain Unicode characters (escaped as \uXXXX per RFC 8259, or as raw UTF-8 bytes). The SDK handles encoding transparently.
Debugging¶
To debug a plugin in Visual Studio:
- Open the plugin project in Visual Studio.
- Set Debugger Type to Native Only in project properties (required for NativeAOT).
- Start HydroSym normally (or use the
HydroSymPluginTester.exefor faster iteration). - Debug → Attach to Process → select
HydroSym.exe. - Set breakpoints in your plugin code — they will be hit when HydroSym calls your plugin.
For HydroSymPluginTester:
1. Launch HydroSymPluginTester.exe --dll YourPlugin.dll --call GetInfo from Visual Studio's debugger (set it as the startup project with command-line arguments).
Common pitfalls¶
| Problem | Cause | Fix |
|---|---|---|
EntryPointNotFoundException on load |
Entry point missing or wrong name | Check dumpbin /exports YourPlugin.dll for HydroSymPlugin_Invoke |
| Crash on first call | Wrong calling convention | Ensure [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] |
| Garbled JSON response | String encoding mismatch | Let the SDK handle UTF-8 conversion; never use Marshal.StringToHGlobalAnsi |
| Plugin loads but nothing happens | Capability not listed | Add the capability to GetInfo.capabilities |
| Deadlock on database call | Async/await on wrong scheduler | Use ConfigureAwait(false) on all awaits in plugin code |