I took a detour from main goal of project, which is type provider, in order to provide with lightweight fsharpish library for ADS communication. To my surprise a lot of drawbacks popped out, here you may find a descriptions of them, plus workarounds for some.
Marshalling structures with BOOL arrays.
On one hand, according to Infosys BOOL type has size of 1 byte (where 1 is true, 0 is false, while any other value is incorrect as it can’t be interpreted correctly), same 1 byte size for bool is in .NET. On the other hand, we have Win32 BOOL, which is 4 byte size (more detailed on SO). Which is scenario we can easily handle in C#, but not in F#.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BoolArrSample
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.I1)]
public readonly bool[] values;
}
.class public sequential ansi sealed beforefieldinit Structs.BoolArrSample
extends [mscorlib]System.ValueType
{
.pack 1
.size 0
.field public initonly marshal(fixed array[10] int8( bool[] values
}
[<Struct;StructLayout(LayoutKind.Sequential, Pack = 1)>]
type BoolArrSample =
[<MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.I1)>]
val values: BOOL[]
.class public sequential ansi sealed serializable NuSoft.Ads.Experimental.Samples.BoolArrSample
extends [mscorlib]System.ValueType
implements class [mscorlib]System.IEquatable`1<valuetype NuSoft.Ads.Experimental.Samples.BoolArrSample>,
[mscorlib]System.Collections.IStructuralEquatable,
class [mscorlib]System.IComparable`1<valuetype NuSoft.Ads.Experimental.Samples.BoolArrSample>,
[mscorlib]System.IComparable,
[mscorlib]System.Collections.IStructuralComparable
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (
01 00 03 00 00 00 00 00
)
.pack 1
.size 0
.field assembly marshal(fixed array[10]( bool[] values@
.property instance bool[] values()
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, int32) = (
01 00 04 00 00 00 00 00 00 00 00 00
)
.get instance bool[] NuSoft.Ads.Experimental.Samples.BoolArrSample::get_values()
{
IL_0000: ldarg.0
IL_0001: ldfld bool[] NuSoft.Ads.Experimental.Samples.BoolArrSample::values@
IL_0006: ret
}
}
}
as you may notice, there’s no ArraySubType value in F# sample IL output. If you look at a visual fsharp sources you’ll notice that ArraySubType is disregarded. Click here for open issue.
Workaround for marhsalling byte arrays in F#
Other than fixing the issue, one can use Struct array instead of bool array, though it’s not much convenient.
[<AutoOpen>]
module Workarounds =
open System.Runtime.InteropServices
[<Struct;StructLayout(LayoutKind.Sequential, Pack=1)>]
type BOOLSTRUCT =
val value: BOOL
new(value) = {
value=value
}
let inline BOOL (x: BOOL) = new BOOLSTRUCT(x)
let inline BOOLSTRUCT (x: BOOLSTRUCT) = x.value
Marshalling TwinCAT TOD, DATETIME, TIME, DATE types
In TwinCAT time and date types are all 4 bytes, closest out-of-the-box equivalents in .Net world are DateTime for DATE_AND_TIME and DATE, and TimeSpan for TIME_OF_DAY and TIME, problem is both of them are 8 bytes long.
Marshalling DateTime/TimeSpan values
If you read trough Infosys, you’ll find Reading and writing of TIME/DATE variables, it utilizes AdsStream and AdsBinaryReader/AdsBinaryWriter, but personally prefer to use other methods provided by TcAdsClient.dll.
[<AutoOpen>]
module Ext =
type TcAdsClient with
member self.ReadDateTime(variableHandle) =
self.ReadAny(variableHandle, typedefof<uint32>)
:?> uint32
|> int64
|> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToDateTime
member self.ReadTimeSpan(variableHandle) =
self.ReadAny(variableHandle, typedefof<uint32>)
:?> uint32
|> int64
|> TwinCAT.Ads.Internal.PlcOpenTimeConverter.ToTimeSpan
member self.WriteAny(variableHandle, value) =
let v =
value
|> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToTicks
|> uint32
self.WriteAny(variableHandle, v)
member self.WriteAny(variableHandle, value) =
let v =
value
|> TwinCAT.Ads.Internal.PlcOpenTimeConverter.ToTicks
|> uint32
self.WriteAny(variableHandle, v)
you can always use code from Infosys as an extension methods as well.
Marshalling DateTime/TimeSpan structure fields
When it comes to workaround for structure fields there is no workaround that I would like, at least not among those that I know to work. I had few approaches to solutions that do not work, and as I learned couldn’t even possible work. May say one learns by failing. Let’s start with one notable failure, as it also comes with F# missing functionality.
Custom marshallers
At first I thought that it would be possible to marshal one filed with usage of custom marshaller, well it doesn’t work that way.

I may be wrong at this, but Custom marshaller is not an option even if we invoked native method from tcadsdll.dll, as the signature
[DllImport("tcadsdll.dll", CharSet=CharSet.None, ExactSpelling=false)]
public static extern unsafe AdsErrorCode AdsSyncReadReq(byte* addr, uint indexGroup, uint indexOffset, int length, void* data);
expects void pointer not actual structure
Workaround
[<Struct;StructLayout(LayoutKind.Sequential, Pack = 1)>]
type DateTimeArraySample =
[<MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.U4)>]
val values: uint32[]
member self.Values =
self.values
|> Array.map (int64 >> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToDateTime)
what I do not like about is obfuscating structure with additional properties, and even if we’d use extension properties/methods, it still involves duplicating every single DT/TOD/D/T fields.
Note on custom marshallers in F#
Putting it simply, they cannot be used ATM, SO question. Still not sure why, but actual code for read and write IL, is present in sources, yet when parsing MarshalAsAttribute exception is thrown explicitly, though it doesn’t seem that hard to fix it, I have it working on modified sources. If I only knew how to test it properly…