RSCG – NativeObjects
name | NativeObjects |
nuget | https://www.nuget.org/packages/NativeObjects/ |
link | https://github.com/kevingosse/NativeObjects |
author | Kevin Gosse |
Object to IntPtr and back
This is how you can use NativeObjects .
The code that you start with is
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <ItemGroup> <PackageReference Include="NativeObjects" Version="1.3.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> <PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath> </PropertyGroup> </Project>
The code that you will use is
using NativeObjectsDemo; Person p = new Person(); p.DateOfBirth= new DateTime(1970,4,16); using (var nativ = NativeObjects.IPerson.Wrap(p)) { SomeNativeCode((IntPtr)nativ.Object); } static void SomeNativeCode(IntPtr nativePerson) { var p = NativeObjects.IPerson.Wrap(nativePerson); Console.WriteLine($"Age: {p.CalculateAge()}"); }
namespace NativeObjectsDemo; [NativeObject] public interface IPerson { public int CalculateAge(); } class Person : IPerson { public DateTime DateOfBirth { get; set; } public int CalculateAge() { return (int)DateTime.Now.Subtract(DateOfBirth).TotalDays / 365; } }
The code that is generated is
using System; [AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] internal class NativeObjectAttribute : Attribute { } [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] internal class NativeObjectsNamespaceAttribute : Attribute { public NativeObjectsNamespaceAttribute(string name) { } }
namespace NativeObjects { using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; public unsafe class IPerson : IDisposable { private IPerson(NativeObjectsDemo.IPerson implementation) { const int delegateCount = 1; var vtable = (IntPtr*)NativeMemory.Alloc((nuint)delegateCount, (nuint)IntPtr.Size); *(vtable + 0) = (IntPtr)(delegate* unmanaged<IntPtr*, int>)&Exports.CalculateAge; var obj = (IntPtr*)NativeMemory.Alloc((nuint)2, (nuint)IntPtr.Size); *obj = (IntPtr)vtable; var handle = GCHandle.Alloc(implementation); *(obj + 1) = GCHandle.ToIntPtr(handle); Object = (IntPtr)obj; } public IntPtr Object { get; private set; } public static IPerson Wrap(NativeObjectsDemo.IPerson implementation) => new(implementation); public static IPersonInvoker Wrap(IntPtr ptr) => new(ptr); public static implicit operator IntPtr(IPerson stub) => stub.Object; public void Dispose() { if (Object != IntPtr.Zero) { var target = (void**)Object; NativeMemory.Free(*target); NativeMemory.Free(target); Object = IntPtr.Zero; } } private static class Exports { [UnmanagedCallersOnly] public static int CalculateAge(IntPtr* self) { var handle = GCHandle.FromIntPtr(*(self + 1)); var obj = (NativeObjectsDemo.IPerson)handle.Target; var result = obj.CalculateAge(); return result; } } } public unsafe struct IPersonInvoker { private readonly IntPtr _implementation; public IPersonInvoker(IntPtr implementation) { _implementation = implementation; } public static implicit operator IntPtr(IPersonInvoker invoker) => invoker._implementation; private nint* VTable => (nint*)*(nint*)_implementation; public int CalculateAge() { var __func__ = (delegate* unmanaged[Stdcall]<IntPtr, int>)*(VTable + 0); return __func__(_implementation); } } }
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/NativeObjects