AdsSymbolicServer Class

AdsServer class that supports Symbolic information and Notifications. Implements the AdsServer

Inheritance Hierarchy

System.Object
  TwinCAT.Ads.Server.AdsServer
    TwinCAT.Ads.Server.AdsSymbolicServer
Namespace:  TwinCAT.Ads.Server
Assembly:  TwinCAT.Ads.SymbolicServer (in TwinCAT.Ads.SymbolicServer.dll) Version: 6.0.328+39e3229

Syntax

C#

public abstract class AdsSymbolicServer : AdsServer, 
    ISymbolProvider, ISymbolServer

The AdsSymbolicServer type exposes the following members.

Properties

 

Name

Description

AmsServer

Gets the the internal AmsServer object. (Inherited from AdsServer.)

DataTypes

Gets the data types

DefaultValueEncoding

Gets/Sets the default value encoding.

InstancePathEncoding

Gets or sets the instance path encoding (Default is the Windows CodePage)

IsConnected

Gets a value indicating whether AdsServer is connected. (Inherited from AdsServer.)

IsDisconnecting

Indicates, that the AdsServer is actually disconnecting. (Inherited from AdsServer.)

IsDisposed

Gets a value indicating whether this instance is disposed. (Inherited from AdsServer.)

IsDynamicPort

Gets a value indicating this AdsServer has a dynamic/unfixed port. (Inherited from AdsServer.)

Logger

Gets the logger object. (Inherited from AdsServer.)

PlatformPointerSize

Gets the Platform pointer size of this Server

RootNamespaceName

Gets the name of the root namespace

ServerAddress

The AMS address of this server. (Inherited from AdsServer.)

ServerName

Gets the name of the server. (Inherited from AdsServer.)

ServerPort

Gets the server port. (Inherited from AdsServer.)

ServerVersion

Gets the Version of the Server. (Inherited from AdsServer.)

Symbols

Gets the symbols.

Methods

 

Name

Description

AddDeviceNotificationRequest

Sends an ADS Add Device Notification request (synchronous). (Inherited from AdsServer.)

AddDeviceNotificationRequestAsync

Sends an ADS Add Device Notification request (async) (Inherited from AdsServer.)

AddDeviceNotificationResponseAsync

Sends an ADS Add Device Notification response. (Inherited from AdsServer.)

ConnectServer

Connect this ADS server to the local ADS router. (Inherited from AdsServer.)

ConnectServerAndWaitAsync

Registers the AdsServer at the router asynchronously. (Inherited from AdsServer.)

DeleteDeviceNotificationRequest

Sends an ADS Delete Device Notification request (synchronous). (Inherited from AdsServer.)

DeleteDeviceNotificationRequestAsync

Sends an ADS Delete Device Notification request (async). (Inherited from AdsServer.)

DeleteDeviceNotificationResponseAsync

Sends an ADS Delete Device Notification response. (Inherited from AdsServer.)

DeviceNotificationRequestAsync

Sends an ADS Device Notification request asynchronously (Inherited from AdsServer.)

DeviceNotificationRequestSync

Sends an ADS Device Notification request (sync) (Inherited from AdsServer.)

Disconnect

Disconnects this ADS server from the local ADS router. (Inherited from AdsServer.)

Dispose.

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. (Inherited from AdsServer.)

Dispose(Boolean)

Releases unmanaged and - optionally - managed resources. (Inherited from AdsServer.)

Equals

Determines whether the specified object is equal to the current object. (Inherited from Object.)

Finalize

Finalizes an instance of the AdsServer class. (Inherited from AdsServer.)

FireNotificationAsync

Fires a single notification to the specified Address.

FireNotifications

Fires outstanding cyclic notifications.

FireNotificationsAsync

Check internal tables for outstanding cyclic Notifications and fire them asynchronously.

GetDataTypesAsync

Gets the data types asynchronously.

GetHashCode

Serves as the default hash function. (Inherited from Object.)

GetServerName

Gets the name of the server. (Inherited from AdsServer.)

