Value marshalling with ANYTYPE concept

This topic describes reading and writing variables/symbols of 'any' type with the help of the ReadAny and WriteAny (ReadSymbol, WriteSymbol) methods. The value will be marshalled / cast directly from/to its appropriate .NET type, what eases the value access.

'Any' types in this context are all types that are 'blittable' to the process image - what means that the memory layout on both sides of the data transfer is equal (e.g some primitive types) or can be marshalled by the marshalling mechanisms of .NET (see 'PlcStruct' in the example below). The memory layout specification can be customized with the 'System.Runtime.InteropServices.StructLayoutAttribute' on the .NET side (see MSDN) and the 'pack_mode' attribute on the TwinCAT PLC Side (TwinCAT 3). TwinCAT 2 only supports a memory layout of PACK = 1.

The appropriate .NET type must be known during compile time and is passed to the methods as parameter. In case of a ReadAny call, the read data will be returned as a object. The type of the object is marshalled to the type specified as parameter type. Because the data size and the memory alignment is taken from this type specification, it is so important that this specification fits to the memory representation in the ADS device (e.g. the PLC).

Because some data types(arrays and strings) need additional information, an overload of the method ReadAny exists, that takes an additional parameter args. A Full list of supported types can be found in the documentation of the overloaded method.

Reading and writing of structures

To be able to read or write PLC structures the memory layout of the structure or class in .NET must be the same as in the PLC. The layout of a structure or class can be specified with the attribute StructLayoutAttribute. The LayoutKind must be set to LayoutKind.Sequential and the pack must be set to 1

If arrays, strings or boolean values are define the class, one has to specify how these fields should be marshalled. This is accomplished with help of the MarshalAs attribute. Because arrays and strings do not have a fixed length in .NET, the property SizeConst is necessary for arrays and strings.

Marshalling values with 'ANY_TYPES' asynchronously

CancellationToken cancel = CancellationToken.None;

