Skip to content

KevinGliewe/embedded_dotnet_runtime_examples

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Embedded .NET 6 Runtime

Build and Test

This repo contains a project with examples for using a embedded .NET 6 runtime in a C++ application using dotnet_runtime.

Build and Run

Windows

Requirements

  • Visual Studio 2019
  • .NET 6 SDK

Steps

  1. Clone this repository recursively using git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git
  2. Run build_run.bat

What happens during the build?

  • Imports VsDevCmd.bat
  • Installs dotnet tool runtimedl
  • Downloads the propper .NET 6 runtime for your system using runtimedl
  • Builds Lib and Host using MSBuild
  • Runs Host

Linux & MacOS

Requirements

  • CMake 3.16 or newer
  • .NET 6 SDK
  • C++ compiler

Steps

  1. Clone this repository recursively using git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git
  2. Run sh build_run.sh

What happens during the build?

  • Installs dotnet tool runtimedl
  • Downloads the propper .NET 6 runtime for your system using runtimedl
  • Builds Lib using .NET 6 SDK
  • Creates makefile using CMake
  • Builds Host using make
  • Runs Host

Examples

Load and initialize .NET runtime

// Init runtime and lib

auto runtime = dotnet_runtime::Runtime(hostfxr_path, libRuntimeconfig_path);

auto lib = dotnet_runtime::Library(&runtime, libDll_path, STR("Lib"));

snippet source | anchor


Managed Entrypoint

Entrypoints can only use blittable types

Managed component-entrypoint

Native

struct args
{
	int number1;
	int number2;
};

snippet source | anchor

auto fpTest_ComponentEntryPoint = a_lib.GetComponentEntrypoint(
	STR("LibNamespace.Test_ManagedEntryPoint"),
	STR("Test_ComponentEntryPoint")
);

bool success = fpTest_ComponentEntryPoint(&_args, sizeof(_args)) == 3;
LogTest(success, L"Test_ComponentEntryPoint");

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Args
{
    public int Number1;
    public int Number2;
}

snippet source | anchor

public static int Test_ComponentEntryPoint(IntPtr arg, int argLength)
{
    if (argLength < Marshal.SizeOf(typeof(Args)))
    {
        return 1;
    }

    Args args = Marshal.PtrToStructure<Args>(arg);

    return args.Number1 + args.Number2;
}

snippet source | anchor

Managed custom-entrypoint

Entrypoints can only use blittable types

Native

struct args
{
	int number1;
	int number2;
};

snippet source | anchor

typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args);

auto fpTest_CustomEntryPoint = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_ManagedEntryPoint"),
	STR("Test_CustomEntryPoint")
);

bool success = fpTest_CustomEntryPoint(_args) == 3;
LogTest(success, L"Test_CustomEntryPoint");

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Args
{
    public int Number1;
    public int Number2;
}

snippet source | anchor

[UnmanagedCallersOnly]
public static int Test_CustomEntryPoint(Args args)
{
    return args.Number1 + args.Number2;
}

snippet source | anchor


Managed Strings

Return managed ASCII string

Native

auto fpTest_ManagedString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_ManagedString"),
	STR("Test_ManagedString_Ansi")
);

char* str = (char*)fpTest_ManagedString_Ansi();

// Compare the ASCII strings
bool success = cmp(str, (char*)"Hello Ansi");

LogTest(success, L"Test_ManagedString_Ansi");

snippet source | anchor

Managed

public static readonly CString HelloAnsi = new CString("Hello Ansi");

[UnmanagedCallersOnly]
public static IntPtr Test_ManagedString_Ansi()
{
    return HelloAnsi.Ptr;
}

snippet source | anchor

What does CString do?

  1. Apends a \0 character on the end of the string.
  2. Converts the string into ANSI encoding.
  3. Allocate memory for native access using Marshal.AllocHGlobal.
  4. Copy the encoded string into the allocated memory.

Return managed wide string

The encoding depends on the platform. For windows systems it is UTF16 and for posix systems it is UTF32.

Native

auto fpTest_ManagedString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_ManagedString"),
	STR("Test_ManagedString_Wide")
);

wchar_t* str = (wchar_t*)fpTest_ManagedString_Wide();

// Compare the wide strings
bool success = cmp(str, (wchar_t*)L"Hello ❤");

LogTest(success, L"Test_ManagedString_Wide");

snippet source | anchor

Managed

What does CString do?

  1. Determins the correct encoding for the current platform. (UTF16 or UTF32)
  2. Apends a \0 character on the end of the string.
  3. Converts the string into the propper encoding.
  4. Allocate memory for native access using Marshal.AllocHGlobal.
  5. Copy the encoded string into the allocated memory.

