mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-07-31 07:48:52 -04:00
245 lines
8.1 KiB
C#
245 lines
8.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Facepunch.InteropGen;
|
|
|
|
/// <summary>
|
|
/// Emits the managed (C#) side: the typed wrappers (imports), the [UnmanagedCallersOnly] thunks
|
|
/// (exports), and the NativeInterop bootstrap that wires the two sides' function pointers together.
|
|
/// </summary>
|
|
internal partial class ManagedWriter : BaseWriter
|
|
{
|
|
public ManagedWriter( Definition definitions, string targetName ) : base( definitions, targetName )
|
|
{
|
|
}
|
|
|
|
public override void Generate()
|
|
{
|
|
Header();
|
|
|
|
Imports();
|
|
PointerStructs();
|
|
|
|
StartBlock( $"namespace {definitions.ManagedNamespace}" );
|
|
{
|
|
Exports();
|
|
NativeInterop();
|
|
}
|
|
EndBlock();
|
|
}
|
|
|
|
private void Header()
|
|
{
|
|
WriteLine( "// <auto-generated>" );
|
|
WriteLine( "// This file was generated by a tool." );
|
|
WriteLine( "// Changes to this file may cause incorrect behavior and will be lost if" );
|
|
WriteLine( "// the code is regenerated." );
|
|
WriteLine( "// </auto-generated>" );
|
|
|
|
WriteLine( "" );
|
|
|
|
WriteLine( "using System;" );
|
|
WriteLine( "using System.Collections.Generic;" );
|
|
WriteLine( "using System.Runtime.CompilerServices;" );
|
|
WriteLine( "using System.Runtime.InteropServices;" );
|
|
|
|
WriteLine( "" );
|
|
}
|
|
|
|
/// <summary>
|
|
/// The NativeInterop bootstrap class: loads the native dll, calls its igen_* initializer to swap
|
|
/// function pointer tables, and distributes the received native pointers into each wrapper's table.
|
|
/// </summary>
|
|
private void NativeInterop()
|
|
{
|
|
IEnumerable<Function> imports = definitions.ManagedClasses.Where( x => !Skip.ShouldSkip( x ) ).SelectMany( x => x.Functions );
|
|
|
|
StartBlock( "internal unsafe static partial class NativeInterop" );
|
|
{
|
|
WriteLine( "static IntPtr _nativeLibraryHandle;" );
|
|
WriteLine( "static bool _initialized;" );
|
|
WriteLine();
|
|
|
|
ErrorFunction();
|
|
|
|
WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
|
|
WriteLine( "delegate void NetCoreImportDelegate( int hash, void* imports, void* exports, int* structSizes );" );
|
|
WriteLine();
|
|
|
|
StartBlock( "internal static void Initialize()" );
|
|
{
|
|
WriteLine( "if ( _initialized ) return;" );
|
|
WriteLine();
|
|
|
|
LoadNativeLibrary();
|
|
ManagedFunctionTable( imports );
|
|
|
|
{
|
|
WriteLine();
|
|
WriteLine( $"var nativeFunctions = new IntPtr[{NativeExportSlots().Count()}];" );
|
|
}
|
|
|
|
StructSizeTable();
|
|
|
|
WriteLine();
|
|
WriteLine();
|
|
WriteLine( "fixed ( void* m = managedFunctions )" );
|
|
WriteLine( "fixed ( void* n = nativeFunctions )" );
|
|
WriteLine( "fixed ( int* s = structSizes )" );
|
|
StartBlock( null );
|
|
WriteLine( $"nativeInit( {definitions.Hash}, m, n, s );" );
|
|
EndBlock();
|
|
WriteLine();
|
|
|
|
BindNativeFunctions();
|
|
|
|
WriteLine( "_initialized = true;" );
|
|
}
|
|
EndBlock();
|
|
|
|
StartBlock( "internal static void Free()" );
|
|
{
|
|
WriteLine( "if ( _nativeLibraryHandle == IntPtr.Zero ) return;" );
|
|
WriteLine( "NativeLibrary.Free( _nativeLibraryHandle );" );
|
|
WriteLine( "_nativeLibraryHandle = IntPtr.Zero;" );
|
|
WriteLine( "_initialized = false;" );
|
|
}
|
|
EndBlock();
|
|
}
|
|
EndBlock();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the native dll and resolve its igen_* initializer into a callable delegate.
|
|
/// </summary>
|
|
private void LoadNativeLibrary()
|
|
{
|
|
WriteLine( $"var _nativeDllName = Sandbox.Interop.GetNativeLibraryName( \"{definitions.NativeDll}\" );" );
|
|
WriteLine( $"if ( !NativeLibrary.TryLoad( System.IO.Path.Combine( NetCore.NativeDllPath, _nativeDllName ), out var nativeDll ) )" );
|
|
WriteLine( $" Sandbox.Interop.NativeAssemblyLoadFailed( _nativeDllName );" );
|
|
WriteLine( "_nativeLibraryHandle = nativeDll;" );
|
|
|
|
WriteLine();
|
|
WriteLine( $"IntPtr nativeInitPtr = NativeLibrary.GetExport( nativeDll, \"igen_{definitions.Ident}\" );" );
|
|
WriteLine( $"if ( nativeInitPtr == IntPtr.Zero ) throw new System.Exception( $\"Couldn't find igen_{definitions.Ident} in {{_nativeDllName}}\" );" );
|
|
|
|
WriteLine();
|
|
WriteLine( $"var nativeInit = Marshal.GetDelegateForFunctionPointer<NetCoreImportDelegate>( nativeInitPtr );" );
|
|
WriteLine( $"if ( nativeInit == null ) throw new System.Exception( $\"Couldn't load from {{_nativeDllName}}\" );" );
|
|
}
|
|
|
|
/// <summary>
|
|
/// The managedFunctions array: a pointer to the [UnmanagedCallersOnly] thunk for every function
|
|
/// managed exports to native, in the order native reads them back.
|
|
/// </summary>
|
|
private void ManagedFunctionTable( IEnumerable<Function> imports )
|
|
{
|
|
WriteLine();
|
|
WriteLine( $"var managedFunctions = new IntPtr[{imports.Count()}]" );
|
|
StartBlock( null );
|
|
foreach ( Function f in imports )
|
|
{
|
|
Class c = f.Class;
|
|
IEnumerable<string> managedArgs = c.SelfArg( false, f.Static ).Concat( f.Parameters ).Concat( new[] { f.Return } ).Where( x => x.IsRealArgument ).Select( x => $"{x.DelegateType( Side.Managed, Dir.Incoming )}" );
|
|
string managedArgss = $"{string.Join( ", ", managedArgs )}";
|
|
|
|
WriteLine( $"(IntPtr) (delegate* unmanaged<{managedArgss}>) &Exports.{f.MangledName}," );
|
|
}
|
|
EndBlock( ";" );
|
|
}
|
|
|
|
/// <summary>
|
|
/// The structSizes array, so native can verify both sides agree on every struct's size.
|
|
/// </summary>
|
|
private void StructSizeTable()
|
|
{
|
|
WriteLine();
|
|
WriteLine( $"var structSizes = new int[]" );
|
|
StartBlock( null );
|
|
|
|
foreach ( Struct s in definitions.Structs )
|
|
{
|
|
if ( Skip.ShouldSkip( s ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string size_of = $"sizeof( {s.ManagedNameWithNamespace} )";
|
|
|
|
WriteLine( $"{size_of}," );
|
|
}
|
|
EndBlock( ";" );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read each received native pointer out of the nativeFunctions array into the matching wrapper's
|
|
/// function pointer field. Slot order comes from <see cref="NativeExportTable"/> on both sides.
|
|
/// </summary>
|
|
private void BindNativeFunctions()
|
|
{
|
|
WriteLine( $"var onError = Marshal.GetDelegateForFunctionPointer<_ErrorFunction>( nativeFunctions[0] );" );
|
|
WriteLine();
|
|
|
|
StartBlock( "try" );
|
|
|
|
int index = 0;
|
|
foreach ( NativeSlot slot in NativeExportSlots() )
|
|
{
|
|
// Slot 0 (the error function) was already read into onError above.
|
|
if ( slot.Kind == NativeSlotKind.Error )
|
|
{
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
string namespc = $"{slot.Class.ManagedNamespace}.{slot.Class.ManagedName}".Trim( '.' );
|
|
|
|
switch ( slot.Kind )
|
|
{
|
|
case NativeSlotKind.CastFromTo:
|
|
WriteLine( $"{namespc}.{InternalNative}.From_{slot.BaseClass.ManagedName}_To_{slot.Class.ManagedName} = (delegate* unmanaged[SuppressGCTransition]< IntPtr, IntPtr >) nativeFunctions[{index}];" );
|
|
break;
|
|
|
|
case NativeSlotKind.CastToFrom:
|
|
WriteLine( $"{namespc}.{InternalNative}.To_{slot.BaseClass.ManagedName}_From_{slot.Class.ManagedName} = (delegate* unmanaged[SuppressGCTransition]< IntPtr, IntPtr >) nativeFunctions[{index}];" );
|
|
break;
|
|
|
|
case NativeSlotKind.Function:
|
|
{
|
|
Function f = slot.Function;
|
|
IEnumerable<string> managedArgs = slot.Class.SelfArg( false, f.Static ).Concat( f.Parameters ).Where( x => x.IsRealArgument ).Select( x => $"{x.DelegateType( Side.Managed, Dir.Outgoing )}" ).Concat( new[] { f.Return.DelegateType( Side.Managed, Dir.Incoming ) } );
|
|
string managedArgss = $"{string.Join( ", ", managedArgs )}";
|
|
string nogc = f.IsNoGC ? "[SuppressGCTransition]" : "";
|
|
|
|
WriteLine( $"{namespc}.{InternalNative}.{f.MangledName} = (delegate* unmanaged{nogc}< {managedArgss} >) nativeFunctions[{index}];" );
|
|
break;
|
|
}
|
|
|
|
case NativeSlotKind.VariableGet:
|
|
WriteLine( $"{namespc}.{InternalNative}.Get__{slot.Variable.MangledName} = (delegate* unmanaged[SuppressGCTransition]<IntPtr, {slot.Variable.Return.DelegateType( Side.Managed, Dir.Incoming )}>)( nativeFunctions[{index}] );" );
|
|
break;
|
|
|
|
case NativeSlotKind.VariableSet:
|
|
WriteLine( $"{namespc}.{InternalNative}.Set__{slot.Variable.MangledName} = (delegate* unmanaged[SuppressGCTransition]<IntPtr, {slot.Variable.Return.DelegateType( Side.Managed, Dir.Outgoing )}, void>)( nativeFunctions[{index}] );" );
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
EndBlock();
|
|
StartBlock( "catch ( System.Exception ___e )" );
|
|
{
|
|
WriteLine( "onError( $\"{___e.Message}\\n\\n{___e.StackTrace}\" );" );
|
|
}
|
|
EndBlock();
|
|
}
|
|
|
|
private void ErrorFunction()
|
|
{
|
|
WriteLine( "[UnmanagedFunctionPointer( CallingConvention.Cdecl )]" );
|
|
WriteLine( "internal delegate void _ErrorFunction( string message );" );
|
|
WriteLine();
|
|
}
|
|
}
|