You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by we...@apache.org on 2023/07/06 17:07:23 UTC

[arrow] branch main updated: GH-35809: [C#] Improvements to the C Data Interface (#35810)

This is an automated email from the ASF dual-hosted git repository.

westonpace pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 88cb517012 GH-35809: [C#] Improvements to the C Data Interface (#35810)
88cb517012 is described below

commit 88cb5170121489da3f39ee75912c680e92941fd9
Author: Theodore Tsirpanis <te...@tsirpanis.gr>
AuthorDate: Thu Jul 6 20:07:15 2023 +0300

    GH-35809: [C#] Improvements to the C Data Interface (#35810)
    
    ### Rationale for this change
    
    This PR fixes issues identified while reading the code of the `Apache.Arrow.C` namespace.
    
    ### What changes are included in this PR?
    
    See each commit message for more details.
    
    ### Are these changes tested?
    
    Using the existing test suite.
    
    ### Are there any user-facing changes?
    
    No.
    * Closes: #35809
    
    Authored-by: Theodore Tsirpanis <te...@tsirpanis.gr>
    Signed-off-by: Weston Pace <we...@gmail.com>
---
 csharp/src/Apache.Arrow/Apache.Arrow.csproj        |  4 ++
 csharp/src/Apache.Arrow/C/CArrowArray.cs           | 17 +++----
 csharp/src/Apache.Arrow/C/CArrowArrayExporter.cs   | 28 ++++++++----
 csharp/src/Apache.Arrow/C/CArrowArrayImporter.cs   |  2 +-
 csharp/src/Apache.Arrow/C/CArrowArrayStream.cs     | 30 ++++++++----
 .../Apache.Arrow/C/CArrowArrayStreamExporter.cs    | 53 +++++++++++++++++++---
 .../Apache.Arrow/C/CArrowArrayStreamImporter.cs    | 23 ++++++++--
 csharp/src/Apache.Arrow/C/CArrowSchema.cs          | 16 +++----
 csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs  | 11 ++++-
 csharp/src/Apache.Arrow/C/NativeDelegate.cs        |  7 ++-
 .../Apache.Arrow.Tests/CDataInterfaceDataTests.cs  |  2 +-
 .../CDataInterfaceSchemaTests.cs                   |  2 +-
 12 files changed, 136 insertions(+), 59 deletions(-)

diff --git a/csharp/src/Apache.Arrow/Apache.Arrow.csproj b/csharp/src/Apache.Arrow/Apache.Arrow.csproj
index c57c8e48b9..43d60ba001 100644
--- a/csharp/src/Apache.Arrow/Apache.Arrow.csproj
+++ b/csharp/src/Apache.Arrow/Apache.Arrow.csproj
@@ -44,4 +44,8 @@
   <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net5.0'))">
     <Compile Remove="Arrays\HalfFloatArray.cs" />
   </ItemGroup>
+  <ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net5.0'))">
+    <!-- Code targeting .NET 5+ should use [UnmanagedCallersOnly]. -->
+    <Compile Remove="C\NativeDelegate.cs" />
+  </ItemGroup>
 </Project>
diff --git a/csharp/src/Apache.Arrow/C/CArrowArray.cs b/csharp/src/Apache.Arrow/C/CArrowArray.cs
index 4aba95add0..a8a084d1d7 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArray.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArray.cs
@@ -38,7 +38,11 @@ namespace Apache.Arrow.C
         public byte** buffers;
         public CArrowArray** children;
         public CArrowArray* dictionary;
-        public delegate* unmanaged[Stdcall]<CArrowArray*, void> release;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowArray*, void> release;
         public void* private_data;
 
         /// <summary>
@@ -51,16 +55,7 @@ namespace Apache.Arrow.C
         {
             var ptr = (CArrowArray*)Marshal.AllocHGlobal(sizeof(CArrowArray));
 
-            ptr->length = 0;
-            ptr->n_buffers = 0;
-            ptr->offset = 0;
-            ptr->buffers = null;
-            ptr->n_children = 0;
-            ptr->children = null;
-            ptr->dictionary = null;
-            ptr->null_count = 0;
-            ptr->release = null;
-            ptr->private_data = null;
+            *ptr = default;
 
             return ptr;
         }
diff --git a/csharp/src/Apache.Arrow/C/CArrowArrayExporter.cs b/csharp/src/Apache.Arrow/C/CArrowArrayExporter.cs
index aafb3b8987..5a793c177e 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArrayExporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArrayExporter.cs
@@ -15,6 +15,7 @@
 
 
 using System;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using Apache.Arrow.Memory;
 
@@ -22,9 +23,13 @@ namespace Apache.Arrow.C
 {
     public static class CArrowArrayExporter
     {
+#if NET5_0_OR_GREATER
+        private static unsafe delegate* unmanaged<CArrowArray*, void> ReleaseArrayPtr => &ReleaseArray;
+#else
         private unsafe delegate void ReleaseArrowArray(CArrowArray* cArray);
         private static unsafe readonly NativeDelegate<ReleaseArrowArray> s_releaseArray = new NativeDelegate<ReleaseArrowArray>(ReleaseArray);
-
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowArray*, void> ReleaseArrayPtr => (delegate* unmanaged[Cdecl]<CArrowArray*, void>)s_releaseArray.Pointer;
+#endif
         /// <summary>
         /// Export an <see cref="IArrowArray"/> to a <see cref="CArrowArray"/>. Whether or not the
         /// export succeeds, the original array becomes invalid. Clone an array to continue using it
@@ -54,7 +59,7 @@ namespace Apache.Arrow.C
             try
             {
                 ConvertArray(allocationOwner, array.Data, cArray);
-                cArray->release = (delegate* unmanaged[Stdcall]<CArrowArray*, void>)(IntPtr)s_releaseArray.Pointer;
+                cArray->release = ReleaseArrayPtr;
                 cArray->private_data = FromDisposable(allocationOwner);
                 allocationOwner = null;
             }
@@ -97,7 +102,7 @@ namespace Apache.Arrow.C
             try
             {
                 ConvertRecordBatch(allocationOwner, batch, cArray);
-                cArray->release = (delegate* unmanaged[Stdcall]<CArrowArray*, void>)s_releaseArray.Pointer;
+                cArray->release = ReleaseArrayPtr;
                 cArray->private_data = FromDisposable(allocationOwner);
                 allocationOwner = null;
             }
@@ -112,7 +117,7 @@ namespace Apache.Arrow.C
             cArray->length = array.Length;
             cArray->offset = array.Offset;
             cArray->null_count = array.NullCount;
-            cArray->release = (delegate* unmanaged[Stdcall]<CArrowArray*, void>)s_releaseArray.Pointer;
+            cArray->release = ReleaseArrayPtr;
             cArray->private_data = null;
 
             cArray->n_buffers = array.Buffers?.Length ?? 0;
@@ -157,7 +162,7 @@ namespace Apache.Arrow.C
             cArray->length = batch.Length;
             cArray->offset = 0;
             cArray->null_count = 0;
-            cArray->release = (delegate* unmanaged[Stdcall]<CArrowArray*, void>)s_releaseArray.Pointer;
+            cArray->release = ReleaseArrayPtr;
             cArray->private_data = null;
 
             cArray->n_buffers = 1;
@@ -180,13 +185,12 @@ namespace Apache.Arrow.C
             cArray->dictionary = null;
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private unsafe static void ReleaseArray(CArrowArray* cArray)
         {
-            if (cArray->private_data != null)
-            {
-                Dispose(&cArray->private_data);
-            }
-            cArray->private_data = null;
+            Dispose(&cArray->private_data);
             cArray->release = null;
         }
 
@@ -199,6 +203,10 @@ namespace Apache.Arrow.C
         private unsafe static void Dispose(void** ptr)
         {
             GCHandle gch = GCHandle.FromIntPtr((IntPtr)(*ptr));
+            if (!gch.IsAllocated)
+            {
+                return;
+            }
             ((IDisposable)gch.Target).Dispose();
             gch.Free();
             *ptr = null;
diff --git a/csharp/src/Apache.Arrow/C/CArrowArrayImporter.cs b/csharp/src/Apache.Arrow/C/CArrowArrayImporter.cs
index 4375b382d9..e1314e5a62 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArrayImporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArrayImporter.cs
@@ -164,7 +164,7 @@ namespace Apache.Arrow.C
                     case ArrowTypeId.Map:
                         break;
                     case ArrowTypeId.Null:
-                        buffers = new ArrowBuffer[0];
+                        buffers = System.Array.Empty<ArrowBuffer>();
                         break;
                     case ArrowTypeId.Dictionary:
                         DictionaryType dictionaryType = (DictionaryType)type;
diff --git a/csharp/src/Apache.Arrow/C/CArrowArrayStream.cs b/csharp/src/Apache.Arrow/C/CArrowArrayStream.cs
index bf1fcb39ef..a900a6895a 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArrayStream.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArrayStream.cs
@@ -35,7 +35,11 @@ namespace Apache.Arrow.C
         ///
         /// Return value: 0 if successful, an `errno`-compatible error code otherwise.
         ///</summary>
-        public delegate* unmanaged[Stdcall]<CArrowArrayStream*, CArrowSchema*, int> get_schema;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowArrayStream*, CArrowSchema*, int> get_schema;
 
         /// <summary>
         /// Callback to get the next array. If no error and the array is released, the stream has ended.
@@ -43,7 +47,11 @@ namespace Apache.Arrow.C
         /// 
         /// Return value: 0 if successful, an `errno`-compatible error code otherwise.
         /// </summary>
-        public delegate* unmanaged[Stdcall]<CArrowArrayStream*, CArrowArray*, int> get_next;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowArrayStream*, CArrowArray*, int> get_next;
 
         /// <summary>
         /// Callback to get optional detailed error information. This must only
@@ -54,13 +62,21 @@ namespace Apache.Arrow.C
         /// Return value: pointer to a null-terminated character array describing the last
         /// error, or NULL if no description is available.
         ///</summary>
-        public delegate* unmanaged[Stdcall]<CArrowArrayStream*, byte*> get_last_error;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowArrayStream*, byte*> get_last_error;
 
         /// <summary>
         /// Release callback: release the stream's own resources. Note that arrays returned by
         /// get_next must be individually released.
         /// </summary>
-        public delegate* unmanaged[Stdcall]<CArrowArrayStream*, void> release;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowArrayStream*, void> release;
 
         public void* private_data;
 
@@ -74,11 +90,7 @@ namespace Apache.Arrow.C
         {
             var ptr = (CArrowArrayStream*)Marshal.AllocHGlobal(sizeof(CArrowArrayStream));
 
-            ptr->get_schema = null;
-            ptr->get_next = null;
-            ptr->get_last_error = null;
-            ptr->release = null;
-            ptr->private_data = null;
+            *ptr = default;
 
             return ptr;
         }
diff --git a/csharp/src/Apache.Arrow/C/CArrowArrayStreamExporter.cs b/csharp/src/Apache.Arrow/C/CArrowArrayStreamExporter.cs
index 911dd7a153..56e0468f94 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArrayStreamExporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArrayStreamExporter.cs
@@ -15,6 +15,7 @@
 
 
 using System;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using Apache.Arrow.Ipc;
 
@@ -22,14 +23,29 @@ namespace Apache.Arrow.C
 {
     public static class CArrowArrayStreamExporter
     {
+#if NET5_0_OR_GREATER
+        private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowSchema*, int> GetSchemaPtr => &GetSchema;
+        private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowArray*, int> GetNextPtr => &GetNext;
+        private static unsafe delegate* unmanaged<CArrowArrayStream*, byte*> GetLastErrorPtr => &GetLastError;
+        private static unsafe delegate* unmanaged<CArrowArrayStream*, void> ReleasePtr => &Release;
+#else
         private unsafe delegate int GetSchemaArrayStream(CArrowArrayStream* cArrayStream, CArrowSchema* cSchema);
         private static unsafe NativeDelegate<GetSchemaArrayStream> s_getSchemaArrayStream = new NativeDelegate<GetSchemaArrayStream>(GetSchema);
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowArrayStream*, CArrowSchema*, int> GetSchemaPtr =>
+            (delegate* unmanaged[Cdecl]<CArrowArrayStream*, CArrowSchema*, int>)s_getSchemaArrayStream.Pointer;
         private unsafe delegate int GetNextArrayStream(CArrowArrayStream* cArrayStream, CArrowArray* cArray);
         private static unsafe NativeDelegate<GetNextArrayStream> s_getNextArrayStream = new NativeDelegate<GetNextArrayStream>(GetNext);
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowArrayStream*, CArrowArray*, int> GetNextPtr =>
+            (delegate* unmanaged[Cdecl]<CArrowArrayStream*, CArrowArray*, int>)s_getNextArrayStream.Pointer;
         private unsafe delegate byte* GetLastErrorArrayStream(CArrowArrayStream* cArrayStream);
         private static unsafe NativeDelegate<GetLastErrorArrayStream> s_getLastErrorArrayStream = new NativeDelegate<GetLastErrorArrayStream>(GetLastError);
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowArrayStream*, byte*> GetLastErrorPtr =>
+            (delegate* unmanaged[Cdecl]<CArrowArrayStream*, byte*>)s_getLastErrorArrayStream.Pointer;
         private unsafe delegate void ReleaseArrayStream(CArrowArrayStream* cArrayStream);
         private static unsafe NativeDelegate<ReleaseArrayStream> s_releaseArrayStream = new NativeDelegate<ReleaseArrayStream>(Release);
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowArrayStream*, void> ReleasePtr =>
+            (delegate* unmanaged[Cdecl]<CArrowArrayStream*, void>)s_releaseArrayStream.Pointer;
+#endif
 
         /// <summary>
         /// Export an <see cref="IArrowArrayStream"/> to a <see cref="CArrowArrayStream"/>.
@@ -55,12 +71,15 @@ namespace Apache.Arrow.C
             }
 
             cArrayStream->private_data = ExportedArrayStream.Export(arrayStream);
-            cArrayStream->get_schema = (delegate* unmanaged[Stdcall]<CArrowArrayStream*, CArrowSchema*, int>)s_getSchemaArrayStream.Pointer;
-            cArrayStream->get_next = (delegate* unmanaged[Stdcall]<CArrowArrayStream*, CArrowArray*, int>)s_getNextArrayStream.Pointer;
-            cArrayStream->get_last_error = (delegate* unmanaged[Stdcall]<CArrowArrayStream*, byte*>)s_getLastErrorArrayStream.Pointer;
-            cArrayStream->release = (delegate* unmanaged[Stdcall]<CArrowArrayStream*, void>)s_releaseArrayStream.Pointer;
+            cArrayStream->get_schema = GetSchemaPtr;
+            cArrayStream->get_next = GetNextPtr;
+            cArrayStream->get_last_error = GetLastErrorPtr;
+            cArrayStream->release = ReleasePtr;
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private unsafe static int GetSchema(CArrowArrayStream* cArrayStream, CArrowSchema* cSchema)
         {
             ExportedArrayStream arrayStream = null;
@@ -76,6 +95,9 @@ namespace Apache.Arrow.C
             }
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private unsafe static int GetNext(CArrowArrayStream* cArrayStream, CArrowArray* cArray)
         {
             ExportedArrayStream arrayStream = null;
@@ -96,6 +118,9 @@ namespace Apache.Arrow.C
             }
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private unsafe static byte* GetLastError(CArrowArrayStream* cArrayStream)
         {
             try
@@ -109,10 +134,12 @@ namespace Apache.Arrow.C
             }
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private unsafe static void Release(CArrowArrayStream* cArrayStream)
         {
-            ExportedArrayStream arrayStream = ExportedArrayStream.FromPointer(cArrayStream->private_data);
-            arrayStream.Dispose();
+            ExportedArrayStream.Free(&cArrayStream->private_data);
             cArrayStream->release = null;
         }
 
@@ -136,6 +163,18 @@ namespace Apache.Arrow.C
                 return (void*)GCHandle.ToIntPtr(gch);
             }
 
+            public static void Free(void** ptr)
+            {
+                GCHandle gch = GCHandle.FromIntPtr((IntPtr)ptr);
+                if (!gch.IsAllocated)
+                {
+                    return;
+                }
+                ((ExportedArrayStream)gch.Target).Dispose();
+                gch.Free();
+                *ptr = null;
+            }
+
             public static ExportedArrayStream FromPointer(void* ptr)
             {
                 GCHandle gch = GCHandle.FromIntPtr((IntPtr)ptr);
@@ -166,7 +205,7 @@ namespace Apache.Arrow.C
             {
                 if (LastError != null)
                 {
-                    Marshal.FreeCoTaskMem((IntPtr)LastError);
+                    Marshal.FreeHGlobal((IntPtr)LastError);
                     LastError = null;
                 }
             }
diff --git a/csharp/src/Apache.Arrow/C/CArrowArrayStreamImporter.cs b/csharp/src/Apache.Arrow/C/CArrowArrayStreamImporter.cs
index b2558f07a4..7e70632bf8 100644
--- a/csharp/src/Apache.Arrow/C/CArrowArrayStreamImporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowArrayStreamImporter.cs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation (ASF) under one
+// Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
 // distributed with this work for additional information
 // regarding copyright ownership.  The ASF licenses this file
@@ -55,6 +55,16 @@ namespace Apache.Arrow.C
             private readonly Schema _schema;
             private bool _disposed;
 
+            internal static string GetLastError(CArrowArrayStream* arrayStream, int errno)
+            {
+                byte* error = arrayStream->get_last_error(arrayStream);
+                if (error == null)
+                {
+                    return $"Array stream operation failed with no message. Error code: {errno}";
+                }
+                return StringUtil.PtrToStringUtf8(error);
+            }
+
             public ImportedArrowArrayStream(CArrowArrayStream* cArrayStream)
             {
                 if (cArrayStream == null)
@@ -70,7 +80,7 @@ namespace Apache.Arrow.C
                 int errno = cArrayStream->get_schema(cArrayStream, &cSchema);
                 if (errno != 0)
                 {
-                    throw new Exception($"Unexpected error recieved from external stream. Errno: {errno}");
+                    throw new Exception(GetLastError(cArrayStream, errno));
                 }
                 _schema = CArrowSchemaImporter.ImportSchema(&cSchema);
 
@@ -92,6 +102,11 @@ namespace Apache.Arrow.C
                     throw new ObjectDisposedException(typeof(ImportedArrowArrayStream).Name);
                 }
 
+                if (cancellationToken.IsCancellationRequested)
+                {
+                    return new(Task.FromCanceled<RecordBatch>(cancellationToken));
+                }
+
                 RecordBatch result = null;
                 CArrowArray cArray = new CArrowArray();
                 fixed (CArrowArrayStream* cArrayStream = &_cArrayStream)
@@ -99,7 +114,7 @@ namespace Apache.Arrow.C
                     int errno = cArrayStream->get_next(cArrayStream, &cArray);
                     if (errno != 0)
                     {
-                        throw new Exception($"Unexpected error recieved from external stream. Errno: {errno}");
+                        return new(Task.FromException<RecordBatch>(new Exception(GetLastError(cArrayStream, errno))));
                     }
                     if (cArray.release != null)
                     {
@@ -115,7 +130,7 @@ namespace Apache.Arrow.C
                 if (!_disposed && _cArrayStream.release != null)
                 {
                     _disposed = true;
-                    fixed (CArrowArrayStream * cArrayStream = &_cArrayStream)
+                    fixed (CArrowArrayStream* cArrayStream = &_cArrayStream)
                     {
                         cArrayStream->release(cArrayStream);
                     }
diff --git a/csharp/src/Apache.Arrow/C/CArrowSchema.cs b/csharp/src/Apache.Arrow/C/CArrowSchema.cs
index af01247800..64761dbd0d 100644
--- a/csharp/src/Apache.Arrow/C/CArrowSchema.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowSchema.cs
@@ -39,7 +39,11 @@ namespace Apache.Arrow.C
         public long n_children;
         public CArrowSchema** children;
         public CArrowSchema* dictionary;
-        public delegate* unmanaged[Stdcall]<CArrowSchema*, void> release;
+        internal delegate* unmanaged
+#if !NET5_0_OR_GREATER
+            [Cdecl]
+#endif
+            <CArrowSchema*, void> release;
         public void* private_data;
 
         /// <summary>
@@ -52,15 +56,7 @@ namespace Apache.Arrow.C
         {
             var ptr = (CArrowSchema*)Marshal.AllocHGlobal(sizeof(CArrowSchema));
 
-            ptr->format = null;
-            ptr->name = null;
-            ptr->metadata = null;
-            ptr->flags = 0;
-            ptr->n_children = 0;
-            ptr->children = null;
-            ptr->dictionary = null;
-            ptr->release = null;
-            ptr->private_data = null;
+            *ptr = default;
 
             return ptr;
         }
diff --git a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
index fe47c9f7f0..9053e80664 100644
--- a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
@@ -18,6 +18,7 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Text;
 using Apache.Arrow.Types;
@@ -26,8 +27,13 @@ namespace Apache.Arrow.C
 {
     public static class CArrowSchemaExporter
     {
+#if NET5_0_OR_GREATER
+        private static unsafe delegate* unmanaged<CArrowSchema*, void> ReleaseSchemaPtr => &ReleaseCArrowSchema;
+#else
         private unsafe delegate void ReleaseArrowSchema(CArrowSchema* cArray);
         private static unsafe readonly NativeDelegate<ReleaseArrowSchema> s_releaseSchema = new NativeDelegate<ReleaseArrowSchema>(ReleaseCArrowSchema);
+        private static unsafe delegate* unmanaged[Cdecl]<CArrowSchema*, void> ReleaseSchemaPtr => (delegate* unmanaged[Cdecl]<CArrowSchema*, void>)s_releaseSchema.Pointer;
+#endif
 
         /// <summary>
         /// Export a type to a <see cref="CArrowSchema"/>.
@@ -63,7 +69,7 @@ namespace Apache.Arrow.C
 
             schema->dictionary = ConstructDictionary(datatype);
 
-            schema->release = (delegate* unmanaged[Stdcall]<CArrowSchema*, void>)s_releaseSchema.Pointer;
+            schema->release = ReleaseSchemaPtr;
 
             schema->private_data = null;
         }
@@ -285,6 +291,9 @@ namespace Apache.Arrow.C
             ptr += length;
         }
 
+#if NET5_0_OR_GREATER
+        [UnmanagedCallersOnly]
+#endif
         private static unsafe void ReleaseCArrowSchema(CArrowSchema* schema)
         {
             if (schema == null) return;
diff --git a/csharp/src/Apache.Arrow/C/NativeDelegate.cs b/csharp/src/Apache.Arrow/C/NativeDelegate.cs
index 48b37fbfe5..ebc6330c9b 100644
--- a/csharp/src/Apache.Arrow/C/NativeDelegate.cs
+++ b/csharp/src/Apache.Arrow/C/NativeDelegate.cs
@@ -20,17 +20,16 @@ using System.Runtime.InteropServices;
 
 namespace Apache.Arrow.C
 {
-    internal readonly struct NativeDelegate<T>
+    internal readonly struct NativeDelegate<T> where T : Delegate
     {
         private readonly T _managedDelegate; // For lifetime management
-        private readonly IntPtr _nativePointer;
 
         public NativeDelegate(T managedDelegate)
         {
             _managedDelegate = managedDelegate;
-            _nativePointer = Marshal.GetFunctionPointerForDelegate<T>(managedDelegate);
+            Pointer = Marshal.GetFunctionPointerForDelegate(managedDelegate);
         }
 
-        public IntPtr Pointer { get { return _nativePointer; } }
+        public IntPtr Pointer { get; }
     }
 }
diff --git a/csharp/test/Apache.Arrow.Tests/CDataInterfaceDataTests.cs b/csharp/test/Apache.Arrow.Tests/CDataInterfaceDataTests.cs
index 89a346f9f7..a430e140cf 100644
--- a/csharp/test/Apache.Arrow.Tests/CDataInterfaceDataTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/CDataInterfaceDataTests.cs
@@ -77,7 +77,7 @@ namespace Apache.Arrow.Tests
                 wasCalled = true;
                 cArray->release = null;
             };
-            cArray->release = (delegate* unmanaged[Stdcall]<CArrowArray*, void>)Marshal.GetFunctionPointerForDelegate(
+            cArray->release = (delegate* unmanaged<CArrowArray*, void>)Marshal.GetFunctionPointerForDelegate(
                 releaseCallback);
 
             Assert.Throws<InvalidOperationException>(() =>
diff --git a/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs b/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs
index a75d777ad3..dfd6f9912c 100644
--- a/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/CDataInterfaceSchemaTests.cs
@@ -105,7 +105,7 @@ namespace Apache.Arrow.Tests
                 wasCalled = true;
                 cSchema->release = null;
             };
-            cSchema->release = (delegate* unmanaged[Stdcall]<CArrowSchema*, void>)Marshal.GetFunctionPointerForDelegate(
+            cSchema->release = (delegate* unmanaged<CArrowSchema*, void>)Marshal.GetFunctionPointerForDelegate(
                 releaseCallback);
 
             Assert.Throws<NullReferenceException>(() =>