-
Notifications
You must be signed in to change notification settings - Fork 0
Home
This document is the authoritative integration guide for application developers embedding V2Root Core in desktop, service, command-line, or mobile-adjacent software.
V2Root Core packages Xray-core as a native C-compatible shared library. The library is designed specifically for V2Root-App and the V2Root ecosystem, but its ABI can be consumed by any language capable of calling a native C library.
- Scope and architecture
- Supported release packages
- ABI and memory ownership
- Loading the library
- Lifecycle and process model
- Complete API reference
- Parser input schema
- Configuration examples
- Language integration examples
- Concurrency and thread safety
- Error handling
- Geo assets
- Logging and observability
- Security guidance
- Production integration checklist
V2Root Core exposes three functional layers:
| Layer | Purpose |
|---|---|
| Runtime | Start and stop one embedded Xray instance and query its state |
| Configuration | Parse share URIs, validate Xray JSON, and convert JSON back to a share URI |
| Operations | Test latency, query traffic, configure logs, download geo assets, and inspect build metadata |
The library is built with Go -buildmode=c-shared. Every release ZIP contains:
- One native shared library (
.dllor.so) - The generated C header for that exact build
- An internal
SHA256SUMSfile covering the binary and header
The embedded runtime is process-global. Loading the same library multiple times inside one process does not create independent Xray engines. Applications that need isolated concurrent engines must use separate operating-system processes.
| Package | Binary |
|---|---|
V2Root-Core-windows-amd64-<version>.zip |
xray-windows-amd64.dll |
V2Root-Core-windows-386-<version>.zip |
xray-windows-386.dll |
V2Root-Core-linux-amd64-<version>.zip |
xray-linux-amd64.so |
V2Root-Core-linux-arm64-<version>.zip |
xray-linux-arm64.so |
V2Root-Core-linux-386-<version>.zip |
xray-linux-386.so |
Always ship the binary and header from the same ZIP. Do not combine a header from one release or architecture with a binary from another.
Verify the package before installation:
sha256sum -c SHA256SUMSAll input char* values are expected to be UTF-8, null-terminated strings. All
returned textual values are UTF-8, null-terminated strings.
Most functions returning char* allocate memory using the library's C
allocator. The caller must release every non-null returned pointer with
FreeCString.
char *value = GetStatus();
if (value != NULL) {
printf("%s\n", value);
FreeCString(value);
}Do not release returned pointers with:
- C or C++
free - Windows
LocalFree,HeapFree, orCoTaskMemFree - A language runtime allocator
- A garbage collector finalizer that may execute after the library is unloaded
Use only FreeCString.
This distinction is important:
| Result | Meaning |
|---|---|
Null pointer from Start or Stop
|
Success |
Non-null pointer from Start or Stop
|
Error text; release it with FreeCString
|
Non-null pointer containing "" from parser/conversion APIs |
Parsing or conversion failed |
Parser APIs normally return an allocated empty C string on failure, not a null pointer. That empty result must still be released.
TestLatency receives a C int timeout measured in seconds. Build-generated
headers are the source of truth for exact platform declarations.
Place the DLL beside the executable or load it by absolute path. The process architecture must match the DLL:
- 64-bit process:
xray-windows-amd64.dll - 32-bit process:
xray-windows-386.dll
Load the .so by absolute path or configure the dynamic loader:
export LD_LIBRARY_PATH="/opt/v2root/lib:${LD_LIBRARY_PATH}"Applications may also install the library under an application-private
directory and use dlopen directly.
Include the generated header:
#include "xray-linux-amd64.h"The exported API is intentionally flat and C-compatible. Do not call internal Go symbols or depend on symbol names not present in the generated header.
The runtime follows this logical state machine:
STOPPED -> STARTING -> RUNNING -> STOPPING -> STOPPED
Current public status strings are:
STOPPEDSTARTINGRUNNINGSTOPPING
Only one server instance can run at a time. Calling Start while the instance
is running returns server already running.
Recommended lifecycle:
- Load the library.
- Call
GetVersionInfo. - Configure logging with
SetLogLeveland optionallySetLogOutput. - Call
ValidateConfig. - Call
Start. - Poll
GetStatusif the UI needs state synchronization. - Query traffic or speed only while running.
- Call
Stopbefore unloading the library or terminating the host process. - Release every returned string.
Start launches Xray asynchronously. A successful null return means the Xray
instance was created and startup was scheduled. Applications that require a
readiness check should poll GetStatus and verify their local proxy port before
declaring the connection usable.
void FreeCString(char *value);Releases any non-null string returned by V2Root Core. Passing null is safe.
char *GetStatus(void);Returns the current process-global runtime state.
Example result:
RUNNING
The result is allocated and must be released.
void SetLogOutput(char *path);Sets both Xray access and error log destinations to the supplied path. An empty path does not clear a previously configured path.
Call this before Start. The path must be writable by the host process.
void SetLogLevel(char *level);Sets the Xray loglevel value applied at startup. Typical values are:
debuginfowarningerrornone
Call this before Start.
char *Start(char *configInput, char *optionsJSON);Starts the process-global Xray runtime.
configInput accepts one of:
- A complete Xray JSON object
- A filesystem path to a JSON configuration
- A
vless://share URI - A
vmess://share URI - A
trojan://share URI - An
ss://share URI
optionsJSON accepts:
{
"geositeFile": "/absolute/path/geosite.dat",
"geositePath": "/absolute/path/geosite.dat"
}geositeFile is strict: if supplied but missing, startup fails. geositePath
is treated as optional.
Return contract:
- Null: startup accepted
- Non-null: human-readable error
Internally, Start:
- Loads or parses the input.
- Migrates legacy
dnsConfigtodns. - Applies configured logging.
- Creates a loopback-only StatsService port.
- Adds API inbound, outbound, and routing entries.
- Enables system traffic statistics.
- Normalizes key routing and outbound fields.
- Asks Xray-core to load the finalized JSON.
- Starts Xray asynchronously.
char *Stop(void);Stops the active engine and clears runtime statistics state.
Return contract:
- Null: stop completed
-
server not running: no active instance existed
Calling Stop repeatedly is safe if the caller handles the second call as a
non-fatal state result.
char *ValidateConfig(char *configInput, char *optionsJSON);Accepts the same configuration forms as Start, applies the same major
normalization rules, and asks Xray-core to load the final configuration without
starting a persistent engine.
Success:
{"result":"valid"}Failure:
{"error":"config validation failed: ..."}Always parse the result as JSON and test for error; do not compare the entire
string because error details may change with Xray-core.
char *TestLatency(char *configsJSON, char *testURL, int timeout);Tests configurations concurrently through temporary local HTTP/SOCKS proxy instances.
configsJSON can be:
- A JSON array of configuration strings
- A single raw JSON configuration string
- A single supported share URI
Example:
[
"vless://...",
"trojan://..."
]testURL defaults to https://www.gstatic.com/generate_204 when empty.
timeout is in seconds.
The return value is a JSON array of strings preserving input order:
["143","287","[ERROR]failed to load config: ..."]A numeric string is latency in milliseconds. Failure values include ERROR,
-1, or strings beginning with [ERROR]. Treat every non-numeric item as a
failed measurement.
Latency testing requires an HTTP or SOCKS inbound.
char *GetTotalTraffics(void);Returns cumulative non-API outbound traffic while the engine is running:
{
"uplink": 1048576,
"downlink": 8388608
}Values are bytes.
When stopped:
{"error":"server is not running"}char *GetRealtimeSpeed(void);Returns transfer rates calculated from the difference between consecutive calls:
{
"uplinkSpeed": 12500.4,
"downlinkSpeed": 84000.2
}Values are bytes per second. The first successful call establishes a baseline and normally returns zero. Poll at a stable interval such as one second. Very frequent calls below approximately 100 ms do not produce useful measurements.
char *UpdateGeoAssets(char *assetPath);Downloads:
geosite.datgeoip.dat
The source is the latest
Loyalsoldier/v2ray-rules-dat GitHub release. Downloads run concurrently.
If assetPath is empty, the target is a Resources directory beside the host
executable.
Example:
{
"geosite.dat":"geosite.dat",
"geoip.dat":"geoip.dat"
}Partial failure is reported per file:
{
"geosite.dat":"failed: ...",
"geoip.dat":"geoip.dat"
}This operation performs network and filesystem I/O. Run it off the UI thread.
char *GetVersionInfo(void);Returns build metadata injected by the release pipeline:
{
"codeVersion": 12,
"version": "v26.3.27",
"releaseDate": "2026-06-15 03:00:00 +0330"
}version is the upstream Xray release tag. releaseDate is the build timestamp
in Tehran time. codeVersion is the GitHub Actions run number used for that
binary.
char *Parse(char *optionsJSON);Auto-detects VLESS, VMess, Trojan, or Shadowsocks from the uri field and
returns formatted Xray JSON. It is the recommended parser entry point.
On failure it returns an allocated empty string.
char *ParseVless(char *optionsJSON);Parses a VLESS URI from optionsJSON.uri. It supports TCP, WebSocket, HTTP,
QUIC, mKCP, gRPC, TLS, and REALITY share parameters. It returns Xray JSON or an
allocated empty string on failure.
char *ParseTrojan(char *optionsJSON);Parses a Trojan URI from optionsJSON.uri. TLS is the default security mode
when the URI omits security. It returns Xray JSON or an allocated empty string
on failure.
char *ParseVmess(char *optionsJSON);Parses the base64-encoded JSON payload of a VMess URI. It supports the transport fields described later in this guide and returns Xray JSON or an allocated empty string on failure.
char *ParseShadowsocks(char *optionsJSON);Parses standard Shadowsocks URIs and supported v2ray-plugin WebSocket/TLS
options. It returns Xray JSON or an allocated empty string on failure.
All protocol-specific parsers use the same options schema as Parse. Use them
when the protocol is already known and protocol mismatch should fail
immediately.
char *JSONToConfigString(char *configJSON);Converts the first eligible outbound to a share URI. It prefers an outbound
tagged Proxy; otherwise it chooses the first outbound not tagged Direct or
Reject, then falls back to the first outbound.
Supported output schemes:
vless://vmess://trojan://ss://
An unsupported or incomplete configuration returns an allocated empty string. The conversion is intentionally lossy for Xray fields that have no equivalent share-URI representation.
The complete parser options object is:
{
"uri": "vless://...",
"httpPort": 10809,
"socksPort": 10808,
"routingMode": "proxy",
"geositePath": "/opt/v2root/geosite.dat",
"geositeFile": "/opt/v2root/geosite.dat",
"geositeDomain": "ir",
"geositeDNS": "1.1.1.1",
"dnsConfig": {
"servers": ["1.1.1.1", "8.8.8.8"],
"clientIp": "",
"tag": "",
"strategy": "UseIP"
},
"geositeRules": [
{
"domain": "ads",
"action": "block"
},
{
"domain": "ir",
"action": "direct"
}
],
"geoipRules": [
{
"country": "private",
"action": "direct"
},
{
"country": "cn",
"outboundTag": "CustomOutbound"
}
]
}| Option | Default |
|---|---|
httpPort |
10809 |
socksPort |
10808 |
routingMode |
proxy in Parse; protocol-specific functions pass an empty value if omitted |
| VLESS/Trojan network | tcp |
| VLESS security | none |
| Trojan security | tls |
Ports must fit in the unsigned 16-bit range. Applications should validate
1..65535 before calling the library.
| Action | Resulting outbound |
|---|---|
proxy |
Proxy |
direct |
Direct |
block or reject
|
Reject |
outboundTag supplied |
Exact custom tag |
Recognized geosite aliases include:
ads, porn, media, anticensorship, vpn, games, dev, ai,
malware, phishing, messaging, cn, not-cn, private, win-spy, and
win-update.
The parser understands common share-URI fields for:
- TCP, including HTTP header camouflage
- WebSocket
- HTTP transport
- QUIC
- mKCP
- gRPC
- TLS
- REALITY
- Shadowsocks
v2ray-pluginWebSocket/TLS options
For REALITY, the public key (pbk) is required.
{
"uri": "vless://UUID@example.com:443?encryption=none&security=tls&sni=example.com&type=ws&path=%2Fws#Example",
"httpPort": 10809,
"socksPort": 10808
}{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"udp": true
}
}
],
"outbounds": [
{
"tag": "Proxy",
"protocol": "freedom",
"settings": {}
}
]
}static char *copy_and_release(char *value) {
if (value == NULL) {
return NULL;
}
char *copy = strdup(value);
FreeCString(value);
return copy;
}
char *validation_ptr = ValidateConfig(config_json, "{}");
char *validation = copy_and_release(validation_ptr);
if (validation == NULL) {
/* Unexpected: ValidateConfig normally returns JSON. */
} else {
/* Parse validation as JSON and inspect "error" or "result". */
free(validation);
}
char *start_error = Start(config_json, "{}");
if (start_error != NULL) {
fprintf(stderr, "Start failed: %s\n", start_error);
FreeCString(start_error);
}import ctypes
import json
lib = ctypes.CDLL("./xray-linux-amd64.so")
lib.FreeCString.argtypes = [ctypes.c_void_p]
lib.FreeCString.restype = None
lib.GetVersionInfo.argtypes = []
lib.GetVersionInfo.restype = ctypes.c_void_p
lib.ValidateConfig.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
lib.ValidateConfig.restype = ctypes.c_void_p
lib.Start.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
lib.Start.restype = ctypes.c_void_p
lib.Stop.argtypes = []
lib.Stop.restype = ctypes.c_void_p
def take_string(pointer):
if not pointer:
return None
try:
return ctypes.string_at(pointer).decode("utf-8")
finally:
lib.FreeCString(pointer)
version = json.loads(take_string(lib.GetVersionInfo()))
print(version)
config = json.dumps({
"inbounds": [],
"outbounds": [{"protocol": "freedom", "tag": "Proxy", "settings": {}}],
}).encode()
validation = json.loads(
take_string(lib.ValidateConfig(config, b"{}"))
)
if "error" in validation:
raise RuntimeError(validation["error"])
start_error = take_string(lib.Start(config, b"{}"))
if start_error is not None:
raise RuntimeError(start_error)
try:
# Application work
pass
finally:
stop_error = take_string(lib.Stop())
if stop_error is not None:
print(f"Stop warning: {stop_error}")using System;
using System.Runtime.InteropServices;
internal static class V2RootNative
{
private const string Library = "xray-windows-amd64.dll";
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr GetVersionInfo();
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ValidateConfig(
[MarshalAs(UnmanagedType.LPUTF8Str)] string config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string options);
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr Start(
[MarshalAs(UnmanagedType.LPUTF8Str)] string config,
[MarshalAs(UnmanagedType.LPUTF8Str)] string options);
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr Stop();
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
internal static extern void FreeCString(IntPtr value);
internal static string? TakeString(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
return null;
try
{
return Marshal.PtrToStringUTF8(pointer);
}
finally
{
FreeCString(pointer);
}
}
}Do not declare a returned string directly as C# string; doing so prevents the
application from reliably returning ownership through FreeCString.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
typedef _GetStatusNative = Pointer<Utf8> Function();
typedef _GetStatusDart = Pointer<Utf8> Function();
typedef _FreeNative = Void Function(Pointer<Utf8>);
typedef _FreeDart = void Function(Pointer<Utf8>);
final library = DynamicLibrary.open('xray-windows-amd64.dll');
final getStatus =
library.lookupFunction<_GetStatusNative, _GetStatusDart>('GetStatus');
final freeCString =
library.lookupFunction<_FreeNative, _FreeDart>('FreeCString');
String takeString(Pointer<Utf8> pointer) {
if (pointer == nullptr) {
throw StateError('Unexpected null native string');
}
try {
return pointer.toDartString();
} finally {
freeCString(pointer);
}
}
final status = takeString(getStatus());Perform blocking calls such as UpdateGeoAssets and large latency batches away
from the Flutter UI isolate.
#include <memory>
#include <string>
struct V2RootStringDeleter {
void operator()(char* value) const noexcept {
FreeCString(value);
}
};
using V2RootString = std::unique_ptr<char, V2RootStringDeleter>;
std::string get_status() {
V2RootString value(GetStatus());
return value ? std::string(value.get()) : std::string();
}Start, Stop, and GetStatus synchronize access to global runtime state.
Do not design the host application around concurrent start/stop races even
though internal locking exists. Serialize lifecycle commands in the application
layer.
Realtime speed baseline state is protected internally, but concurrent callers
share the same baseline. For stable UI metrics, designate one polling task as
the sole caller of GetRealtimeSpeed.
Parser calls do not use the lifecycle mutex. They may set the process-wide
XRAY_LOCATION_ASSET environment variable when geo paths are supplied.
Therefore, concurrent parser calls using different asset directories should be
serialized.
TestLatency runs configurations concurrently and may consume significant CPU,
memory, sockets, and file descriptors. Limit batch size in user-facing
applications.
The ABI currently exposes no callbacks. Poll status and statistics from the host application.
V2Root Core currently uses three error styles:
| API family | Error representation |
|---|---|
Start, Stop
|
Non-null human-readable string |
| JSON operational APIs | JSON object containing error
|
| Parser/conversion APIs | Empty string |
TestLatency |
Per-item string such as [ERROR]..., ERROR, or -1
|
Recommended wrapper strategy:
- Convert and free the native result immediately.
- Convert each API family to one host-language exception/result type.
- Preserve the original native message for diagnostics.
- Avoid matching complete error text.
- Log version metadata with every failure report.
Do not include share URIs, UUIDs, passwords, private keys, or complete configurations in telemetry unless the user explicitly opts in.
Routing rules using geosite: or geoip: require compatible asset files.
Recommended application layout:
V2RootApp/
bin/
xray-windows-amd64.dll
resources/
geosite.dat
geoip.dat
Call UpdateGeoAssets(resourcesPath) during an explicit update operation, not
on every startup. Downloads are currently replaced directly at the destination;
applications requiring transactional updates should download through their own
updater, verify files, and atomically replace the active assets.
The library sets XRAY_LOCATION_ASSET when a valid geo path is provided. This
is a process-wide environment setting.
Set logging before startup:
SetLogLevel("warning");
SetLogOutput("/var/log/v2root/xray.log");Operational recommendations:
- Rotate log files in the host application or operating system.
- Restrict log directory permissions.
- Avoid
debugin production unless diagnosing a specific problem. - Record
GetVersionInfoin support bundles. - Record status transitions and sanitized error strings.
- Poll total traffic less frequently than realtime speed.
- Treat all imported share URIs and JSON as untrusted input.
- Call
ValidateConfigbeforeStart. - Bind proxy inbounds to loopback unless remote access is intentional.
- Protect geo asset and log directories from untrusted modification.
- Never expose the internal StatsService port; the library binds it to
127.0.0.1. - Do not unload the shared library while its runtime or native calls are active.
- Verify release ZIP checksums before distribution.
- Keep the DLL/SO architecture aligned with the host process.
- Store secrets outside configuration logs and crash reports.
- Select the correct OS and architecture ZIP.
- Verify the ZIP's internal
SHA256SUMS. - Ship the matching generated header for native builds.
- Use an absolute library path where possible.
- Declare C calling conventions exactly as generated.
- Marshal all text as UTF-8.
- Treat null and empty strings differently.
- Call
FreeCStringexactly once for every non-null returned string. - Copy native text before releasing its pointer.
- Configure logging before startup.
- Validate before starting.
- Serialize
StartandStop. - Prevent multiple engines in one process.
- Stop before unloading or process shutdown.
- Poll speed from one task at a stable interval.
- Run network and latency operations off the UI thread.
- Show actionable validation errors without exposing credentials.
- Distinguish stopped, starting, running, and stopping states.
- Make geo asset updates explicit and observable.
- Include
GetVersionInfoin support reports. - Include OS, process architecture, and package name.
- Include sanitized validation/start errors.
- Never include full credentials or share URIs by default.
Release binaries are built against a specific upstream Xray tag. The
GetVersionInfo.version field identifies that tag. Consumers should:
- Prefer capability detection over assumptions.
- Keep the generated header synchronized with the binary.
- Run integration tests before adopting a new release.
- Treat undocumented symbols and internal JSON normalization as implementation details.
The public ABI is represented by the generated header and the functions documented here. Changes to return schemas or ownership rules should be treated as integration-significant even if function names remain unchanged.