public static readonly CString HelloWide = new CString("Hello ❤", CEncoding.Wide);

[UnmanagedCallersOnly]
public static IntPtr Test_ManagedString_Wide()
{
    return HelloWide.Ptr;
}

snippet source | anchor


Native Strings

Native ASCII string

Native

auto fpTest_NativeString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeString"),
	STR("Test_NativeString_Ansi")
);

bool success = fpTest_NativeString_Ansi((void*)"Hello Ansi");
LogTest(success, L"Test_NativeString_Ansi");

snippet source | anchor

Managed

[UnmanagedCallersOnly]
public static int Test_NativeString_Ansi(IntPtr stringPtr)
{
    return CEncoding.Ascii.GetString(stringPtr) == "Hello Ansi" ? 1 : 0;
}

snippet source | anchor

Native Wide string

Native

auto fpTest_NativeString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeString"),
	STR("Test_NativeString_Wide")
);

bool success = fpTest_NativeString_Wide((void*)L"Hello ❤");
LogTest(success, L"Test_NativeString_Wide");

snippet source | anchor

Managed

[UnmanagedCallersOnly]
public static int Test_NativeString_Wide(IntPtr stringPtr)
{
    return CEncoding.Wide.GetString(stringPtr) == "Hello ❤" ? 1 : 0;
}

snippet source | anchor

Native string to managed function pointer

Native

struct RetArgs
{
	bool (*CallbackAnsi)(const char*);
	bool (*CallbackWide)(const wchar_t*);
};

snippet source | anchor

typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn2)(void*);

auto fpTest_NativeString_FunctionPointer = (custom_entry_point_fn2)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeString"),
	STR("Test_NativeString_FunctionPointer")
);

RetArgs retArgs;
fpTest_NativeString_FunctionPointer(&retArgs);

{ // Ansi
	bool success = retArgs.CallbackAnsi("Hello Ansi");
	LogTest(success, L"Test_NativeString_FunctionPointer.CallbackAnsi");

	ret &= success;
}

{ // Wide
	bool success = retArgs.CallbackWide(L"Hello ❤");
	LogTest(success, L"Test_NativeString_FunctionPointer.CallbackWide");

	ret &= success;
}

snippet source | anchor

Managed

public delegate bool FunctionPointerCallbackAnsiDelegate(NativeString nstr);
public delegate bool FunctionPointerCallbackWideDelegate(NativeWString nstr);

public static FunctionPointerCallbackAnsiDelegate FunctionPointerCallbackAnsiDelegateInstance =
    new FunctionPointerCallbackAnsiDelegate(CallbackAnsi);

public static FunctionPointerCallbackWideDelegate FunctionPointerCallbackWideDelegateInstance =
    new FunctionPointerCallbackWideDelegate(CallbackWide);

public static bool CallbackAnsi(NativeString nstr) => nstr.ToString() == "Hello Ansi";
public static bool CallbackWide(NativeWString nstr) => nstr.ToString() == "Hello ❤";

[StructLayout(LayoutKind.Sequential)]
public struct RetArgs
{
    public IntPtr CallbackAnsi;
    public IntPtr CallbackWide;
}

[UnmanagedCallersOnly]
public static void Test_NativeString_FunctionPointer(IntPtr retArgsPtr)
{
    unsafe
    {
        RetArgs* retArgs = (RetArgs*)retArgsPtr;
        retArgs->CallbackAnsi =
            Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackAnsiDelegateInstance);
        retArgs->CallbackWide =
            Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackWideDelegateInstance);
    }
}

snippet source | anchor


Managed function-pointer

Managed function-pointer to instance method

Native

typedef int (*managed_callback_fn)(int);

snippet source | anchor

typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int);

// Get the managed entry point
auto fpTest_ManagedFunctionPointer_Instance = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_ManagedFunctionPointer"),
	STR("Test_ManagedFunctionPointer_Instance")
);

// Get the function pointer to the managed method
managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Instance(2);

bool success = managedCallback(6) == 8;

LogTest(success, L"Test_ManagedFunctionPointer_Instance");

snippet source | anchor

Managed

public delegate int FunctionPointerCallbackDelegate(int a);

snippet source | anchor

public class CallableObject
{
    public FunctionPointerCallbackDelegate CallbackDelegate;
    public IntPtr CallbackFunctionPointer;

    private int Member;

    public int Callback(int i) => i + Member;