using (AdsClient client = new AdsClient())
{
    client.Connect(AmsNetId.Local, 851);

    // Bool value
    bool boolValue = false;
    ResultAnyValue resultBoolValue = await client.ReadValueAsync("MAIN.bool1", typeof(bool),cancel);
    boolValue = (bool)resultBoolValue.Value;
    ResultWrite resultWrite = await client.WriteValueAsync("MAIN.bool1", boolValue,cancel);

    // or
    ResultHandle resultHandleBool = await client.CreateVariableHandleAsync("MAIN.bool1",cancel); // BOOL
    //resultHandleBool.ThrowOnError(); // or

    if (resultHandleBool.Succeeded) 
    {
        ResultAnyValue resultReadBool = await client.ReadAnyAsync(resultHandleBool.Handle, typeof(bool), cancel);
        boolValue = (bool)resultReadBool.Value;
        ResultWrite resultWriteBool = await client.WriteAnyAsync(resultHandleBool.Handle, boolValue, cancel);
        ResultAds resultHandleDeleteBool = await client.DeleteVariableHandleAsync(resultHandleBool.Handle, cancel);
    }

    // RealValue
    ResultHandle resultHandleReal = await client.CreateVariableHandleAsync("MAIN.real1",cancel); // REAL

    if (resultHandleReal.Succeeded)
    {
        ResultAnyValue resultReadReal = await client.ReadAnyAsync(resultHandleReal.Handle, typeof(float), cancel); // REAL
        ResultWrite resultWriteReal = await client.WriteAnyAsync(resultHandleReal.Handle, resultReadReal.Value, cancel);
        ResultAds resultHandleDeleteReal = await client.DeleteVariableHandleAsync(resultHandleReal.Handle, cancel);
    }

    // String
    ResultHandle resultHandleString = await client.CreateVariableHandleAsync("MAIN.string1",cancel); // STRING[80]

    if (resultHandleString.Succeeded)
    {
        ResultAnyValue resultReadString = await client.ReadAnyAsync(resultHandleString.Handle, typeof(string), new int[] { 80 }, cancel); // Needs additional para for strlen
        ResultWrite resultWriteString = await client.WriteAnyAsync(resultHandleString.Handle, resultReadString.Value, new int[] { 80 }, cancel);
        ResultAds resultHandleDeleteString = await client.DeleteVariableHandleAsync(resultHandleString.Handle, cancel);
    }

    // ushort[]
    ResultHandle resultHandleArray = await client.CreateVariableHandleAsync("MAIN.uint1Arr", cancel); // ARRAY [0..9] OF UINT

    if (resultHandleArray.Succeeded)
    {
        ResultAnyValue resultReadArray = await client.ReadAnyAsync(resultHandleArray.Handle, typeof(ushort[]), new int[] { 10 }, cancel);
        ushort[] arrayValue = (ushort[])resultReadArray.Value;
        ResultWrite resultWriteArray = await client.WriteAnyAsync(resultHandleArray.Handle, arrayValue, new int[] { 10 }, cancel);
        ResultAds resultHandleDeleteArray = await client.DeleteVariableHandleAsync(resultHandleArray.Handle, cancel);
    }

    // Complex Struct Type
    // Take care the the corresponding .NET Type is blittable / marshallable to the PLC type
    ResultHandle resultHandleStruct = await client.CreateVariableHandleAsync("MAIN.struct",cancel);

    if (resultHandleStruct.Succeeded)
    {
        ResultAnyValue resultReadStruct = await client.ReadAnyAsync(resultHandleStruct.Handle, typeof(PlcStruct), cancel);
        PlcStruct structValue = (PlcStruct)resultReadStruct.Value;
        ResultWrite resultWriteStruct = await client.WriteAnyAsync(resultHandleStruct.Handle, structValue, cancel);
        ResultAds resultHandleDeleteStruct = await client.DeleteVariableHandleAsync(resultHandleStruct.Handle, cancel);
    }

    // ARRAY [0..9] OF STRING[80]
    // args[0] --> Number of Characters
    // args[1] --> Number of Array Elements
    // Needs additional para for strlen and number of Elements in Array
    ResultHandle resultHandleStringArray = await client.CreateVariableHandleAsync("MAIN.stringArr",cancel); // ARRAY [0..9] OF STRING[80]

    if (resultHandleStringArray.Succeeded)
    {
        ResultAnyValue resultReadStringArray = await client.ReadAnyAsync(resultHandleStringArray.Handle, typeof(string[]), new int[] { 80, 10 }, cancel);
        string[] stringArrValue = (string[])resultReadStringArray.Value;
        ResultWrite resultWriteStringArray = await client.WriteAnyAsync(resultHandleStringArray.Handle, stringArrValue, new int[] { 80, 10 }, cancel);
        ResultAds resultHandleDeleteStringArray = await client.DeleteVariableHandleAsync(resultHandleStringArray.Handle, cancel);
    }

}

Defining Memory layout of struct type.

// Attention: Dependent of the System where the PLC runs, the StructLayout of the exchanged
// Structures must match. With the ANY_TYPE concept this is realized with 'blittable' objects,
// that match on .NET and PLC side.

// Default Pack Modes:

// TC3 I64/x86: Normal, in this case Pack = 8
// TC2 x86:     Pack = 1

// On TC3 PLC side we can force the packing of structures with the attribute
// {attribute 'pack_mode' := '1'}, see also 'pack_mode' attribute in Beckhoff InfoSystem
// For TC2 is the Pack setting Pack = 1 the only possible way, because it is not selectable.

// We have to ensure that the pack mode on both sides is equal!

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PlcStruct
{
    // Type must be 'blittable' to the corresponding PLC Struct Type
    // See MSDN for MarshalAs and Default Marshalling.

    [MarshalAs(UnmanagedType.I1)]
    public bool boolVal; // BOOL
    public byte byteVal; // BYTE
    public ushort ushortVal; // UINT
    public short shortVal; // INT
    public uint uintVal; // UDINT
    public int dintVal; // DINT
    public uint udintVal; // UDINT
    public float realVal; // REAL
    public double lrealVal; // LREAL
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
    public string stringVal; // STRING[80]

    [MarshalAs(UnmanagedType.U4)]
    public uint timeVal; // TIME
    [MarshalAs(UnmanagedType.U4)]
    public uint todVal; // TOD
    [MarshalAs(UnmanagedType.U4)]
    public uint dateval; // DATE
    [MarshalAs(UnmanagedType.U4)]
    public uint dtVal; // DT
}

Marshalling values with 'ANY_TYPES' (synchronous)

