diff --git a/csharp/src/Apache.Arrow/C/Import.cs b/csharp/src/Apache.Arrow/C/CArrowSchema.cs similarity index 80% rename from csharp/src/Apache.Arrow/C/Import.cs rename to csharp/src/Apache.Arrow/C/CArrowSchema.cs index 013706428a5e9..56836b8c10459 100644 --- a/csharp/src/Apache.Arrow/C/Import.cs +++ b/csharp/src/Apache.Arrow/C/CArrowSchema.cs @@ -23,14 +23,21 @@ using Apache.Arrow.Types; [UnmanagedFunctionPointer(CallingConvention.StdCall)] -public delegate void ReleaseFFIArrowSchema(IntPtr schema); +public delegate void ReleaseCArrowSchema(IntPtr schema); namespace Apache.Arrow.C { - - + /// + /// An Arrow C Data Interface Schema, which represents a type, field, or schema. + /// + /// + /// + /// This is used to export , , or + /// to other languages. It matches the layout of the + /// ArrowSchema struct described in https://github.com/apache/arrow/blob/main/cpp/src/arrow/c/abi.h. + /// + /// Initialize the exported C schema as an Arrow type. + /// + /// The Arrow type to export. + /// An uninitialized CArrowSchema. + public static void ExportDataType(IArrowType datatype, out CArrowSchema schema) { schema.format = GetFormat(datatype); schema.name = null; @@ -130,26 +142,32 @@ public static void ExportDataType(IArrowType datatype, out FFIArrowSchema schema schema.release = (IntPtr self) => { - var schema = Marshal.PtrToStructure(self); + var schema = Marshal.PtrToStructure(self); if (schema.n_children > 0) { for (int i = 0; i < schema.n_children; i++) { FreePtr(schema.children[i]); } + Marshal.FreeHGlobal((IntPtr)schema.children); } if (schema.dictionary != IntPtr.Zero) { FreePtr(schema.dictionary); } - Marshal.DestroyStructure(self); + Marshal.DestroyStructure(self); }; schema.private_data = IntPtr.Zero; } - public static void ExportField(Field field, out FFIArrowSchema schema) + /// + /// Initialize the exported C schema as a field. + /// + /// Field to export. + /// An uninitialized CArrowSchema. + public static void ExportField(Field field, out CArrowSchema schema) { ExportDataType(field.DataType, out schema); schema.name = field.Name; @@ -158,23 +176,41 @@ public static void ExportField(Field field, out FFIArrowSchema schema) schema.flags = GetFlags(field.DataType, field.IsNullable); } - public static void ExportSchema(Schema schema, out FFIArrowSchema out_schema) + /// + /// Initialize the exported C schema as a schema. + /// + /// Schema to export. + /// An uninitialized CArrowSchema + public static void ExportSchema(Schema schema, out CArrowSchema out_schema) { // TODO: top-level metadata var struct_type = new StructType(schema.Fields.Values.ToList()); ExportDataType(struct_type, out out_schema); } + /// + /// Allocate an unmanaged pointer and copy this instances data to it. + /// + /// + /// To avoid a memory leak, you must call on this + /// pointer when done using it. + /// public IntPtr AllocateAsPtr() { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(this)); - Marshal.StructureToPtr(this, ptr, false); + Marshal.StructureToPtr(this, ptr, false); return ptr; } + /// + /// Free a pointer that was allocated in . + /// + /// + /// Do not call this on a pointer that was allocated elsewhere. + /// public static void FreePtr(IntPtr ptr) { - var schema = Marshal.PtrToStructure(ptr); + var schema = Marshal.PtrToStructure(ptr); if (schema.release != null) { // Call release if not already called. @@ -190,7 +226,7 @@ public static void FreePtr(IntPtr ptr) /// public IntPtr Export(IntPtr ptr) { - Marshal.StructureToPtr(this, ptr, false); + Marshal.StructureToPtr(this, ptr, false); return ptr; } @@ -302,15 +338,32 @@ public void Visit(IArrowType type) } } - public class ImportedArrowSchema : IDisposable + /// + /// A imported from somewhere else. + /// + /// + /// + /// Typically, when importing a schema we will allocate an uninitialized + /// , pass the pointer to the foreign function, + /// then construct this class with the initialized pointer. + /// + /// + /// var c_schema = new CArrowSchema(); + /// IntPtr imported_ptr = c_schema.AllocateAsPtr(); + /// foreign_export_function(imported_ptr); + /// var imported_type = new ImportedArrowSchema(imported_ptr); + /// ArrowType arrow_type = imported_type.GetAsType(); + /// + /// + public sealed class ImportedArrowSchema : IDisposable { - private FFIArrowSchema _data; + private CArrowSchema _data; private IntPtr _handle; private bool _is_root; public ImportedArrowSchema(IntPtr handle) { - _data = Marshal.PtrToStructure(handle); + _data = Marshal.PtrToStructure(handle); if (_data.release == null) { throw new Exception("Tried to import a schema that has already been released."); @@ -353,7 +406,7 @@ public ArrowType GetAsType() var dictionary_schema = new ImportedArrowSchema(_data.dictionary, /*is_root*/ false); var dictionary_type = dictionary_schema.GetAsType(); - bool ordered = (_data.flags & FFIArrowSchema.ARROW_FLAG_NULLABLE) == FFIArrowSchema.ARROW_FLAG_NULLABLE; + bool ordered = (_data.flags & CArrowSchema.ARROW_FLAG_NULLABLE) == CArrowSchema.ARROW_FLAG_NULLABLE; return new DictionaryType(indices_type, dictionary_type, ordered); } @@ -444,7 +497,7 @@ public Field GetAsField() { string field_name = string.IsNullOrEmpty(_data.name) ? "" : _data.name; - bool nullable = (_data.flags & FFIArrowSchema.ARROW_FLAG_NULLABLE) == FFIArrowSchema.ARROW_FLAG_NULLABLE; + bool nullable = (_data.flags & CArrowSchema.ARROW_FLAG_NULLABLE) == CArrowSchema.ARROW_FLAG_NULLABLE; return new Field(field_name, GetAsType(), nullable); } diff --git a/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs b/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs index a0100fd0bf1e6..5f120ee86ec93 100644 --- a/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs +++ b/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs @@ -134,8 +134,8 @@ public void ImportType() foreach ((Field field, dynamic py_field) in schema.Fields.Values.AsEnumerable() .Zip(py_fields)) { - var ffi_schema = new FFIArrowSchema(); - IntPtr imported_ptr = ffi_schema.AllocateAsPtr(); + var c_schema = new CArrowSchema(); + IntPtr imported_ptr = c_schema.AllocateAsPtr(); using (Py.GIL()) { @@ -148,7 +148,7 @@ public void ImportType() dataTypeComparer.Visit(imported_type.GetAsType()); // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(imported_ptr); + CArrowSchema.FreePtr(imported_ptr); } } @@ -162,8 +162,8 @@ public void ImportField() foreach ((Field field, dynamic py_field) in schema.Fields.Values.AsEnumerable() .Zip(py_fields)) { - var ffi_schema = new FFIArrowSchema(); - IntPtr imported_ptr = ffi_schema.AllocateAsPtr(); + var c_schema = new CArrowSchema(); + IntPtr imported_ptr = c_schema.AllocateAsPtr(); using (Py.GIL()) { @@ -174,7 +174,7 @@ public void ImportField() FieldComparer.Compare(field, imported_field.GetAsField()); // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(imported_ptr); + CArrowSchema.FreePtr(imported_ptr); } } @@ -185,8 +185,8 @@ public void ImportSchema() Schema schema = GetTestSchema(); dynamic py_schema = GetPythonSchema(); - var ffi_schema = new FFIArrowSchema(); - IntPtr imported_ptr = ffi_schema.AllocateAsPtr(); + var c_schema = new CArrowSchema(); + IntPtr imported_ptr = c_schema.AllocateAsPtr(); using (Py.GIL()) { @@ -197,7 +197,7 @@ public void ImportSchema() SchemaComparer.Compare(schema, imported_field.GetAsSchema()); // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(imported_ptr); + CArrowSchema.FreePtr(imported_ptr); } @@ -213,8 +213,8 @@ public void ExportType() .Zip(py_fields)) { IArrowType datatype = field.DataType; - var exported_type = new FFIArrowSchema(); - FFIArrowSchema.ExportDataType(datatype, out exported_type); + var exported_type = new CArrowSchema(); + CArrowSchema.ExportDataType(datatype, out exported_type); // For Python, we need to provide the pointer IntPtr exported_ptr = exported_type.AllocateAsPtr(); @@ -228,15 +228,15 @@ public void ExportType() } // Python should have called release once `exported_py_type` went out-of-scope. - var ffi_schema = Marshal.PtrToStructure(exported_ptr); - Assert.Null(ffi_schema.release); - Assert.Null(ffi_schema.format); - Assert.Equal(0, ffi_schema.flags); - Assert.Equal(0, ffi_schema.n_children); - Assert.Equal(IntPtr.Zero, ffi_schema.dictionary); + var c_schema = Marshal.PtrToStructure(exported_ptr); + Assert.Null(c_schema.release); + Assert.Null(c_schema.format); + Assert.Equal(0, c_schema.flags); + Assert.Equal(0, c_schema.n_children); + Assert.Equal(IntPtr.Zero, c_schema.dictionary); // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(exported_ptr); + CArrowSchema.FreePtr(exported_ptr); } } @@ -250,8 +250,8 @@ public void ExportField() foreach ((Field field, dynamic py_field) in schema.Fields.Values.AsEnumerable() .Zip(py_fields)) { - var exported_field = new FFIArrowSchema(); - FFIArrowSchema.ExportField(field, out exported_field); + var exported_field = new CArrowSchema(); + CArrowSchema.ExportField(field, out exported_field); // For Python, we need to provide the pointer var exported_ptr = exported_field.AllocateAsPtr(); @@ -264,13 +264,13 @@ public void ExportField() } // Python should have called release once `exported_py_type` went out-of-scope. - var ffi_schema = Marshal.PtrToStructure(exported_ptr); + var ffi_schema = Marshal.PtrToStructure(exported_ptr); Assert.Null(ffi_schema.name); Assert.Null(ffi_schema.release); Assert.Null(ffi_schema.format); // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(exported_ptr); + CArrowSchema.FreePtr(exported_ptr); } } @@ -281,8 +281,8 @@ public void ExportSchema() Schema schema = GetTestSchema(); dynamic py_schema = GetPythonSchema(); - var exported_schema = new FFIArrowSchema(); - FFIArrowSchema.ExportSchema(schema, out exported_schema); + var exported_schema = new CArrowSchema(); + CArrowSchema.ExportSchema(schema, out exported_schema); // For Python, we need to provide the pointer var exported_ptr = exported_schema.AllocateAsPtr(); @@ -295,7 +295,7 @@ public void ExportSchema() } // Since we allocated, we are responsible for freeing the pointer. - FFIArrowSchema.FreePtr(exported_ptr); + CArrowSchema.FreePtr(exported_ptr); } }