GetSymbolsAsync

Gets the symbols asynchronously

GetType

Gets the Type of the current instance. (Inherited from Object.)

HasValueChanged

Determines whether the value of the Symbol has changed.

MemberwiseClone

Creates a shallow copy of the current Object. (Inherited from Object.)

OnAddDeviceNotificationAsync

Called when an ADS Add Device Notification indication is received. (Overrides AdsServer.OnAddDeviceNotificationAsync(AmsAddress, UInt32, UInt32, UInt32, Int32, AmsAddress, NotificationSettings, CancellationToken).)

OnAddDeviceNotificationConfirmationAsync

Called when an ADS Add Device Notification confirmation is received. (Inherited from AdsServer.)

OnBeforeConnected

Handler function that is called, when the AdsServer is connected, but before calling OnConnected.. (Overrides AdsServer.OnBeforeConnected..)

OnConnected

Handler function that is called, when the AdsServer is connected. (Overrides AdsServer.OnConnected..)

OnCreateSymbols

Handler function to create the Initial Symbols of the server.

OnDeleteDeviceNotificationAsync

Called when a DeleteDeviceNotification request is indicated. (Overrides AdsServer.OnDeleteDeviceNotificationAsync(AmsAddress, UInt32, UInt32, CancellationToken).)

OnDeleteDeviceNotificationConfirmationAsync

Called when an ADS Delete Device Notification confirmation is received. (Inherited from AdsServer.)

OnDisconnect

Called when the AdsServer is about to be disconnected. (Overrides AdsServer.OnDisconnect..)

OnFireNotifications

Handler function for fireing notifications to the specified targets.

OnFireNotificationsAsync

Handler function for firering notifications to the specified targets.

OnGetValue

Handler function to determine the (stored) value of the symbol from the internal caches of the AdsSymbolicServer.

OnReadAsync

Called when an ADS Read indication is received. (Overrides AdsServer.OnReadAsync(AmsAddress, UInt32, UInt32, UInt32, Int32, CancellationToken).)

OnReadConfirmationAsync

Called when an ADS Read confirmation is received. (Inherited from AdsServer.)

OnReadDeviceInfoConfirmationAsync

Called when an ADS Read Device Info confirmation is received. (Inherited from AdsServer.)

OnReadDeviceStateAsync

Called when an ADS Read State indication is received. (Inherited from AdsServer.)

OnReadDeviceStateConfirmationAsync

Called when an ADS Read State confirmation is received. (Inherited from AdsServer.)

OnReadIgIoAsync

Handler function for handling pure IG/IO Read indications

OnReadRawValue

Handler function for Reading the internal value data in raw format.

OnReadWriteAsync

Called when an ADS Read Write indication is received. (Overrides AdsServer.OnReadWriteAsync(AmsAddress, UInt32, UInt32, UInt32, Int32, ReadOnlyMemory.Byte., CancellationToken).)

OnReadWriteConfirmationAsync

Called when an ADS Read Write confirmation is received. (Inherited from AdsServer.)

OnReadWriteIgIoAsync

Handler function for handling of pure IG/IO calls.

OnRouterNotification

Handler Function for a Router Notification. (Inherited from AdsServer.)

OnRpcInvoke

Handler function called when an RpcInvoke occurs.

OnServerConnectionStateChanged

Handles the ServerConnectionStateChanged event. (Inherited from AdsServer.)

OnSetValue

Handler function to store a new Symbol value within internal caches of the AdsSymbolicServer.

OnWriteAsync

Called when an ADS Write indication is received. (Overrides AdsServer.OnWriteAsync(AmsAddress, UInt32, UInt32, UInt32, ReadOnlyMemory.Byte., CancellationToken).)

OnWriteConfirmationAsync

Called when an ADS Write confirmation is received. (Inherited from AdsServer.)

OnWriteControlAsync

Called when an ADS Write Control indication is received. (Inherited from AdsServer.)

OnWriteControlConfirmationAsync

Called when an ADS Write Control confirmation is received. (Inherited from AdsServer.)