    public CallableObject(int member)
    {
        Member = member;
        CallbackDelegate = new FunctionPointerCallbackDelegate(Callback);
        CallbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(CallbackDelegate);
    }

}

public static CallableObject CallableObjectInstance;

// Enty point for unmanaged code
[UnmanagedCallersOnly]
public static IntPtr Test_ManagedFunctionPointer_Instance(int member)
{
    CallableObjectInstance = new CallableObject(member);
    return CallableObjectInstance.CallbackFunctionPointer;
}

snippet source | anchor

Managed function-pointer to static function

Native

typedef int (*managed_callback_fn)(int);

snippet source | anchor

typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)();

// Get the managed entry point
auto fpTest_ManagedFunctionPointer_Static = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_ManagedFunctionPointer"),
	STR("Test_ManagedFunctionPointer_Static")
);

// Get the function pointer to the managed function
managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Static();

bool success = managedCallback(5) == 8;

LogTest(success, L"Test_ManagedFunctionPointer_Static");

snippet source | anchor

Managed

public delegate int FunctionPointerCallbackDelegate(int a);

snippet source | anchor

public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateStatic = new FunctionPointerCallbackDelegate(Callback);

public static int Callback(int i) => i + 3;

// Enty point for unmanaged code
[UnmanagedCallersOnly]
public static IntPtr Test_ManagedFunctionPointer_Static()
{
    return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateStatic);
}

snippet source | anchor


Native function-pointer

Calling checked native function pointer

Native

int FEXPORT CallbackFunc(int i)
{
	return i * 2;
}

snippet source | anchor

void* fpNativeCallback = (void*)&CallbackFunc;

snippet source | anchor

auto fpTest_NativeFunctionPointer_Checked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeFunctionPointer"),
	STR("Test_NativeFunctionPointer_Checked")
);

bool success = fpTest_NativeFunctionPointer_Checked(fpNativeCallback, 4) == 8;
LogTest(success, L"Test_NativeFunctionPointer_Checked");

snippet source | anchor

Managed

public delegate int FunctionPointerCallbackDelegate(int a);

[UnmanagedCallersOnly]
public static int Test_NativeFunctionPointer_Checked(IntPtr nativeFunctionPtr, int number)
{
    var callbackFuncDelegate =
        (FunctionPointerCallbackDelegate)Marshal.GetDelegateForFunctionPointer(nativeFunctionPtr,
            typeof(FunctionPointerCallbackDelegate));
    return callbackFuncDelegate(number);
}

snippet source | anchor

Calling unchecked native function pointer

Native

int FEXPORT CallbackFunc(int i)
{
	return i * 2;
}

snippet source | anchor

void* fpNativeCallback = (void*)&CallbackFunc;

snippet source | anchor

auto fpTest_NativeFunctionPointer_Unchecked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeFunctionPointer"),
	STR("Test_NativeFunctionPointer_Unchecked")
);

bool success = fpTest_NativeFunctionPointer_Unchecked(fpNativeCallback, 4) == 8;
LogTest(success, L"Test_NativeFunctionPointer_Unchecked");

snippet source | anchor

Managed

[UnmanagedCallersOnly]
public static int Test_NativeFunctionPointer_Unchecked(IntPtr nativeFunctionPtr, int number)
{
    unsafe
    {
        unchecked
        {
            var callbackFuncPtr = (delegate* unmanaged[Cdecl]<int, int>)nativeFunctionPtr;
            return callbackFuncPtr(number);
        }
    }
}

snippet source | anchor


Usage of unsafe managed code to access native objects

Native

struct Args
{
	int number1;
	int number2;
	int sum;
	char returnMsg[128];
};

bool Run(dotnet_runtime::Library& a_lib)
{
	bool ret = true;

	typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*);

	auto fpTest_ManagedUnsafe_Struct = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
		STR("LibNamespace.Test_ManagedUnsafe"),
		STR("Test_ManagedUnsafe_Struct")
	);

	Args args;
	args.number1 = 1;
	args.number2 = 2;

	fpTest_ManagedUnsafe_Struct(&args);

	{ //sum
		bool success = args.sum == 3;
		LogTest(success, L"Test_ManagedUnsafe_Struct.Sum");
		ret &= success;
	}

	{ // ReturnMsg
		bool success = cmp(args.returnMsg, (char*)"Hello Ansi");
		LogTest(success, L"Test_ManagedUnsafe_Struct.ReturnMsg");
		ret &= success;
	}
	return ret;
}

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Args
{
    public int Number1;
    public int number2;
    public int Sum;
    public fixed byte ReturnMsg[128];
}

