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 is equal (e.g primitive types) or is marshallable by the default marshalling of .NET (see 'PlcStruct' in the example below).
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'
using (TcAdsClient client = new TcAdsClient())
{
client.Connect("1.2.3.4.5.6", 851);
// Bool value
bool boolValue = (bool)client.ReadSymbol("MAIN.bool1", typeof(bool), false);
adsClient.WriteSymbol("MAIN.bool1", boolValue,false);
// or
int handle1 = adsClient.CreateVariableHandle("MAIN.bool1"); // BOOL
boolValue = (bool)client.ReadAny(handle1, typeof(bool));
adsClient.WriteAny(handle1, boolValue);
adsClient.DeleteVariableHandle(handle1);
// RealValue
int handle2 = adsClient.CreateVariableHandle("MAIN.bool1"); // BOOL
float realValue = (float)client.ReadAny(handle2, typeof(float)); // REAL
client.WriteAny(handle2, realValue);
adsClient.DeleteVariableHandle(handle2);
// String
int handle3 = adsClient.CreateVariableHandle("MAIN.string1"); // STRING[80]
string stringValue = (string)client.ReadAny(handle3, typeof(string), new int[] { 80 }); // Needs additional para for strlen
adsClient.WriteAny(handle3, stringValue, new int[] { 80 });
// ushort[]
int handle4 = adsClient.CreateVariableHandle("MAIN.uint1Arr"); // ARRAY [0..9] OF UINT
ushort[] ushortArr = (ushort[])client.ReadAny(handle4, typeof(ushort[]), new int[] { 10 });
adsClient.WriteAny(handle4, ushortArr, new int[] { 10 });
adsClient.DeleteVariableHandle(handle4);
// Complex Struct Type
// Take care the the corresponding .NET Type is blittable / marshallable to the PLC type
int handle5 = adsClient.CreateVariableHandle("MAIN.struct");
PlcStruct structValue = (PlcStruct)adsClient.ReadAny(handle5, typeof(PlcStruct));
adsClient.WriteAny(handle5, typeof(PlcStruct));
adsClient.DeleteVariableHandle(handle5);
// 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
int handle6 = adsClient.CreateVariableHandle("MAIN.stringArr"); // ARRAY [0..9] OF STRING[80]
string[] stringArr = (string[])client.ReadAny(handle6, typeof(string[]), new int[] { 80, 10 });
adsClient.WriteAny(handle6, stringValue, new int[] { 80 });
adsClient.DeleteVariableHandle(handle6);
}
Defining Memory layout of struct type.
[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
}
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'
using (TcAdsClient client = new TcAdsClient())
{
client.AdsNotificationEx += Client_AdsNotificationEx;
client.Connect("1.2.3.4.5.6", 851);
// Add UDINT
int notificationHandle = client.AddDeviceNotificationEx("MAIN.udint", AdsTransMode.OnChange, 200, 200, null, typeof(uint));
Thread.Sleep(5000); // ...
client.DeleteDeviceNotification(notificationHandle); // Unregister Event
}
Notifications with 'ANY_TYPES'
private void Client_AdsNotificationEx(object sender, AdsNotificationExEventArgs e)
{
uint value = (uint)e.Value; // Marshalled value as .NET Type
}