OnWriteIgIoAsync

Handler function for handling of pure Write IG/IO Indications.

OnWriteRawValue

Handler function for writing the symbol value data in raw format.

ReadDeviceInfoRequestAsync

Sends an ADS Read Device Info request asynchronously (Inherited from AdsServer.)

ReadDeviceInfoRequestSync

Sends an ADS Read Device Info request synchronously. (Inherited from AdsServer.)

ReadDeviceInfoResponseAsync

Sends an ADS Read Device Info response. (Inherited from AdsServer.)

ReadDeviceStateRequestAsync

Sends an ADS Read State request (asynchronous) (Inherited from AdsServer.)

ReadDeviceStateRequestSync

Sends an ADS Read State request (synchronous) (Inherited from AdsServer.)

ReadDeviceStateResponseAsync

Sends an ADS Read State response. (Inherited from AdsServer.)

ReadRequest

Sends an ADS Read Request. (Inherited from AdsServer.)

ReadRequestAsync

Sends an ADS Read Request asynchronously. (Inherited from AdsServer.)

ReadResponseAsync

Sends an ADS Read response. (Inherited from AdsServer.)

ReadWriteRequestAsync

Sends an ADS Read Write request. (Inherited from AdsServer.)

ReadWriteRequestSync

Sends an ADS Read Write request synchronously (Inherited from AdsServer.)

ReadWriteResponseAsync

Sends an ADS Read Write Response. (Inherited from AdsServer.)

ResetCachedSymbolicData

Resets the cached symbolic data.

SetValue(ISymbol, Object)

Sets the value of the symbol.

SetValue(String, Object)

Sets the value of the symbol.

ToString

Returns a string that represents the current object. (Inherited from Object.)

TryGetDataTypes

Tries to get the symbols from the device target.

TryGetSymbols

Tries to geth the symbols from the device target.

WriteControlRequest

Sends an ADS Write Control request (synchronous) (Inherited from AdsServer.)

WriteControlRequestAsync

Sends an ADS Write Control request (asynchronous). (Inherited from AdsServer.)

WriteControlRequestSync

Sends an ADS Write Control request (synchronous). (Inherited from AdsServer.)

WriteControlResponseAsync

Sends an ADS Write Control response. (Inherited from AdsServer.)

WriteRequest

Sends an ADS Write request synchronously. (Inherited from AdsServer.)

WriteRequestAsync

Sends an ADS Write request asynchronously. (Inherited from AdsServer.)

WriteResponseAsync

Sends an ADS Write response. (Inherited from AdsServer.)

Events

 

Name

Description

ServerConnectionStateChanged

The connection status has changed (Inherited from AdsServer.)

Fields

 

Name

Description

handleTable

The handle table (InstancePath --> Symbol handle)

notificationTable

The notification table

notificationTrigger

The notification trigger Source

serverVersion

The version of the AdsServer (Inherited from AdsServer.)

symbolFactory

The symbol factory

symbolNameMarshaler

The SymbolName marshaler

symbolVersion

The symbol version as respond by SymbolVersion

Remarks

DataTypes and hierarchical trees of symbols can be downloaded from this by symbolic information access. The symbolic access is necessary for the following functionality:

This symbolic access is for example used by the following TwinCAT tools:

Examples

The following sample shows how to derive from the AdsServer class and create your own Customized ADS Server.

C#

public class SymbolicTestServer : AdsSymbolicServer
{
    /// <summary>
    /// AmsPort of the SymbolicTestServer
    /// </summary>
    /// <remarks>
    /// User Server Ports must be in between
    /// AmsPort.CUSTOMER_FIRST (25000) <= PORT <= AmsPort.CUSTOMER_LAST (25999)
    /// to not conflict with Beckhoff prereserved servers!
    /// </remarks>
    static ushort s_Port = 25000;

    /// <summary>
    /// Dictionary containing the Values of the Symbols (Symbol -- Value)
    /// </summary>
    Dictionary<ISymbol, object> _symbolValues = new Dictionary<ISymbol, object>();