[UnmanagedCallersOnly]
public static void Test_ManagedUnsafe_Struct(IntPtr ptr)
{
    unsafe
    {
        Args* args = (Args*) ptr;

        args->Sum = args->Number1 + args->number2;

        // Get the memory offset of Args.ReturnMsg
        var destReturnMsg = IntPtr.Add(ptr, (int)Marshal.OffsetOf(typeof(Args), nameof(Args.ReturnMsg)));

        var data = CEncoding.Ascii.GetBytes("Hello Ansi");

        // Copy the data to the unmanaged memory
        Marshal.Copy(data, 0, (IntPtr)destReturnMsg, data.Length);
    }
}

snippet source | anchor


Native Arrays

Native arrays using fixed struct member

Native

struct args
{
	int Arr[8];
	int Multiplier;
};

snippet source | anchor

args _args{
	1, 2, 3, 4, 5, 6, 7, 8, // Arr[8]
	2 // Multiplier
};

snippet source | anchor

typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args);

auto fpTest_NativeArray_StructFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeArray"),
	STR("Test_NativeArray_StructFixed")
);

bool success = fpTest_NativeArray_StructFixed(_args) == 72;
LogTest(success, L"Test_NativeArray_StructFixed");

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Args
{
    public fixed int Arr[8];
    public int Multiplier;
}

[UnmanagedCallersOnly]
public static unsafe int Test_NativeArray_StructFixed(Args args)
{
    int ret = 0;

    for(int i = 0; i < 8; i++)
    {
        ret += args.Arr[i] * args.Multiplier;
    }

    return ret;
}

snippet source | anchor

Native arrays using pointer

Native

typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int[], int);

auto fpTest_NativeArray_ArgumentFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeArray"),
	STR("Test_NativeArray_ArgumentFixed")
);

bool success = fpTest_NativeArray_ArgumentFixed(_args.Arr, _args.Multiplier) == 72;
LogTest(success, L"Test_NativeArray_ArgumentFixed");

snippet source | anchor

Managed

[UnmanagedCallersOnly]
public static int Test_NativeArray_ArgumentFixed(IntPtr arrPtr, int multiplier)
{
    // ArrPointer as argument does not work here!

    ArrPointer8<int> arr = new ArrPointer8<int>(arrPtr);

    return arr.Sum(el => el * multiplier); ;
}

snippet source | anchor

Native arrays using ArrPointerX on function-pointer

Native

typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)();
typedef int (*managed_callback_fn)(int[], int);

auto fpTest_NativeArray_ArgumentFixed_FunctionPointer = (custom_entry_point_fn)a_lib.GetCustomEntrypoint(
	STR("LibNamespace.Test_NativeArray"),
	STR("Test_NativeArray_ArgumentFixed_FunctionPointer")
);

managed_callback_fn callback = (managed_callback_fn)fpTest_NativeArray_ArgumentFixed_FunctionPointer();

bool success = callback(_args.Arr, _args.Multiplier) == 72;
LogTest(success, L"Test_NativeArray_ArgumentFixed_FunctionPointer");

snippet source | anchor

Managed

public delegate int FunctionPointerCallbackDelegate(ArrPointer8<int> arr, int multiplier);

public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateInstance =
    new FunctionPointerCallbackDelegate(Callback);

public static int Callback(ArrPointer8<int> arr, int multiplier) => arr.Sum(el => el * multiplier);

[UnmanagedCallersOnly]
public static IntPtr Test_NativeArray_ArgumentFixed_FunctionPointer()
{
    return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateInstance);
}

snippet source | anchor


Native Symbols

Calling native exported symbols using DllImport

Native

extern "C"
{
	int FEXPORT Test_DllImport_ExternC(int number)
	{
		return number * 2;
	}
}

snippet source | anchor

void* host_handle = dotnet_runtime::get_host_handle();
return fpTest_DllImport_Call(host_handle, 4) == 8;

snippet source | anchor

For posix systems, use the -export-dynamic flag for the linker.

Managed

private static IntPtr s_moduleHandle = IntPtr.Zero;

private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
    IntPtr ret = libraryName == "Test_DllImport_LibName" ? s_moduleHandle : NativeLibrary.Load(libraryName, assembly, searchPath);
    //Console.WriteLine($"ImportResolver s_moduleHandle={s_moduleHandle} ret={ret}");
    return ret;
}

[DllImport("Test_DllImport_LibName")]
public static extern int Test_DllImport_ExternC(int number);

[UnmanagedCallersOnly]
public static int Test_DllImport_Call(IntPtr moduleHandle, int number)
{
    s_moduleHandle = moduleHandle;
    NativeLibrary.SetDllImportResolver(typeof(Test_DllImport).Assembly, ImportResolver);

    return Test_DllImport_ExternC(number);
}

