From 7da84df776e5c90a4aab6b654b81a383ad85a63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Thu, 3 Sep 2026 03:22:35 +0200 Subject: [PATCH] Carry a MEOS byte buffer and its length as one array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MEOS states the length of a buffer it answers through a pointer the caller supplies, and reads one back as a pointer and a length beside it. To a C# caller both are one array: `AsWKB` answers a `byte[]` of the length MEOS wrote, `FromWKB` takes a `byte[]` and hands MEOS its address and its own length, and the length out-parameter leaves the signature — every `size_t *` in the surface is one of these 28, and nothing else asks for one. The hex form needs no length either, the string ending where MEOS ends it, so its buffer is written and dropped. That is why `AsHEXWKB` takes only the variant it is given. The length beside an array argument is spelled `count` for an array of values and `size` for a byte buffer, and both say the same thing, so the counted-array convention reads either. A scalar element needs no gathering: what is pinned across the call is the caller's own array. 61 methods reach the object layer that way — 1232 against 1171, 63 deferred against 105. Three tests round-trip the forms: a span through its own WKB, a temporal float through its own, and a span through the hex form, each read back to the value it started as. --- MEOS.NET.Tests/ByteBufferTests.cs | 54 +++++++++++++ MEOS.NET/Types/BigIntSet.g.cs | 13 +++ MEOS.NET/Types/Cbuffer.g.cs | 47 +++++++++++ MEOS.NET/Types/DateSet.g.cs | 13 +++ MEOS.NET/Types/FloatSet.g.cs | 13 +++ MEOS.NET/Types/IntSet.g.cs | 13 +++ MEOS.NET/Types/Npoint.g.cs | 47 +++++++++++ MEOS.NET/Types/Pcpatch.g.cs | 13 +++ MEOS.NET/Types/Pcpoint.g.cs | 13 +++ MEOS.NET/Types/Pose.g.cs | 47 +++++++++++ MEOS.NET/Types/PoseChain.g.cs | 47 +++++++++++ MEOS.NET/Types/Raquet.g.cs | 81 +++++++++++++++++++ MEOS.NET/Types/STBox.g.cs | 47 +++++++++++ MEOS.NET/Types/Set.g.cs | 47 +++++++++++ MEOS.NET/Types/Span.g.cs | 47 +++++++++++ MEOS.NET/Types/SpanSet.g.cs | 47 +++++++++++ MEOS.NET/Types/TBox.g.cs | 47 +++++++++++ MEOS.NET/Types/Temporal.g.cs | 47 +++++++++++ MEOS.NET/Types/TsTzSet.g.cs | 13 +++ tools/objectgen.py | 129 +++++++++++++++++++++++++----- 20 files changed, 807 insertions(+), 18 deletions(-) create mode 100644 MEOS.NET.Tests/ByteBufferTests.cs diff --git a/MEOS.NET.Tests/ByteBufferTests.cs b/MEOS.NET.Tests/ByteBufferTests.cs new file mode 100644 index 0000000..35279b5 --- /dev/null +++ b/MEOS.NET.Tests/ByteBufferTests.cs @@ -0,0 +1,54 @@ +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// MEOS states the length of a buffer it answers through a pointer, and reads + /// one back as a pointer and a length. Both are one C# array to the caller. + /// + [TestClass] + public class ByteBufferTests : MeosTest + { + [TestMethod] + public void ASpanRoundTripsThroughItsOwnWKB() + { + Span span = FloatSpan.In("[8, 10]")!; + + byte[]? wkb = span.AsWKB(0); + + Assert.IsNotNull(wkb); + Assert.IsTrue(wkb!.Length > 0); + + Span? read = Span.FromWKB(wkb); + + Assert.IsNotNull(read); + Assert.AreEqual("[8, 10]", read!.ToString()); + } + + [TestMethod] + public void ATemporalValueRoundTripsThroughItsOwnWKB() + { + Temporal temp = TFloat.In("[25.5@2024-12-06, 27.5@2024-12-08]")!; + + byte[]? wkb = temp.AsWKB(0); + + Assert.IsNotNull(wkb); + + Temporal? read = Temporal.FromWKB(wkb!); + + Assert.IsNotNull(read); + Assert.AreEqual(temp.ToString(), read!.ToString()); + } + + [TestMethod] + public void TheHexFormNeedsNoLengthOfItsOwn() + { + Span span = FloatSpan.In("[8, 10]")!; + + string hex = span.AsHEXWKB(0); + + Assert.IsTrue(hex.Length > 0); + Assert.AreEqual("[8, 10]", Span.FromHEXWKB(hex)!.ToString()); + } + } +} diff --git a/MEOS.NET/Types/BigIntSet.g.cs b/MEOS.NET/Types/BigIntSet.g.cs index babb57f..b48cb5c 100644 --- a/MEOS.NET/Types/BigIntSet.g.cs +++ b/MEOS.NET/Types/BigIntSet.g.cs @@ -54,5 +54,18 @@ public long[] Values() public static Set? In(string str) => MEOSFactory.WrapSet(Meos.BigintsetIn(str)); + public static Set? Make(long[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.BigintsetMake(_values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/Cbuffer.g.cs b/MEOS.NET/Types/Cbuffer.g.cs index 22713e7..1c978e0 100644 --- a/MEOS.NET/Types/Cbuffer.g.cs +++ b/MEOS.NET/Types/Cbuffer.g.cs @@ -21,9 +21,43 @@ public override string ToString() public string AsEWKT(int maxdd) => Meos.CbufferAsEwkt(this.Ptr, maxdd); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.CbufferAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string AsText(int maxdd) => Meos.CbufferAsText(this.Ptr, maxdd); + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.CbufferAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Cbuffer cb2) => Meos.CbufferCmp(this.Ptr, cb2.Ptr); @@ -102,6 +136,19 @@ public bool Same(Cbuffer cb2) public static Cbuffer? FromHEXWKB(string hexwkb) => MEOSFactory.WrapCbuffer(Meos.CbufferFromHexwkb(hexwkb)); + public static Cbuffer? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapCbuffer(Meos.CbufferFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static Cbuffer? In(string str) => MEOSFactory.WrapCbuffer(Meos.CbufferIn(str)); diff --git a/MEOS.NET/Types/DateSet.g.cs b/MEOS.NET/Types/DateSet.g.cs index 779b5cf..f7cbb2a 100644 --- a/MEOS.NET/Types/DateSet.g.cs +++ b/MEOS.NET/Types/DateSet.g.cs @@ -57,5 +57,18 @@ public int[] Values() public static Set? In(string str) => MEOSFactory.WrapSet(Meos.DatesetIn(str)); + public static Set? Make(int[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.DatesetMake(_values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/FloatSet.g.cs b/MEOS.NET/Types/FloatSet.g.cs index 89889d1..366d2fa 100644 --- a/MEOS.NET/Types/FloatSet.g.cs +++ b/MEOS.NET/Types/FloatSet.g.cs @@ -69,5 +69,18 @@ public double[] Values() public static Set? In(string str) => MEOSFactory.WrapSet(Meos.FloatsetIn(str)); + public static Set? Make(double[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.FloatsetMake(_values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/IntSet.g.cs b/MEOS.NET/Types/IntSet.g.cs index 2f71aea..52bcb87 100644 --- a/MEOS.NET/Types/IntSet.g.cs +++ b/MEOS.NET/Types/IntSet.g.cs @@ -57,5 +57,18 @@ public int[] Values() public static Set? In(string str) => MEOSFactory.WrapSet(Meos.IntsetIn(str)); + public static Set? Make(int[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.IntsetMake(_values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/Npoint.g.cs b/MEOS.NET/Types/Npoint.g.cs index a6a3919..e8cd438 100644 --- a/MEOS.NET/Types/Npoint.g.cs +++ b/MEOS.NET/Types/Npoint.g.cs @@ -21,9 +21,43 @@ public override string ToString() public string AsEWKT(int maxdd) => Meos.NpointAsEwkt(this.Ptr, maxdd); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.NpointAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string AsText(int maxdd) => Meos.NpointAsText(this.Ptr, maxdd); + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.NpointAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Npoint np2) => Meos.NpointCmp(this.Ptr, np2.Ptr); @@ -90,6 +124,19 @@ public bool Same(Npoint np2) public static Npoint? FromHEXWKB(string hexwkb) => MEOSFactory.WrapNpoint(Meos.NpointFromHexwkb(hexwkb)); + public static Npoint? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapNpoint(Meos.NpointFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static Npoint? In(string str) => MEOSFactory.WrapNpoint(Meos.NpointIn(str)); diff --git a/MEOS.NET/Types/Pcpatch.g.cs b/MEOS.NET/Types/Pcpatch.g.cs index 9b11a35..bc98e83 100644 --- a/MEOS.NET/Types/Pcpatch.g.cs +++ b/MEOS.NET/Types/Pcpatch.g.cs @@ -78,5 +78,18 @@ public uint Npoints() } } + public static Pcpatch? MakeCoords(uint pcid, double[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapPcpatch(Meos.PcpatchMakeCoords(pcid, _values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/Pcpoint.g.cs b/MEOS.NET/Types/Pcpoint.g.cs index f006758..861f9be 100644 --- a/MEOS.NET/Types/Pcpoint.g.cs +++ b/MEOS.NET/Types/Pcpoint.g.cs @@ -44,5 +44,18 @@ public string HexOut(int maxdd) public static Pcpoint? HexIn(string str) => MEOSFactory.WrapPcpoint(Meos.PcpointHexIn(str)); + public static Pcpoint? Make(uint pcid, double[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapPcpoint(Meos.PcpointMake(pcid, _values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/MEOS.NET/Types/Pose.g.cs b/MEOS.NET/Types/Pose.g.cs index 49beaa8..94dd808 100644 --- a/MEOS.NET/Types/Pose.g.cs +++ b/MEOS.NET/Types/Pose.g.cs @@ -30,9 +30,43 @@ public string AsEWKT(int maxdd) public string AsGeopose(int conformance, int precision) => Meos.PoseAsGeopose(this.Ptr, conformance, precision); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.PoseAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string AsText(int maxdd) => Meos.PoseAsText(this.Ptr, maxdd); + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.PoseAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Pose pose2) => Meos.PoseCmp(this.Ptr, pose2.Ptr); @@ -138,6 +172,19 @@ public double[] Ypr() public static Pose? FromHEXWKB(string hexwkb) => MEOSFactory.WrapPose(Meos.PoseFromHexwkb(hexwkb)); + public static Pose? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapPose(Meos.PoseFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static Pose? In(string str) => MEOSFactory.WrapPose(Meos.PoseIn(str)); diff --git a/MEOS.NET/Types/PoseChain.g.cs b/MEOS.NET/Types/PoseChain.g.cs index 3a4c08a..1acd4af 100644 --- a/MEOS.NET/Types/PoseChain.g.cs +++ b/MEOS.NET/Types/PoseChain.g.cs @@ -24,9 +24,43 @@ public override string ToString() public string AsEWKT(int maxdd) => Meos.PosechainAsEwkt(this.Ptr, maxdd); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.PosechainAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string AsText(int maxdd) => Meos.PosechainAsText(this.Ptr, maxdd); + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.PosechainAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(PoseChain pc2) => Meos.PosechainCmp(this.Ptr, pc2.Ptr); @@ -120,6 +154,19 @@ public bool Same(PoseChain pc2) public static PoseChain? FromHEXWKB(string hexwkb) => MEOSFactory.WrapPoseChain(Meos.PosechainFromHexwkb(hexwkb)); + public static PoseChain? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapPoseChain(Meos.PosechainFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static PoseChain? In(string str) => MEOSFactory.WrapPoseChain(Meos.PosechainIn(str)); diff --git a/MEOS.NET/Types/Raquet.g.cs b/MEOS.NET/Types/Raquet.g.cs index 2acc778..eb367d6 100644 --- a/MEOS.NET/Types/Raquet.g.cs +++ b/MEOS.NET/Types/Raquet.g.cs @@ -18,6 +18,40 @@ internal Raquet(IntPtr ptr) : base(ptr) { } public override string ToString() => this.Out(); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.RaquetAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.RaquetAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Raquet rq2) => Meos.RaquetCmp(this.Ptr, rq2.Ptr); @@ -57,6 +91,27 @@ public double Nodata() public string Out() => Meos.RaquetOut(this.Ptr); + public byte[]? Pixels() + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.RaquetPixels(this.Ptr, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string Pixtype() => Meos.RaquetPixtype(this.Ptr); @@ -72,11 +127,37 @@ public int Width() public static Raquet? FromHEXWKB(string hexwkb) => MEOSFactory.WrapRaquet(Meos.RaquetFromHexwkb(hexwkb)); + public static Raquet? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapRaquet(Meos.RaquetFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static Raquet? In(string str) => MEOSFactory.WrapRaquet(Meos.RaquetIn(str)); public static Raquet? Read(string path, ulong quadbin) => MEOSFactory.WrapRaquet(Meos.RaquetRead(path, quadbin)); + public static Raquet? ReadBytes(byte[] data, ulong quadbin) + { + GCHandle _data = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapRaquet(Meos.RaquetReadBytes(_data.AddrOfPinnedObject(), (ulong) data.Length, quadbin)); + } + finally + { + _data.Free(); + } + } + } } diff --git a/MEOS.NET/Types/STBox.g.cs b/MEOS.NET/Types/STBox.g.cs index 54be205..8342680 100644 --- a/MEOS.NET/Types/STBox.g.cs +++ b/MEOS.NET/Types/STBox.g.cs @@ -21,6 +21,40 @@ public override string ToString() public double Area(bool spheroid) => Meos.StboxArea(this.Ptr, spheroid); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.StboxAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.StboxAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(STBox box2) => Meos.StboxCmp(this.Ptr, box2.Ptr); @@ -353,6 +387,19 @@ public double Volume() public static STBox? FromHEXWKB(string hexwkb) => MEOSFactory.WrapSTBox(Meos.StboxFromHexwkb(hexwkb)); + public static STBox? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSTBox(Meos.StboxFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static STBox? GetSpaceTile(Geo point, double xsize, double ysize, double zsize, Geo sorigin) => MEOSFactory.WrapSTBox(Meos.StboxGetSpaceTile(point.Ptr, xsize, ysize, zsize, sorigin.Ptr)); diff --git a/MEOS.NET/Types/Set.g.cs b/MEOS.NET/Types/Set.g.cs index d027a4a..092ac78 100644 --- a/MEOS.NET/Types/Set.g.cs +++ b/MEOS.NET/Types/Set.g.cs @@ -14,6 +14,40 @@ public class Set : Collection { internal Set(IntPtr ptr) : base(ptr) { } + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.SetAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.SetAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Set s2) => Meos.SetCmp(this.Ptr, s2.Ptr); @@ -71,5 +105,18 @@ public int NumValues() public static Set? FromHEXWKB(string hexwkb) => MEOSFactory.WrapSet(Meos.SetFromHexwkb(hexwkb)); + public static Set? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.SetFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + } } diff --git a/MEOS.NET/Types/Span.g.cs b/MEOS.NET/Types/Span.g.cs index b953178..8cd8a6d 100644 --- a/MEOS.NET/Types/Span.g.cs +++ b/MEOS.NET/Types/Span.g.cs @@ -14,6 +14,40 @@ public class Span : Collection { internal Span(IntPtr ptr) : base(ptr) { } + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.SpanAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.SpanAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(Span s2) => Meos.SpanCmp(this.Ptr, s2.Ptr); @@ -59,5 +93,18 @@ public bool UpperInc() public static Span? FromHEXWKB(string hexwkb) => MEOSFactory.WrapSpan(Meos.SpanFromHexwkb(hexwkb)); + public static Span? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSpan(Meos.SpanFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + } } diff --git a/MEOS.NET/Types/SpanSet.g.cs b/MEOS.NET/Types/SpanSet.g.cs index 31d1e34..ccb9dd5 100644 --- a/MEOS.NET/Types/SpanSet.g.cs +++ b/MEOS.NET/Types/SpanSet.g.cs @@ -14,6 +14,40 @@ public class SpanSet : Collection { internal SpanSet(IntPtr ptr) : base(ptr) { } + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.SpansetAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.SpansetAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(SpanSet ss2) => Meos.SpansetCmp(this.Ptr, ss2.Ptr); @@ -83,5 +117,18 @@ public bool UpperInc() public static SpanSet? FromHEXWKB(string hexwkb) => MEOSFactory.WrapSpanSet(Meos.SpansetFromHexwkb(hexwkb)); + public static SpanSet? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSpanSet(Meos.SpansetFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + } } diff --git a/MEOS.NET/Types/TBox.g.cs b/MEOS.NET/Types/TBox.g.cs index bccb117..47c82ca 100644 --- a/MEOS.NET/Types/TBox.g.cs +++ b/MEOS.NET/Types/TBox.g.cs @@ -18,6 +18,40 @@ internal TBox(IntPtr ptr) : base(ptr) { } public override string ToString() => this.Out(15); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.TboxAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.TboxAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public int Cmp(TBox box2) => Meos.TboxCmp(this.Ptr, box2.Ptr); @@ -250,6 +284,19 @@ public string Out(int maxdd) public static TBox? FromHEXWKB(string hexwkb) => MEOSFactory.WrapTBox(Meos.TboxFromHexwkb(hexwkb)); + public static TBox? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapTBox(Meos.TboxFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static TBox? In(string str) => MEOSFactory.WrapTBox(Meos.TboxIn(str)); diff --git a/MEOS.NET/Types/Temporal.g.cs b/MEOS.NET/Types/Temporal.g.cs index 957d13d..cb39b4a 100644 --- a/MEOS.NET/Types/Temporal.g.cs +++ b/MEOS.NET/Types/Temporal.g.cs @@ -34,6 +34,19 @@ internal Temporal(IntPtr ptr) : base(ptr) { } public Temporal? AppendTsequence(Temporal seq, bool expand) => MEOSFactory.WrapTemporal(Meos.TemporalAppendTsequence(this.Ptr, seq.Ptr, expand)); + public string AsHEXWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + return Meos.TemporalAsHexwkb(this.Ptr, variant, _size_out); + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public string AsMFJSON(bool with_bbox, int flags, int precision, string srs) => Meos.TemporalAsMfjson(this.Ptr, with_bbox, flags, precision, srs); @@ -46,6 +59,27 @@ public string AsMFJSON(bool with_bbox, int flags, int precision, string srs) public Temporal? AsTsequenceset(InterpType interp) => MEOSFactory.WrapTemporal(Meos.TemporalAsTsequenceset(this.Ptr, (int) interp)); + public byte[]? AsWKB(byte variant) + { + IntPtr _size_out = Marshal.AllocHGlobal(sizeof(long)); + try + { + IntPtr _bytes = Meos.TemporalAsWkb(this.Ptr, variant, _size_out); + if (_bytes == IntPtr.Zero) + { + return null; + } + + byte[] _wkb = new byte[Marshal.ReadInt64(_size_out)]; + Marshal.Copy(_bytes, _wkb, 0, _wkb.Length); + return _wkb; + } + finally + { + Marshal.FreeHGlobal(_size_out); + } + } + public Temporal? AtMax() => MEOSFactory.WrapTemporal(Meos.TemporalAtMax(this.Ptr)); @@ -418,6 +452,19 @@ public bool UpperInc() public static Temporal? FromHEXWKB(string hexwkb) => MEOSFactory.WrapTemporal(Meos.TemporalFromHexwkb(hexwkb)); + public static Temporal? FromWKB(byte[] wkb) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapTemporal(Meos.TemporalFromWkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length)); + } + finally + { + _wkb.Free(); + } + } + public static Temporal? MergeArray(Temporal[] temparr) { IntPtr[] _temparrValues = new IntPtr[temparr.Length]; diff --git a/MEOS.NET/Types/TsTzSet.g.cs b/MEOS.NET/Types/TsTzSet.g.cs index 313c633..2f295e1 100644 --- a/MEOS.NET/Types/TsTzSet.g.cs +++ b/MEOS.NET/Types/TsTzSet.g.cs @@ -88,5 +88,18 @@ public long[] Values() public static Set? In(string str) => MEOSFactory.WrapSet(Meos.TstzsetIn(str)); + public static Set? Make(long[] values) + { + GCHandle _values = GCHandle.Alloc(values, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapSet(Meos.TstzsetMake(_values.AddrOfPinnedObject(), values.Length)); + } + finally + { + _values.Free(); + } + } + } } diff --git a/tools/objectgen.py b/tools/objectgen.py index dca65e8..128a756 100644 --- a/tools/objectgen.py +++ b/tools/objectgen.py @@ -263,7 +263,9 @@ class Method: def __init__(self, name: str, ret: str, params: list[tuple[str, str]], body: str, static: bool, arrays: list[tuple[str, str]] | None = None, out_param: tuple[str, int, str] | None = None, - structs: list[tuple[str, str]] | None = None): + structs: list[tuple[str, str]] | None = None, + scalar_arrays: list[str] | None = None, + length_out: str | None = None, byte_buffer: bool = False): self.name = name self.ret = ret self.params = params @@ -275,6 +277,12 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]], self.out_param = out_param # (parameter name, struct type) for each struct argument passed by value. self.structs = structs or [] + # The caller's own arrays of scalars, pinned across the call. + self.scalar_arrays = scalar_arrays or [] + # The out-parameter MEOS states the answered buffer's length in, and + # whether that buffer is the answer itself. + self.length_out = length_out + self.byte_buffer = byte_buffer class Generator: @@ -401,8 +409,13 @@ def method_for(self, cls: str, entry: dict) -> Method | None: nxt = declared[i + 1] if i + 1 < len(declared) else None if (i == 0 and receiver_first) or not clean(p["cType"]).endswith("*"): continue - if (nxt and nxt["name"] == "count" - and clean(nxt["cType"]) in ("int", "int32", "int32_t")): + # The length is named `count` beside an array of values and `size` + # beside a byte buffer, and both say the same thing: the pointer is + # the first element of an array the caller already holds whole. + if (nxt and ((nxt["name"] == "count" + and clean(nxt["cType"]) in ("int", "int32", "int32_t")) + or (nxt["name"] == "size" + and clean(nxt["cType"]) == "size_t"))): counted.add(codegen.csharp_param_name(p["name"])) params = list(wrapper_params) @@ -413,9 +426,20 @@ def method_for(self, cls: str, entry: dict) -> Method | None: args.append("this.Ptr") params = params[1:] + # A `size_t *` out-parameter states the LENGTH of the buffer the call + # answers. It leaves the C# signature — a byte array and a string each + # carry their own length — and the buffer MEOS writes it into is + # allocated for the call. + out_params = (f.get("shape") or {}).get("outParams") or [] + length_out = next( + (codegen.csharp_param_name(p["name"]) for p in f.get("params", []) + if p["name"] in out_params and clean(p["cType"]) == "size_t *"), None) + if length_out: + out_params = [o for o in out_params + if codegen.csharp_param_name(o) != length_out] + # A `bool` return with one value out-parameter is MEOS saying whether the # value exists: the method answers the value, or nothing. - out_params = (f.get("shape") or {}).get("outParams") or [] result_out = None if clean(f["returnType"]["c"]) == "bool" and len(out_params) == 1: pointee = clean(next( @@ -430,7 +454,10 @@ def method_for(self, cls: str, entry: dict) -> Method | None: f"{pointee}, which has no reader") return None - ret = self.map_return(f, wrapper_ret) + if length_out and clean(f["returnType"]["c"]) == "uint8_t *": + ret = ("byte[]?", "$") + else: + ret = self.map_return(f, wrapper_ret) if ret is None: self.deferred[cls].append( f"{oo}: return {clean(f['returnType']['c'])} needs wrapping") @@ -456,25 +483,39 @@ def method_for(self, cls: str, entry: dict) -> Method | None: sig: list[tuple[str, str]] = [] arrays: list[tuple[str, str]] = [] + scalar_arrays: list[str] = [] structs: list[tuple[str, str]] = [] for cs_type, pname in params: if result_out is not None and pname == result_out[0]: args.append(f"_{pname}") continue + if pname == length_out: + args.append(f"_{pname}") + continue if pname in count_of: - args.append(f"{count_of[pname]}.Length") + cast = "" if cs_type == "int" else f"({cs_type}) " + args.append(f"{cast}{count_of[pname]}.Length") continue if pname in counted: + pointee = clean(c_by_name[pname])[:-1].strip() element = self.m.class_for_ctype(clean(c_by_name[pname])[:-1]) - if element is None: - self.deferred[cls].append( - f"{oo}: argument {pname} is an array of " - f"{clean(c_by_name[pname])[:-1]}, which has no class") - return None - sig.append((f"{element}[]", pname)) - arrays.append((pname, element)) - args.append(f"_{pname}.AddrOfPinnedObject()") - continue + if element is not None: + sig.append((f"{element}[]", pname)) + arrays.append((pname, element)) + args.append(f"_{pname}.AddrOfPinnedObject()") + continue + # A scalar element is the array itself: the values need no + # gathering, so what is pinned is the caller's own array. + scalar = codegen.SCALAR_MAP.get(pointee) + if scalar is not None: + sig.append((f"{scalar}[]", pname)) + scalar_arrays.append(pname) + args.append(f"_{pname}.AddrOfPinnedObject()") + continue + self.deferred[cls].append( + f"{oo}: argument {pname} is an array of " + f"{pointee}, which has no class") + return None mapped = self.map_param(c_by_name.get(pname, ""), cs_type, pname) if mapped is None: self.deferred[cls].append( @@ -491,13 +532,19 @@ def method_for(self, cls: str, entry: dict) -> Method | None: if result_out is not None: name, (_, size, reader) = result_out out = (name, size, reader.format(f"_{name}")) - if structs and (arrays or out): + if structs and (arrays or out or scalar_arrays or length_out): self.deferred[cls].append( f"{oo}: a struct argument beside a counted array or an " "out-parameter needs one body doing both") return None + if length_out and (arrays or out): + self.deferred[cls].append( + f"{oo}: a length out-parameter beside a counted array or a " + "value out-parameter needs one body doing both") + return None return Method(pascal(oo), ret_type, sig, ret_expr.replace("$", call), static, - arrays, out, structs) + arrays, out, structs, scalar_arrays, length_out, + ret_type == "byte[]?") def inherited_names(self, cls: str) -> set[tuple]: names: set[tuple] = set() @@ -548,7 +595,9 @@ def emit_class(self, cls: str) -> str: lines.append(f" public {new}{kind}{m.ret} {m.name}({args})") if m.out_param: lines.extend(self.out_param_body(m)) - elif m.arrays: + elif m.length_out: + lines.extend(self.buffer_body(m)) + elif m.arrays or m.scalar_arrays: lines.extend(self.array_body(m)) elif m.structs: lines.extend(self.struct_body(m)) @@ -610,6 +659,44 @@ def struct_body(self, method: Method) -> list[str]: lines += [" }", " }"] return lines + def buffer_body(self, method: Method) -> list[str]: + """The body of a method whose call states a length through a pointer. + + MEOS writes the length of what it answers into a buffer the caller + supplies. A string carries its own length, so there the buffer is + written and dropped; a byte array does not, so there it says how much of + what MEOS answered to copy.""" + name = method.length_out + lines = [ + " {", + f" IntPtr _{name} = Marshal.AllocHGlobal(sizeof(long));", + " try", + " {", + ] + if method.byte_buffer: + lines += [ + f" IntPtr _bytes = {method.body};", + " if (_bytes == IntPtr.Zero)", + " {", + " return null;", + " }", + "", + f" byte[] _wkb = new byte[Marshal.ReadInt64(_{name})];", + " Marshal.Copy(_bytes, _wkb, 0, _wkb.Length);", + " return _wkb;", + ] + else: + lines.append(f" return {method.body};") + lines += [ + " }", + " finally", + " {", + f" Marshal.FreeHGlobal(_{name});", + " }", + " }", + ] + return lines + def array_body(self, method: Method) -> list[str]: """The body of a method taking a counted array. @@ -618,6 +705,10 @@ def array_body(self, method: Method) -> list[str]: for the call. MEOS copies what it keeps, so the pin lasts exactly as long as the call does.""" lines = [" {"] + for name in method.scalar_arrays: + lines.append( + f" GCHandle _{name} = " + f"GCHandle.Alloc({ident(name)}, GCHandleType.Pinned);") for name, element in method.arrays: lines += [ f" IntPtr[] _{name}Values = new IntPtr[{ident(name)}.Length];", @@ -634,6 +725,8 @@ def array_body(self, method: Method) -> list[str]: lines.append(" }") lines.append(" finally") lines.append(" {") + for name in method.scalar_arrays: + lines.append(f" _{name}.Free();") for name, _ in method.arrays: lines.append(f" _{name}.Free();") lines += [" }", " }"]