    /// <summary>
    /// Gets the SymbolValue Dictionary of the <see cref="SymbolicTestServer"/>.
    /// </summary>
    /// <value>The symbol values.</value>
    public IDictionary<ISymbol, object> SymbolValues => _symbolValues;

    /// <summary>
    /// Symbolic Marshaler for Values
    /// </summary>
    SymbolicAnyTypeMarshaler _symbolMarshaler = new SymbolicAnyTypeMarshaler();

    /// <summary>
    /// Initializes a new instance of the <see cref="SymbolicTestServer"/> class.
    /// </summary>
    public SymbolicTestServer()
    : base(s_Port, "SymbolicTestServer", null)
    {
    /// AMS Router enpoint can be changed via envrionment variables which is
    /// benefitial in containerized setups where the AMS router is not listening
    /// at the default loopback address and port 
    IPAddress ipEndpoint;
    if (!IPAddress.TryParse(System.Environment.GetEnvironmentVariable("ENV_AmsConfiguration__LoopbackAddress"), out ipEndpoint))
    {
        ipEndpoint = IPAddress.Loopback;
    }

    int port;
    if (!int.TryParse(System.Environment.GetEnvironmentVariable("ENV_AmsConfiguration__LoopbackPort"), out port))
    {
        port = 48898;
    }

    AmsConfiguration.RouterEndPoint = new IPEndPoint(ipEndpoint, port);
    }

    IDisposable _changeValueObserver = null;

    /// <summary>
    /// Called when [connected].
    /// </summary>
    protected override void OnConnected()
    {
    this.AddSymbols()
        .AddNotificationTrigger();

    // An Observable.Interval is used to simulate changed values (on a 1 Second base)
    IObservable<long> changeValueTrigger = Observable.Interval(TimeSpan.FromSeconds(1.0), Scheduler.Default);
    _changeValueObserver = changeValueTrigger.Subscribe(toggleValues);
    base.OnConnected();
    }

    protected override bool OnDisconnect()
    {
    if (_changeValueObserver != null)
        _changeValueObserver.Dispose();

    return base.OnDisconnect();
    }

    private void toggleValues(long count)
    {
    SetValue("Globals.int1", (short)count);
    }

    /// <summary>
    /// Creates an Notification trigger for the Notifications base tick.
    /// </summary>
    private SymbolicTestServer AddNotificationTrigger()
    {
    base.notificationTrigger.Add(new BaseTickTrigger(TimeSpan.FromMilliseconds(100)));
    return this;
    }