using (AdsClient client = new AdsClient())
{
    client.Connect(AmsNetId.Local, 851);

    // Bool value
    bool boolValue = (bool)client.ReadValue("MAIN.bool1", typeof(bool));
    client.WriteValue("MAIN.bool1", boolValue);

    // or
    uint handleBool = client.CreateVariableHandle("MAIN.bool1"); // BOOL
    boolValue = (bool)client.ReadAny(handleBool, typeof(bool));
    client.WriteAny(handleBool, boolValue);
    client.DeleteVariableHandle(handleBool);

    // RealValue
    uint handleReal = client.CreateVariableHandle("MAIN.real1"); // REAL
    float realValue = (float)client.ReadAny(handleReal, typeof(float)); 
    client.WriteAny(handleReal, realValue);
    client.DeleteVariableHandle(handleReal);

    // String
    uint handleString = client.CreateVariableHandle("MAIN.string1"); // STRING[80]
    string stringValue = (string)client.ReadAny(handleString, typeof(string), new int[] { 80 }); // Needs additional para for strlen
    client.WriteAny(handleString, stringValue, new int[] { 80 });
    client.DeleteVariableHandle(handleString);

    // ushort[]
    uint handleArray = client.CreateVariableHandle("MAIN.uint1Arr"); // ARRAY [0..9] OF UINT
    ushort[] arrayValue = (ushort[])client.ReadAny(handleArray, typeof(ushort[]), new int[] { 10 });
    client.WriteAny(handleArray, arrayValue, new int[] { 10 });
    client.DeleteVariableHandle(handleArray);

    // Complex Struct Type
    // Take care the the corresponding .NET Type is blittable / marshallable to the PLC type
    uint handleStruct = client.CreateVariableHandle("MAIN.struct");
    PlcStruct structValue = (PlcStruct)client.ReadAny(handleStruct, typeof(PlcStruct));
    client.WriteAny(handleStruct, structValue);
    client.DeleteVariableHandle(handleStruct);

    // ARRAY [0..9] OF STRING[80]
    // args[0] --> Number of Characters
    // args[1] --> Number of Array Elements
    // Needs additional para for strlen and number of Elements in Array
    uint handleStringArr = client.CreateVariableHandle("MAIN.stringArr"); // ARRAY [0..9] OF STRING[80]
    string[] stringArr = (string[])client.ReadAny(handleStringArr, typeof(string[]), new int[] { 80, 10 });
    client.WriteAny(handleStringArr, stringArr, new int[] { 80, 10 });
    client.DeleteVariableHandle(handleStringArr);

}

ADS Notifications with Type marshalling (AdsNotificationEx)

The method AddDeviceNotificationEx is used to register notifications for a PLC variable. If the value of a variable changes the event AdsNotificationEx is fired. The difference to the event AdsNotification, is that the value of the variable is stored in an object instead of in an AdsStream. Therefore one has to pass the type of the object to the method AddDeviceNotificationEx

Notifications with 'ANY_TYPES' (asynchronous)

CancellationToken cancel = CancellationToken.None;

using (AdsClient client = new AdsClient())
{
    client.AdsNotificationEx += Client_AdsNotificationEx;
    client.Connect(AmsNetId.Local, 851);

    // Add UDINT
    ResultHandle resultHandle = await client.AddDeviceNotificationExAsync("MAIN.udint", new NotificationSettings(AdsTransMode.OnChange, 200, 200), null, typeof(uint),null, cancel);
    await Task.Delay(5000, cancel); // Wait ....
    ResultAds resultHandleDelete = await client.DeleteDeviceNotificationAsync(resultHandle.Handle,cancel); // Unregister Event
}

Notifications with 'ANY_TYPES'

private void Client_AdsNotificationEx(object sender, AdsNotificationExEventArgs e)
{
    uint value = (uint)e.Value; // Marshalled value as .NET Type
}

Notifications with 'ANY_TYPES' (synchronous)

using (AdsClient client = new AdsClient())
{
    client.AdsNotificationEx += Client_AdsNotificationEx; 
    client.Connect(AmsNetId.Local, 851);

    // Add UDINT
    uint notificationHandle = client.AddDeviceNotificationEx("MAIN.udint", new NotificationSettings(AdsTransMode.OnChange, 200, 200), null, typeof(uint));
    Thread.Sleep(5000); // ...
    client.DeleteDeviceNotification(notificationHandle); // Unregister Event
}