From 982e65ab395ee65c2256c1c527eac6e593b1df74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Wed, 2 Sep 2026 22:59:42 +0200 Subject: [PATCH] Return a MEOS struct as the struct, not as a pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three MEOS functions hand a struct back by value — `tpoint_as_mvtgeom` answers `MvtGeom`, `tgeo_space_split` answers `SpaceSplit`, `tgeo_space_time_split` answers `SpaceTimeSplit` — and a struct return is not a pointer. On the ABI these bindings ship for, a 24-byte struct comes back through a hidden first argument the caller supplies, so a binding that declares the return as a pointer shifts every real argument by one register and the callee reads the second argument where the first belongs. Measured on `tpoint_as_mvtgeom` over a temporal point and an STBox: with the return declared `void *` the call answers `The temporal value must be a temporal point`, MEOS validating the STBox it receives in place of the temporal value, and with the return declared `MvtGeom` the same call answers `count=2` and two live pointers. The C# form of such a struct comes from the catalog's own `structs` entry: field for field, a pointer as `IntPtr` and a scalar through the same map the parameters use, under `[StructLayout(LayoutKind.Sequential)]`. Only a struct a function actually returns or takes by value gets one, which the generator reads off the signatures rather than a list — every other MEOS struct crosses the boundary as a pointer and stays opaque. Two tests read every field of one. The MvtGeom test reads its geometry back through `geo_as_text` and walks its `times` array for exactly `count` increasing timestamps; the SpaceSplit test reads each fragment through `tspatial_as_text` and each bin through `geo_as_text`. A field at the wrong offset, or a call whose arguments shifted, answers neither. --- MEOS.NET.Tests/ByValueStructTests.cs | 65 +++++++++++++++++ MEOS.NET/Functions/Meos.Native.g.cs | 8 +- MEOS.NET/Functions/Meos.hashtable.g.cs | 2 + MEOS.NET/Functions/Meos.meos.g.cs | 2 + MEOS.NET/Functions/Meos.meos_catalog.g.cs | 2 + MEOS.NET/Functions/Meos.meos_cbuffer.g.cs | 2 + MEOS.NET/Functions/Meos.meos_cellindex.g.cs | 2 + MEOS.NET/Functions/Meos.meos_geo.g.cs | 14 ++-- MEOS.NET/Functions/Meos.meos_h3.g.cs | 2 + MEOS.NET/Functions/Meos.meos_internal.g.cs | 2 + .../Functions/Meos.meos_internal_geo.g.cs | 2 + MEOS.NET/Functions/Meos.meos_json.g.cs | 2 + MEOS.NET/Functions/Meos.meos_npoint.g.cs | 2 + MEOS.NET/Functions/Meos.meos_pointcloud.g.cs | 2 + MEOS.NET/Functions/Meos.meos_pose.g.cs | 2 + MEOS.NET/Functions/Meos.meos_quadbin.g.cs | 2 + MEOS.NET/Functions/Meos.meos_raster.g.cs | 2 + MEOS.NET/Functions/Meos.meos_rgeo.g.cs | 2 + MEOS.NET/Functions/Meos.meos_s2cell.g.cs | 2 + MEOS.NET/Functions/Meos.pc_api.g.cs | 2 + MEOS.NET/Functions/Meos.pg_bool.g.cs | 2 + MEOS.NET/Functions/Meos.pg_date.g.cs | 2 + MEOS.NET/Functions/Meos.pg_float.g.cs | 2 + MEOS.NET/Functions/Meos.pg_int.g.cs | 2 + MEOS.NET/Functions/Meos.pg_interval.g.cs | 2 + MEOS.NET/Functions/Meos.pg_numeric.g.cs | 2 + MEOS.NET/Functions/Meos.pg_text.g.cs | 2 + MEOS.NET/Functions/Meos.pg_time.g.cs | 2 + MEOS.NET/Functions/Meos.pg_timestamp.g.cs | 2 + MEOS.NET/Functions/Meos.pgsql_compat.g.cs | 2 + MEOS.NET/Functions/Meos.pgtypes.g.cs | 2 + MEOS.NET/Structures/MeosStructs.g.cs | 39 ++++++++++ tools/codegen.py | 73 ++++++++++++++++++- 33 files changed, 245 insertions(+), 10 deletions(-) create mode 100644 MEOS.NET.Tests/ByValueStructTests.cs create mode 100644 MEOS.NET/Structures/MeosStructs.g.cs diff --git a/MEOS.NET.Tests/ByValueStructTests.cs b/MEOS.NET.Tests/ByValueStructTests.cs new file mode 100644 index 0000000..ad26c23 --- /dev/null +++ b/MEOS.NET.Tests/ByValueStructTests.cs @@ -0,0 +1,65 @@ +using System.Runtime.InteropServices; + +using MEOS.NET.Functions; +using MEOS.NET.Structures; +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// The three MEOS functions that hand a struct back by value. A struct return + /// is not a pointer — the ABI passes it in registers or through a hidden + /// pointer the caller supplies — so a field read out of one is only the field + /// MEOS filled when the return is declared as the struct it is. + /// + [TestClass] + public class ByValueStructTests : MeosTest + { + private const string Trip = + "[POINT(1 1)@2000-01-01, POINT(2 2)@2000-01-02, POINT(3 3)@2000-01-03]"; + + [TestMethod] + public void EveryFieldOfAnMvtGeomIsTheOneMeosFilled() + { + Temporal trip = TGeomPoint.In(Trip)!; + STBox bounds = STBox.In("STBOX XT(((0,0),(10,10)),[2000-01-01, 2000-01-04])")!; + + MvtGeom mvt = Meos.TpointAsMvtgeom(trip.Ptr, bounds.Ptr, 4096, 0, true); + + // The geometry field is a GSERIALIZED the geometry functions read. + StringAssert.StartsWith(Meos.GeoAsText(mvt.geom, 6), "LINESTRING"); + + // The times field is an array of exactly count timestamps, in order. + Assert.IsTrue(mvt.count > 0, $"count was {mvt.count}"); + long previous = long.MinValue; + for (int i = 0; i < mvt.count; i++) + { + long moment = Marshal.ReadInt64(mvt.times, i * sizeof(long)); + Assert.IsTrue(moment > previous, $"timestamp {i} is out of order"); + previous = moment; + } + } + + [TestMethod] + public void EveryFieldOfASpaceSplitIsTheOneMeosFilled() + { + Temporal trip = TGeomPoint.In(Trip)!; + IntPtr origin = Meos.GeomIn("POINT(0 0)", -1); + + SpaceSplit split = Meos.TgeoSpaceSplit( + trip.Ptr, 2.0, 2.0, 2.0, origin, false, true); + + Assert.IsTrue(split.count > 0, $"count was {split.count}"); + for (int i = 0; i < split.count; i++) + { + IntPtr fragment = Marshal.ReadIntPtr(split.fragments, i * IntPtr.Size); + IntPtr bin = Marshal.ReadIntPtr(split.bins, i * IntPtr.Size); + + // Each fragment is a temporal point and each bin the geometry it + // falls in, so both read back through their own MEOS functions. + StringAssert.Contains(Meos.TspatialAsText(fragment, 6), "POINT"); + StringAssert.StartsWith(Meos.GeoAsText(bin, 6), "POINT"); + } + } + } +} diff --git a/MEOS.NET/Functions/Meos.Native.g.cs b/MEOS.NET/Functions/Meos.Native.g.cs index b40fd7e..a5393d6 100644 --- a/MEOS.NET/Functions/Meos.Native.g.cs +++ b/MEOS.NET/Functions/Meos.Native.g.cs @@ -3,6 +3,8 @@ using System.CodeDom.Compiler; using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { public static partial class Meos @@ -5746,7 +5748,7 @@ private static partial class Native internal static partial IntPtr TgeompointToTgeometry(IntPtr temp); [LibraryImport(DllPath, EntryPoint = "tpoint_as_mvtgeom", StringMarshalling = StringMarshalling.Utf8)] - internal static partial IntPtr TpointAsMvtgeom(IntPtr temp, IntPtr bounds, int extent, int buffer, [MarshalAs(UnmanagedType.U1)] bool clip_geom); + internal static partial MvtGeom TpointAsMvtgeom(IntPtr temp, IntPtr bounds, int extent, int buffer, [MarshalAs(UnmanagedType.U1)] bool clip_geom); [LibraryImport(DllPath, EntryPoint = "tpoint_tfloat_to_geomeas", StringMarshalling = StringMarshalling.Utf8)] [return: MarshalAs(UnmanagedType.U1)] @@ -6481,10 +6483,10 @@ private static partial class Native internal static partial IntPtr StboxTimeTiles(IntPtr bounds, IntPtr duration, long torigin, [MarshalAs(UnmanagedType.U1)] bool border_inc, IntPtr count); [LibraryImport(DllPath, EntryPoint = "tgeo_space_split", StringMarshalling = StringMarshalling.Utf8)] - internal static partial IntPtr TgeoSpaceSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr sorigin, [MarshalAs(UnmanagedType.U1)] bool bitmatrix, [MarshalAs(UnmanagedType.U1)] bool border_inc); + internal static partial SpaceSplit TgeoSpaceSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr sorigin, [MarshalAs(UnmanagedType.U1)] bool bitmatrix, [MarshalAs(UnmanagedType.U1)] bool border_inc); [LibraryImport(DllPath, EntryPoint = "tgeo_space_time_split", StringMarshalling = StringMarshalling.Utf8)] - internal static partial IntPtr TgeoSpaceTimeSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr duration, IntPtr sorigin, long torigin, [MarshalAs(UnmanagedType.U1)] bool bitmatrix, [MarshalAs(UnmanagedType.U1)] bool border_inc); + internal static partial SpaceTimeSplit TgeoSpaceTimeSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr duration, IntPtr sorigin, long torigin, [MarshalAs(UnmanagedType.U1)] bool bitmatrix, [MarshalAs(UnmanagedType.U1)] bool border_inc); [LibraryImport(DllPath, EntryPoint = "geo_cluster_kmeans", StringMarshalling = StringMarshalling.Utf8)] internal static partial IntPtr GeoClusterKmeans(IntPtr geoms, uint ngeoms, uint k, IntPtr count); diff --git a/MEOS.NET/Functions/Meos.hashtable.g.cs b/MEOS.NET/Functions/Meos.hashtable.g.cs index 0046658..0edb4ac 100644 --- a/MEOS.NET/Functions/Meos.hashtable.g.cs +++ b/MEOS.NET/Functions/Meos.hashtable.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions hashtable.h declares. diff --git a/MEOS.NET/Functions/Meos.meos.g.cs b/MEOS.NET/Functions/Meos.meos.g.cs index 3f8d40f..f6ded9e 100644 --- a/MEOS.NET/Functions/Meos.meos.g.cs +++ b/MEOS.NET/Functions/Meos.meos.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_catalog.g.cs b/MEOS.NET/Functions/Meos.meos_catalog.g.cs index fe6baf3..ab3a246 100644 --- a/MEOS.NET/Functions/Meos.meos_catalog.g.cs +++ b/MEOS.NET/Functions/Meos.meos_catalog.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_catalog.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_cbuffer.g.cs b/MEOS.NET/Functions/Meos.meos_cbuffer.g.cs index a2fe1af..5147c89 100644 --- a/MEOS.NET/Functions/Meos.meos_cbuffer.g.cs +++ b/MEOS.NET/Functions/Meos.meos_cbuffer.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_cbuffer.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_cellindex.g.cs b/MEOS.NET/Functions/Meos.meos_cellindex.g.cs index 7baeef9..1ab9ebc 100644 --- a/MEOS.NET/Functions/Meos.meos_cellindex.g.cs +++ b/MEOS.NET/Functions/Meos.meos_cellindex.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_cellindex.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_geo.g.cs b/MEOS.NET/Functions/Meos.meos_geo.g.cs index 3a1f76f..8a08333 100644 --- a/MEOS.NET/Functions/Meos.meos_geo.g.cs +++ b/MEOS.NET/Functions/Meos.meos_geo.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_geo.h declares. @@ -760,8 +762,8 @@ public static IntPtr TgeometryToTgeompoint(IntPtr temp) public static IntPtr TgeompointToTgeometry(IntPtr temp) => SafeExecution(() => Native.TgeompointToTgeometry(temp)); - public static IntPtr TpointAsMvtgeom(IntPtr temp, IntPtr bounds, int extent, int buffer, bool clip_geom) - => SafeExecution(() => Native.TpointAsMvtgeom(temp, bounds, extent, buffer, clip_geom)); + public static MvtGeom TpointAsMvtgeom(IntPtr temp, IntPtr bounds, int extent, int buffer, bool clip_geom) + => SafeExecution(() => Native.TpointAsMvtgeom(temp, bounds, extent, buffer, clip_geom)); public static bool TpointTfloatToGeomeas(IntPtr tpoint, IntPtr measure, bool segmentize, IntPtr result) => SafeExecution(() => Native.TpointTfloatToGeomeas(tpoint, measure, segmentize, result)); @@ -1714,11 +1716,11 @@ public static IntPtr[] StboxTimeTiles(IntPtr bounds, IntPtr duration, long torig finally { Marshal.FreeHGlobal(_cnt); } } - public static IntPtr TgeoSpaceSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr sorigin, bool bitmatrix, bool border_inc) - => SafeExecution(() => Native.TgeoSpaceSplit(temp, xsize, ysize, zsize, sorigin, bitmatrix, border_inc)); + public static SpaceSplit TgeoSpaceSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr sorigin, bool bitmatrix, bool border_inc) + => SafeExecution(() => Native.TgeoSpaceSplit(temp, xsize, ysize, zsize, sorigin, bitmatrix, border_inc)); - public static IntPtr TgeoSpaceTimeSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr duration, IntPtr sorigin, long torigin, bool bitmatrix, bool border_inc) - => SafeExecution(() => Native.TgeoSpaceTimeSplit(temp, xsize, ysize, zsize, duration, sorigin, torigin, bitmatrix, border_inc)); + public static SpaceTimeSplit TgeoSpaceTimeSplit(IntPtr temp, double xsize, double ysize, double zsize, IntPtr duration, IntPtr sorigin, long torigin, bool bitmatrix, bool border_inc) + => SafeExecution(() => Native.TgeoSpaceTimeSplit(temp, xsize, ysize, zsize, duration, sorigin, torigin, bitmatrix, border_inc)); public static int[] GeoClusterKmeans(IntPtr geoms, uint ngeoms, uint k) { diff --git a/MEOS.NET/Functions/Meos.meos_h3.g.cs b/MEOS.NET/Functions/Meos.meos_h3.g.cs index edd4f5e..3d047f9 100644 --- a/MEOS.NET/Functions/Meos.meos_h3.g.cs +++ b/MEOS.NET/Functions/Meos.meos_h3.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_h3.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_internal.g.cs b/MEOS.NET/Functions/Meos.meos_internal.g.cs index 8e8612b..4208a3a 100644 --- a/MEOS.NET/Functions/Meos.meos_internal.g.cs +++ b/MEOS.NET/Functions/Meos.meos_internal.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_internal.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_internal_geo.g.cs b/MEOS.NET/Functions/Meos.meos_internal_geo.g.cs index 5e2f247..04d0ff3 100644 --- a/MEOS.NET/Functions/Meos.meos_internal_geo.g.cs +++ b/MEOS.NET/Functions/Meos.meos_internal_geo.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_internal_geo.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_json.g.cs b/MEOS.NET/Functions/Meos.meos_json.g.cs index 5385087..10f7a4c 100644 --- a/MEOS.NET/Functions/Meos.meos_json.g.cs +++ b/MEOS.NET/Functions/Meos.meos_json.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_json.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_npoint.g.cs b/MEOS.NET/Functions/Meos.meos_npoint.g.cs index a7eccdb..94245a1 100644 --- a/MEOS.NET/Functions/Meos.meos_npoint.g.cs +++ b/MEOS.NET/Functions/Meos.meos_npoint.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_npoint.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_pointcloud.g.cs b/MEOS.NET/Functions/Meos.meos_pointcloud.g.cs index 351ea04..f2ff594 100644 --- a/MEOS.NET/Functions/Meos.meos_pointcloud.g.cs +++ b/MEOS.NET/Functions/Meos.meos_pointcloud.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_pointcloud.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_pose.g.cs b/MEOS.NET/Functions/Meos.meos_pose.g.cs index 6a21533..a0435af 100644 --- a/MEOS.NET/Functions/Meos.meos_pose.g.cs +++ b/MEOS.NET/Functions/Meos.meos_pose.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_pose.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs index 1d979a5..22e4723 100644 --- a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs +++ b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_quadbin.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_raster.g.cs b/MEOS.NET/Functions/Meos.meos_raster.g.cs index 47cc5f7..d585ecd 100644 --- a/MEOS.NET/Functions/Meos.meos_raster.g.cs +++ b/MEOS.NET/Functions/Meos.meos_raster.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_raster.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_rgeo.g.cs b/MEOS.NET/Functions/Meos.meos_rgeo.g.cs index ad2c484..8449d4b 100644 --- a/MEOS.NET/Functions/Meos.meos_rgeo.g.cs +++ b/MEOS.NET/Functions/Meos.meos_rgeo.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_rgeo.h declares. diff --git a/MEOS.NET/Functions/Meos.meos_s2cell.g.cs b/MEOS.NET/Functions/Meos.meos_s2cell.g.cs index 42bd0a1..23621c6 100644 --- a/MEOS.NET/Functions/Meos.meos_s2cell.g.cs +++ b/MEOS.NET/Functions/Meos.meos_s2cell.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions meos_s2cell.h declares. diff --git a/MEOS.NET/Functions/Meos.pc_api.g.cs b/MEOS.NET/Functions/Meos.pc_api.g.cs index 2ed3dd7..ba74bcf 100644 --- a/MEOS.NET/Functions/Meos.pc_api.g.cs +++ b/MEOS.NET/Functions/Meos.pc_api.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pc_api.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_bool.g.cs b/MEOS.NET/Functions/Meos.pg_bool.g.cs index cc8bb5a..454d7c3 100644 --- a/MEOS.NET/Functions/Meos.pg_bool.g.cs +++ b/MEOS.NET/Functions/Meos.pg_bool.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_bool.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_date.g.cs b/MEOS.NET/Functions/Meos.pg_date.g.cs index eddf146..649cb74 100644 --- a/MEOS.NET/Functions/Meos.pg_date.g.cs +++ b/MEOS.NET/Functions/Meos.pg_date.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_date.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_float.g.cs b/MEOS.NET/Functions/Meos.pg_float.g.cs index 02a4f88..ebb7301 100644 --- a/MEOS.NET/Functions/Meos.pg_float.g.cs +++ b/MEOS.NET/Functions/Meos.pg_float.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_float.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_int.g.cs b/MEOS.NET/Functions/Meos.pg_int.g.cs index 3ad943d..711b318 100644 --- a/MEOS.NET/Functions/Meos.pg_int.g.cs +++ b/MEOS.NET/Functions/Meos.pg_int.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_int.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_interval.g.cs b/MEOS.NET/Functions/Meos.pg_interval.g.cs index 8d6a1a8..5e28934 100644 --- a/MEOS.NET/Functions/Meos.pg_interval.g.cs +++ b/MEOS.NET/Functions/Meos.pg_interval.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_interval.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_numeric.g.cs b/MEOS.NET/Functions/Meos.pg_numeric.g.cs index 62e037e..a743327 100644 --- a/MEOS.NET/Functions/Meos.pg_numeric.g.cs +++ b/MEOS.NET/Functions/Meos.pg_numeric.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_numeric.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_text.g.cs b/MEOS.NET/Functions/Meos.pg_text.g.cs index 4c5ec7e..0fbfc13 100644 --- a/MEOS.NET/Functions/Meos.pg_text.g.cs +++ b/MEOS.NET/Functions/Meos.pg_text.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_text.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_time.g.cs b/MEOS.NET/Functions/Meos.pg_time.g.cs index 06a32e8..f2e43b5 100644 --- a/MEOS.NET/Functions/Meos.pg_time.g.cs +++ b/MEOS.NET/Functions/Meos.pg_time.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_time.h declares. diff --git a/MEOS.NET/Functions/Meos.pg_timestamp.g.cs b/MEOS.NET/Functions/Meos.pg_timestamp.g.cs index b5f18bc..43ad7b0 100644 --- a/MEOS.NET/Functions/Meos.pg_timestamp.g.cs +++ b/MEOS.NET/Functions/Meos.pg_timestamp.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pg_timestamp.h declares. diff --git a/MEOS.NET/Functions/Meos.pgsql_compat.g.cs b/MEOS.NET/Functions/Meos.pgsql_compat.g.cs index 770134e..86d8eab 100644 --- a/MEOS.NET/Functions/Meos.pgsql_compat.g.cs +++ b/MEOS.NET/Functions/Meos.pgsql_compat.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pgsql_compat.h declares. diff --git a/MEOS.NET/Functions/Meos.pgtypes.g.cs b/MEOS.NET/Functions/Meos.pgtypes.g.cs index 6a77cde..5dd3553 100644 --- a/MEOS.NET/Functions/Meos.pgtypes.g.cs +++ b/MEOS.NET/Functions/Meos.pgtypes.g.cs @@ -2,6 +2,8 @@ using System.Runtime.InteropServices; +using MEOS.NET.Structures; + namespace MEOS.NET.Functions { /// The MEOS functions pgtypes.h declares. diff --git a/MEOS.NET/Structures/MeosStructs.g.cs b/MEOS.NET/Structures/MeosStructs.g.cs new file mode 100644 index 0000000..8728d90 --- /dev/null +++ b/MEOS.NET/Structures/MeosStructs.g.cs @@ -0,0 +1,39 @@ +#nullable enable + +using System.CodeDom.Compiler; +using System.Runtime.InteropServices; + +namespace MEOS.NET.Structures +{ + /// The MEOS MvtGeom struct, as MEOS returns it. + [GeneratedCode("MEOS.NET.Codegen", "0.1.0")] + [StructLayout(LayoutKind.Sequential)] + public struct MvtGeom + { + public IntPtr geom; + public IntPtr times; + public int count; + } + + /// The MEOS SpaceSplit struct, as MEOS returns it. + [GeneratedCode("MEOS.NET.Codegen", "0.1.0")] + [StructLayout(LayoutKind.Sequential)] + public struct SpaceSplit + { + public IntPtr fragments; + public IntPtr bins; + public int count; + } + + /// The MEOS SpaceTimeSplit struct, as MEOS returns it. + [GeneratedCode("MEOS.NET.Codegen", "0.1.0")] + [StructLayout(LayoutKind.Sequential)] + public struct SpaceTimeSplit + { + public IntPtr fragments; + public IntPtr space_bins; + public IntPtr time_bins; + public int count; + } + +} diff --git a/tools/codegen.py b/tools/codegen.py index 281a751..4d2c277 100755 --- a/tools/codegen.py +++ b/tools/codegen.py @@ -27,6 +27,7 @@ # per MEOS header exactly as the catalog groups the functions. NAMESPACE = "MEOS.NET.Functions" CLASS = "Meos" +STRUCT_NAMESPACE = "MEOS.NET.Structures" # Canonical C type -> C# parameter/return type. # `canonical` field from meos-idl.json is libclang-normalized (e.g. int32_t -> int). @@ -89,6 +90,12 @@ def configure(idl: dict) -> None: ENUM_TYPES.update(e["name"] for e in idl.get("enums", []) if e.get("name")) STRUCTS.clear() STRUCTS.update({s["name"]: s for s in idl.get("structs", []) if s.get("fields")}) + BY_VALUE_STRUCTS.clear() + for f in idl.get("functions", []): + for spot in [f["returnType"]] + list(f.get("params", [])): + canonical = spot["canonical"].replace("const ", "").strip() + if "*" not in canonical and canonical in STRUCTS: + BY_VALUE_STRUCTS.add(canonical) # Size and alignment in bytes of the scalar C types a struct field can have, on @@ -104,6 +111,56 @@ def configure(idl: dict) -> None: } +# The catalog structs a function returns or takes BY VALUE, filled in by +# ``configure``. Those are the only ones the binding gives a C# type: every +# other MEOS struct crosses the boundary as a pointer and stays opaque. +BY_VALUE_STRUCTS: set[str] = set() + + +def csharp_field_type(c_type: str) -> str: + """The C# type of a by-value struct's field.""" + t = c_type.replace("const ", "").strip() + if t.endswith("*"): + return "IntPtr" + if t in ENUM_TYPES: + return "int" + if t not in SCALAR_MAP: + raise SystemExit(f"codegen: struct field type {t!r} has no C# mapping") + return SCALAR_MAP[t] + + +def gen_structs() -> str: + """The C# form of each struct a function hands back by value. + + A struct return is not a pointer: the ABI passes it in registers or through a + hidden pointer the caller supplies, so declaring it as an IntPtr reads an + address where there is none.""" + lines = [ + "#nullable enable", + "", + "using System.CodeDom.Compiler;", + "using System.Runtime.InteropServices;", + "", + f"namespace {STRUCT_NAMESPACE}", + "{", + ] + for name in sorted(BY_VALUE_STRUCTS): + struct = STRUCTS[name] + lines += [ + f" /// The MEOS {name} struct, as MEOS returns it.", + f' [GeneratedCode("MEOS.NET.Codegen", "{GENERATOR_VERSION}")]', + " [StructLayout(LayoutKind.Sequential)]", + f" public struct {name}", + " {", + ] + for field in struct["fields"]: + lines.append(f" public {csharp_field_type(field['cType'])} " + f"{csharp_param_name(field['name'])};") + lines += [" }", ""] + lines += ["}", ""] + return "\n".join(lines) + + def _type_layout(c_type: str) -> tuple[int, int]: """``(size, alignment)`` in bytes of a struct field's type: a scalar, a pointer, a fixed-size array, or a nested catalog struct.""" @@ -160,6 +217,8 @@ def csharp_type_for(canonical: str) -> str: return "IntPtr" if t in ENUM_TYPES: return "int" + if t in BY_VALUE_STRUCTS: + return t return SCALAR_MAP.get(t, "IntPtr") # unknown scalar -> opaque pointer is safer than guessing @@ -214,6 +273,8 @@ def gen_external_functions(funcs: list[dict]) -> str: lines.append("using System.CodeDom.Compiler;") lines.append("using System.Runtime.InteropServices;") lines.append("") + lines.append(f"using {STRUCT_NAMESPACE};") + lines.append("") lines.append(f"namespace {NAMESPACE}") lines.append("{") lines.append(f" public static partial class {CLASS}") @@ -547,6 +608,8 @@ def gen_exposed_functions(funcs: list[dict], header: str | None = None) -> str: lines.append("") lines.append("using System.Runtime.InteropServices;") lines.append("") + lines.append(f"using {STRUCT_NAMESPACE};") + lines.append("") lines.append(f"namespace {NAMESPACE}") lines.append("{") if header: @@ -624,13 +687,21 @@ def main(idl_path: str, dll_path: str = DLL_PATH) -> None: if clashing: raise SystemExit(f"codegen: C names sharing one C# name: {clashing}") + struct_dir = repo_root / "MEOS.NET" / "Structures" + if struct_dir.exists(): + for stale in struct_dir.glob("*.g.cs"): + stale.unlink() + struct_dir.mkdir(parents=True, exist_ok=True) + (struct_dir / "MeosStructs.g.cs").write_text(gen_structs()) + (out_dir / "Meos.Native.g.cs").write_text(gen_external_functions(funcs)) grouped = by_header(funcs) for header, group in sorted(grouped.items()): stem = header.removesuffix(".h") (out_dir / f"Meos.{stem}.g.cs").write_text(gen_exposed_functions(group, header)) print(f"Wrote {len(funcs)} functions across {len(grouped)} headers " - f"to MEOS.NET/Functions/", file=sys.stderr) + f"to MEOS.NET/Functions/, {len(BY_VALUE_STRUCTS)} by-value structs", + file=sys.stderr) if __name__ == "__main__":