    /// <summary>
    /// Create the Symbolic information DataAreas, DataTypes and Symbols.
    /// </summary>
    private SymbolicTestServer AddSymbols()
    {
    // Create some Primitive types
    PrimitiveType dtBool = new PrimitiveType("BOOL", typeof(bool)); // 1-Byte size
    PrimitiveType dtInt = new PrimitiveType("INT", typeof(short)); // 2-Byte size
    PrimitiveType dtDInt = new PrimitiveType("DINT", typeof(int)); // 4-Byte size

    // Create an TwinCAT specific Type PCCH (for testing purposes)
    // Which is used for interop to C++ TCOM Modules
    // It defines a pointer to an UTF8 String, where the Length of the data is  specified
    // With a LengthIs parameter
    PrimitiveType dtByte = new PrimitiveType("BYTE", typeof(byte)); // Necessary for the PCCH Type (which is in fact an Alias to POINTER TO BYTE)!
    PCCHType dtPCCH = PCCHType.Create();

    // StringType (fixed size)
    StringType dtString = new StringType(80, Encoding.Unicode);

    // Struct Type
    // Associated to this struct Type, we use a corresponding 'ValueType' within our ServerValues
    // to hold the StructInstance value. This class/value is used to marshal/unmarshal struct types.
    // See the C# class 'MyStruct' at the bottom of this example. 
    StructType dtStruct = new StructType("MYSTRUCT", null)
        // Add Members
        .AddAligned(new Member("name", dtString))
        .AddAligned(new Member("a", dtBool))
        .AddAligned(new Member("b", dtInt))
        .AddAligned(new Member("c", dtDInt));


    // Create an RPC Invokable Type
    // Firstly the methods ...

    // INT Method1([in] INT i1, [in] i2)
    RpcMethod rpc1 = new RpcMethod("Method1")
        //Add Parameters
        .AddParameter("i1", dtInt, MethodParamFlags.In)
        .AddParameter("i2", dtInt, MethodParamFlags.In)
        //Set Return Type
        .SetReturnType(dtInt);

    // INT Method2([in] INT in1, [out] INT out1)
    RpcMethod rpc2 = new RpcMethod("Method2")
        .AddParameter("in1", dtInt, MethodParamFlags.In)
        .AddParameter("out1", dtInt, MethodParamFlags.Out)
        .SetReturnType(dtInt);

    // STRING[80] Method3([in] INT len, [in][LengthIs = 1] PCCH str)
    RpcMethod rpc3 = new RpcMethod("Method3")
        .AddParameter("len", dtInt, MethodParamFlags.In)
        // Referencing to parameter 'len' via LengthIs Attribute
        .AddParameter("str", dtPCCH, MethodParamFlags.In, 1)
        .SetReturnType(dtString);

    // STRING[80] Method4([in] INT len, [out][LengthIs = 1] PCCH str)
    RpcMethod rpc4 = new RpcMethod("Method4")
        .AddParameter("len", dtInt, MethodParamFlags.In)
        .AddParameter("str", dtPCCH, MethodParamFlags.Out, 1)
        .SetReturnType(dtString);

    // STRING[80] Method5([in] INT len, [out][LengthIs = 1] PCCH str)
    RpcMethod rpc5 = new RpcMethod("Method5")
        .AddParameter("len", dtInt, MethodParamFlags.In)
        .AddParameter("str", dtPCCH, MethodParamFlags.Out, 1)
        .SetReturnType(dtString);

    // ... Create the RpcStructType itself and bind the Members/Methods
    StructType dtStructRpc = new StructType("MYRPCSTRUCT");
    dtStructRpc
        // Add Members (Fields)
        .AddAligned(new Member("name", dtString))
        .AddAligned(new Member("a", dtBool))
        .AddAligned(new Member("b", dtInt))
        .AddAligned(new Member("c", dtDInt))
        // Add Methods
        .AddMethod(rpc1)
        .AddMethod(rpc2);

    // Create an Array Type
    // Create the Dimensions
    IDimensionCollection dims = new DimensionCollection()
        // Add Dimension
        .AddDimension(new Dimension(0, 4))
        .AddDimension(new Dimension(0, 2));

    // Create the Array Type itself
    ArrayType dtArray = new ArrayType(dtInt, dims);

    // Create an Enumeration Type
    // Define the Enum Fields/Values
    EnumValueCollection<int> enumValues = new EnumValueCollection<int>()
        // Add Enumeration Fields/Entry
        .AddValue("None", 0)
        .AddValue("Red", 1)
        .AddValue("Yellow", 2)
        .AddValue("Green", 3);

    // Create the EnumType
    EnumType<int> dtEnum = new EnumType<int>("MYENUM", dtDInt, enumValues);

    // Alias Type
    AliasType dtAlias = new AliasType("MYALIAS", dtEnum);

    //Pointer Type
    PointerType dtPointer = new PointerType(dtInt, this.PlatformPointerSize);

    // Reference Type
    ReferenceType dtReference = new ReferenceType(dtInt, this.PlatformPointerSize);

    // Add the Types to the internal SymbolFactory
    base.symbolFactory
        .AddType(dtBool)
        .AddType(dtInt)
        .AddType(dtDInt)
        .AddType(dtString)
        .AddType(dtStruct)
        .AddType(dtArray)
        .AddType(dtEnum)
        .AddType(dtAlias)
        .AddType(dtPointer)
        .AddType(dtReference)
        .AddType(dtStructRpc)
        .AddType(dtByte)
        .AddType(dtPCCH);

    // Define some ProcessImage DataAreas (virtual) used for IndexGroup/IndexOffset Alignements
    DataArea general = new DataArea("General", 0x01, 0x1000, 0x1000);
    DataArea globals = new DataArea("Globals", 0x02, 0x1000, 0x1000);
    DataArea statics = new DataArea("Statics", 0x03, 0x1000, 0x1000);

    // Add the DataAreas to the SymbolFactory
    base.symbolFactory
        .AddDataArea(general)
        .AddDataArea(globals)
        .AddDataArea(statics);

    // Create the Symbols (Symbol Instances)
    // With DataType and define its DataArea in the (virtual)ProcessImage
    base.symbolFactory
        .AddSymbol("Globals.bool1", dtBool, globals)
        .AddSymbol("Globals.int1", dtInt, globals)
        .AddSymbol("Globals.dint1", dtDInt, globals)
        .AddSymbol("Globals.string1", dtString, globals)
        .AddSymbol("Globals.myStruct1", dtStruct, globals)
        .AddSymbol("Globals.myArray1", dtArray, globals)
        .AddSymbol("Globals.myEnum1", dtEnum, globals)
        .AddSymbol("Globals.myAlias1", dtAlias, globals)
        .AddSymbol("Globals.pointer1", dtPointer, globals)
        .AddSymbol("Globals.reference1", dtReference, globals)
        .AddSymbol("Globals.rpcInvoke1", dtStructRpc, globals)
        .AddSymbol("Main.bool1", dtBool, general)
        .AddSymbol("Main.int1", dtInt, general)
        .AddSymbol("Main.dint1", dtDInt, general)
        .AddSymbol("Main.string1", dtString, general)
        .AddSymbol("Main.myStruct1", dtStruct, general)
        .AddSymbol("Main.myArray1", dtArray, general)
        .AddSymbol("Main.myEnum1", dtEnum, general)
        .AddSymbol("Main.myAlias1", dtAlias, general)
        .AddSymbol("Main.pointer1", dtPointer, general)
        .AddSymbol("Main.reference1", dtReference, general)
        .AddSymbol("Main.rpcInvoke1", dtStructRpc, general);

    // Here we set the initial values of or Symbol instances
    _symbolValues.Add(this.Symbols["Globals.bool1"], true);
    _symbolValues.Add(this.Symbols["Globals.int1"], (short)42);
    _symbolValues.Add(this.Symbols["Globals.dint1"], 42);
    _symbolValues.Add(this.Symbols["Globals.string1"], "Hello world!");
    _symbolValues.Add(this.Symbols["Globals.myStruct1"], new MyStruct("Globals.myStruct1", true, 42, 99));
    _symbolValues.Add(this.Symbols["Globals.myArray1"], new short[4, 2]); ;
    _symbolValues.Add(this.Symbols["Globals.myEnum1"], "Yellow");
    _symbolValues.Add(this.Symbols["Globals.myAlias1"], "Red");
    _symbolValues.Add(this.Symbols["Globals.pointer1"], 0);
    _symbolValues.Add(this.Symbols["Globals.reference1"], 0);
    _symbolValues.Add(this.Symbols["Globals.rpcInvoke1"], new MyStruct("Globals.rpcInvoke1", false, 555, 666));
    _symbolValues.Add(this.Symbols["Main.bool1"], true);
    _symbolValues.Add(this.Symbols["Main.int1"], (short)42);
    _symbolValues.Add(this.Symbols["Main.dint1"], 42);
    _symbolValues.Add(this.Symbols["Main.string1"], "Hello world!");
    _symbolValues.Add(this.Symbols["Main.myStruct1"], new MyStruct("Main.myStruct1", true, 42, 99));
    _symbolValues.Add(this.Symbols["Main.myArray1"], new short[4, 2]);
    _symbolValues.Add(this.Symbols["Main.myEnum1"], "Yellow");
    _symbolValues.Add(this.Symbols["Main.myAlias1"], "Red");
    _symbolValues.Add(this.Symbols["Main.pointer1"], 0);
    _symbolValues.Add(this.Symbols["Main.reference1"], 0);
    _symbolValues.Add(this.Symbols["Main.rpcInvoke1"], new MyStruct("Main.rpcInvoke1", false, 555, 666));

    return this;
    }

