2

Using TwinCAT 3 ADS.Net for reading from PLC, I'm trying to read a struct containing array of structs, but the ReadAny command crashes with "Unable to marshal type" exception.

Reading directly an array of structs works fine though.

public object ReadAny(long indexGroup, long indexOffset, Type type, int[] args);

The header remark of the ReadAny method says: “If the Type of the object to be read is an array type, the number of elements for each dimension has to be specified in the parameter args."

But what should args be for a struct containing array of structs? (Without 'args' it fails too.)

I currently work with .NET 4.7, VS 2013.

Is there an option?

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class WholeData
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public Station[] StationArray;
    // Potentially more fields...
}

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class Station
{
    [MarshalAs(UnmanagedType.I1)]
    public bool isPass;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
    public string name;
    // More fields...
}

// -- Main --
int[] args = { 5 };

// Works fine:
Station[] stationArray = (Station[])m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(Station[]), args);

// Fail:
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(WholeData), args);
// - OR -
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(m_VarHandle, typeof(WholeData), args);
Udi Y
  • 258
  • 3
  • 12
  • I'm guessing this is some sort of memory error, as arrays are reference types (so an array of structs is a reference) and structs are a value type (so a struct with an array of structs is a value not a reference) – MindSwipe Jul 30 '19 at 09:44

1 Answers1

3

I tested successfully following code:

c# code:

    class Program
    {
        public static TcAdsClient client;
        static void Main(string[] args)
        {


            // Create the ADS Client
            using (client = new TcAdsClient())
            {
                // Establish Connection
                client.Connect(new AmsAddress("10.1.2.95.1.1", 851));
                int handle = client.CreateVariableHandle("PRG_AIS.stAds");

                AdsClass ads = (AdsClass)client.ReadAny(handle, typeof(AdsClass));
                ads.boolArr[0] = 1;
                client.WriteAny(handle, ads);
                Console.ReadLine();

            }
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    class AdsClass
    {

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
        public byte[] boolArr = new byte[10];
    }

ST code:

TYPE AdsStruct :
STRUCT
    bTestArray : ARRAY[0..9] OF BOOL;
END_STRUCT
END_TYPE

AdsStruct is defined as stAds in PRG_AIS.

OR if you have an array of structs modify the code the following way:

c# code:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
class AdsClass
{

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public InnerStruct[] strArr = new InnerStruct[10];
}

struct InnerStruct
{
    public byte bBoolTest;
    public int nIntTest;
}

ST code:

TYPE AdsStruct  :
STRUCT
    stTestArray : ARRAY[0..9] OF InnerStruct;
END_STRUCT
END_TYPE

TYPE InnerStruct :
STRUCT
    bBoolTest : BOOL;
    nIntTest  : DINT;
END_STRUCT
END_TYPE
Filippo Boido
  • 1,136
  • 7
  • 11
  • Your code refers to an array of bytes (a simple type), so it works. Try it with an array of structs. – Udi Y Jul 30 '19 at 15:24
  • Hey Udi, "To mark an answer as accepted, click on the check mark beside the answer to toggle it from greyed out to filled in." https://stackoverflow.com/help/someone-answers – Filippo Boido Aug 01 '19 at 14:30
  • I tried to find out what's the different between our codes, and saw that you used "Pack = 1" rather than 0. In Beckhoff documentation, under section “Reading and writing of PLC variables of any type”, they say: “TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0” Since I use TwinCAT 3, I wrote =0. https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adssamples_net/html/TwinCAT.Ads.Sample01.html&id= – Udi Y Aug 01 '19 at 16:33
  • When trying to change it to =1, I got some memory shift/alignment problems. In the following example, the short number (2 bytes) became Gibberish. [StructLayout(LayoutKind.Sequential, Pack = 1)] public class InnerClass { public EnumTest eEnum; [MarshalAs(UnmanagedType.I1)] public bool bBool; public short sShort; } – Udi Y Aug 01 '19 at 16:38
  • 1
    You must have the same alignment in the plc and in the c# program. In the plc you can set the pack of a structure with following attribute: {attribute 'pack_mode' := ''} for more info: https://infosys.beckhoff.com/index.php?content=../content/1031/tc3_plc_intro/2529746059.html&id=4224027070683261754 – Filippo Boido Aug 01 '19 at 16:43
  • 2
    I believe the difference is that you probably had something like "class" InnerStruct instead of "struct". Apparently it doesn't work if you try to pass nested classes types to ReadAny..You must use structs if you have complex nested structures. – Filippo Boido Aug 01 '19 at 16:49
  • Do you have any other problems? – Filippo Boido Aug 02 '19 at 15:31
  • I changed my class to a struct and voila! It now works fine. Thanks a lot! – Udi Y Aug 04 '19 at 08:49