snippet source | anchor

Calling native exported symbols using GetProcAddress/dlsym

Native

extern "C"
{
	int FEXPORT Test_NativeExport_ExternC(int number)
	{
		return number * 4;
	}
}

snippet source | anchor

void* host_handle = dotnet_runtime::get_host_handle();
return fpTest_NativeExport_Call(host_handle, 4) == 16;

snippet source | anchor

For posix systems, use the -export-dynamic flag for the linker.

Managed

public delegate int FunctionPointerCallbackDelegate(int a);

[UnmanagedCallersOnly]
public static int Test_NativeExport_Call(IntPtr moduleHandle, int number)
{
    var exportedDelegate = ModuleLoader.GetExport<FunctionPointerCallbackDelegate>(moduleHandle, "Test_NativeExport_ExternC");
    int result = exportedDelegate(number);

    unsafe
    {
        unchecked
        {
            var callbackFuncPtr = (delegate* unmanaged[Cdecl]<int, int>)ModuleLoader.GetExport(moduleHandle, "Test_NativeExport_ExternC");
            result += callbackFuncPtr(number);
        }
    }

    return result / 2;
}

snippet source | anchor


Native VTable

VTable

Calling native VTable from managed code

Native

class ClassLayout
{
private:
	int m_iTest = 0;
public:
	virtual void AddOne() { this->m_iTest += 1; }
	virtual void AddTwo() { this->m_iTest += 2; }

	int GetTest() { return m_iTest; }
};

snippet source | anchor

ClassLayout* instance = new ClassLayout();

// Calls AddOne and AddTwo, then overwrites VTable
fpTest_NativeVTable_Call((void*)instance);

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
private struct ClassLayout
{
    public IntPtr VTable;
    public int test;
}

[StructLayout(LayoutKind.Sequential)]
private struct ClassVTable
{
    public IntPtr Method_AddOne;
    public IntPtr Method_AddTwo;
}

delegate void MethodDelegate(IntPtr thisPtr);

snippet source | anchor

ClassLayout instance = Marshal.PtrToStructure<ClassLayout>(classInstance);

ClassVTable vTable = Marshal.PtrToStructure<ClassVTable>(instance.VTable);

MethodDelegate method_AddOne =
    Marshal.GetDelegateForFunctionPointer<MethodDelegate>(vTable.Method_AddOne);
MethodDelegate method_AddTwo =
    Marshal.GetDelegateForFunctionPointer<MethodDelegate>(vTable.Method_AddTwo);

method_AddOne(classInstance);
method_AddTwo(classInstance);

snippet source | anchor

Overwriting native VTable with managed code

Native

class ClassLayout
{
private:
	int m_iTest = 0;
public:
	virtual void AddOne() { this->m_iTest += 1; }
	virtual void AddTwo() { this->m_iTest += 2; }

	int GetTest() { return m_iTest; }
};

snippet source | anchor

ClassLayout* instance = new ClassLayout();

// Calls AddOne and AddTwo, then overwrites VTable
fpTest_NativeVTable_Call((void*)instance);

snippet source | anchor

Managed

[StructLayout(LayoutKind.Sequential)]
private struct ClassLayout
{
    public IntPtr VTable;
    public int test;
}

[StructLayout(LayoutKind.Sequential)]
private struct ClassVTable
{
    public IntPtr Method_AddOne;
    public IntPtr Method_AddTwo;
}

delegate void MethodDelegate(IntPtr thisPtr);

snippet source | anchor

// This delegate will be the new virtual method
private static readonly unsafe MethodDelegate delegate_SubOne = new MethodDelegate(thisPtr =>
{
    var instance = (ClassLayout*)thisPtr;
    instance->test -= 1;
});

// Create new unmanaged VTable instance
private static readonly UnmanagedMemory<ClassVTable> OverwrittenVTable = new();

snippet source | anchor

unsafe
{
    // Get the new VTable
    var vTable = OverwrittenVTable.PtrElem;

    // Set the virtual methods with the new managed function pointer (Both the same for simplicity)
    vTable->Method_AddTwo = vTable->Method_AddOne =
        Marshal.GetFunctionPointerForDelegate(delegate_SubOne);

    // Overwrite the VTable reference of the instance
    var instance = (ClassLayout*) classInstance;
    instance->VTable = (IntPtr) vTable;
}

snippet source | anchor


LICENSE

dotnet_runtime_test is licensed under MIT license. See LICENSE for more details.