    /// <summary>
    /// Called when an ADS Read State indication is received.
    /// </summary>
    /// <param name="sender">The sender address of the request.</param>
    /// <param name="invokeId">The invoke identifier of the request.</param>
    /// <param name="cancel">The cancellation token.</param>
    /// <returns>A task that represents the asynchronous <see cref="M:TwinCAT.Ads.Server.AdsServer.ReadDeviceStateIndicationAsync(TwinCAT.Ads.AmsAddress,System.UInt32,System.Threading.CancellationToken)" /> operation. The <see cref="T:System.Threading.Tasks.Task`1" /> parameter contains the <see cref="T:TwinCAT.Ads.AdsErrorCode" /> as
    /// <see cref="P:System.Threading.Tasks.Task`1.Result" />.</returns>
    /// <remarks>Overwrite this method in derived classes to react on ADS Read State indications.
    /// The default implementation replies with an ADS ServiceNotSupported error code (0x701).</remarks>
    protected override Task<ResultReadDeviceState> OnReadDeviceStateAsync(AmsAddress sender, uint invokeId, CancellationToken cancel)
    {
    AdsState adsState = AdsState.Run;
    ushort deviceState = 0;
    StateInfo state = new StateInfo(adsState, deviceState);
    ResultReadDeviceState result = ResultReadDeviceState.CreateSuccess(state);
    return Task.FromResult(result);
    }

    /// <summary>
    /// Handler function for Reading the internal value data in raw format.
    /// </summary>
    /// <param name="symbol">The symbol.</param>
    /// <param name="span">The span.</param>
    /// <returns>AdsErrorCode.</returns>
    /// <remarks>This method is called, when a Read request was received to read the symbols value.
    /// Implement this handler to Read and marshal the value data.</remarks>
    protected override AdsErrorCode OnReadRawValue(ISymbol symbol, Span<byte> span)
    {
    object value;
    if (_symbolValues.TryGetValue(symbol, out value))
    {
        int bytes = 0;
        if (_symbolMarshaler.TryMarshal(symbol, value, span, out bytes))
        return AdsErrorCode.NoError;
        else
        return AdsErrorCode.DeviceInvalidSize;

    }
    else
        return AdsErrorCode.DeviceSymbolNotFound;
    }

    /// <summary>
    /// Handler function for writing the symbol value data in raw format.
    /// </summary>
    /// <param name="symbol">The symbol.</param>
    /// <param name="span">The span.</param>
    /// <returns>AdsErrorCode.</returns>
    /// <remarks>This method is called, when a Write request was received to overwrite the symbols value.
    /// Implement this handler to overwrite the Ads Servers internal value data.</remarks>
    protected override AdsErrorCode OnWriteRawValue(ISymbol symbol, ReadOnlySpan<byte> span)
    {
    object value;
    _symbolMarshaler.Unmarshal(symbol, span, null, out value);
    SetValue(symbol, value);
    return AdsErrorCode.NoError;
    }

    /// <summary>
    /// Handler function to store a new Symbol value within internal caches of the <see cref="T:TwinCAT.Ads.Server.AdsSymbolicServer" />.
    /// </summary>
    /// <param name="symbol">The symbol.</param>
    /// <param name="value">The value.</param>
    /// <param name="valueChanged">if set to <c>true</c>, the value was changed.</param>
    /// <returns>AdsErrorCode.</returns>
    protected override AdsErrorCode OnSetValue(ISymbol symbol, object value, out bool valueChanged)
    {
    object oldValue = null;
    valueChanged = false;
    if (_symbolValues.TryGetValue(symbol, out oldValue))
    {
        if (!value.Equals(oldValue))
        {
        valueChanged = true;
        }
        return AdsErrorCode.NoError;
    }
    return AdsErrorCode.DeviceSymbolNotFound;
    }

    /// <summary>
    /// Handler function to determine the (stored) value of the symbol from the internal caches of the <see cref="T:TwinCAT.Ads.Server.AdsSymbolicServer" />.
    /// </summary>
    /// <param name="symbol">The symbol.</param>
    /// <returns>System.Object.</returns>
    protected override AdsErrorCode OnGetValue(ISymbol symbol, out object value)
    {
    if (_symbolValues.TryGetValue(symbol, out value))
    {
        return AdsErrorCode.NoError;
    }
    return AdsErrorCode.DeviceSymbolNotFound;
    }

    /// <summary>
    /// Handler function called when an RpcInvoke occurs.
    /// </summary>
    /// <param name="structInstance">The structure instance.</param>
    /// <param name="method">The method.</param>
    /// <param name="parameterValues">The parameter values.</param>
    /// <param name="returnValue">The return value.</param>
    /// <returns>AdsErrorCode.</returns>
    /// <remarks>Overwrite this method to react on RpcInvokes within your custom <see cref="AdsSymbolicServer" />.
    /// The default implementation returns <see cref="AdsErrorCode.DeviceServiceNotSupported" />.</remarks>
    protected override AdsErrorCode OnRpcInvoke(IInterfaceInstance structInstance, IRpcMethod method, object[] parameterValues, out object returnValue)
    {
    // Here we implement or handler for the RPC Call.

    object val;
    // Select the right RpcStructInstance and get its value object.
    if (_symbolValues.TryGetValue(structInstance, out val))
    {
        MyStruct myStructValue = val as MyStruct;

        if (myStructValue != null)
        {
        // For demo simplification, we choose the Method simply by name.
        // This could be done in a more generic way, e.g with Reflection or whatever custom infrastructure.
        switch (method.Name)
        {
            case "Method1":
            {
                returnValue = myStructValue.Method1((short)parameterValues[0], (short)parameterValues[1]);
                return AdsErrorCode.NoError;
            }
            case "Method2":
            {
                returnValue = myStructValue.Method2((short)parameterValues[0], out var out1);
                parameterValues[1] = out1;
                return AdsErrorCode.NoError;
            }
            default:
            returnValue = null;
            return AdsErrorCode.DeviceServiceNotSupported;
        }
        }
        else
        {
        returnValue = null;
        return AdsErrorCode.DeviceServiceNotSupported;
        }
    }
    else
    {
        returnValue = null;
        return AdsErrorCode.DeviceServiceNotSupported;
    }
    }
}

// Necessary helper struct to use the .NET Default Interop marshaler which is used by the
// SymbolicAnyTypeMarshaler to Marshal/Unmarshal Struct values.
// The Layout must exactly map the 'dtMyStruct' DataType definition, so that
// the .NET Interop default marshaler is able to 'blit' the value in its own data buffers.
// MyStruct als implements the RpcInvoke Methods in this example.
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 1)]
public class MyStruct
{
    // Constructor
    public MyStruct(string name, bool a, short b, int c)
    {
    this.name = name;
    this.a = a;
    this.b = b;
    this.c = c;
    }

    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
    public string name;
    [FieldOffset(162)]
    [MarshalAs(UnmanagedType.U1)] // Boolean is Marshaled as UnmanagedType.I4 otherwise
    public bool a;
    [FieldOffset(163)]
    // [MarshalAs(UnmanagedType.I2)] (Default)
    public short b;
    [FieldOffset(165)]
    // [MarshalAs(UnmanagedType.I4)] (Default)
    public int c;

    // Definition of the RpcMethods

    public short Method1(short i1, short i2)
    {
    // Just return the addition of both inputs
    return (short)(i1 + i2);
    }
    public short Method2(short i1, out short i2)
    {
    i2 = (short)(i1 + 1);
    return (short)(i1 + 2);
    }
}

Reference

TwinCAT.Ads.Server Namespace

TwinCAT.Ads.Server.AdsServer