using System; using System.Runtime.InteropServices; using UnitTestSharp; namespace Susurrus.XAudio2.UnitTests { public class VtableTests : TestFixture { [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate int AddToSelfCall(IntPtr self, int addend); /// /// A native object whose methods return their slot times a thousand plus an addend, or -1 if they weren't /// called with the object's address. /// public class FakeNativeObject : IDisposable { private readonly AddToSelfCall[] methods; private readonly IntPtr table; public IntPtr Address { get; } public FakeNativeObject(int slotCount) { methods = new AddToSelfCall[slotCount]; table = Marshal.AllocHGlobal(slotCount * IntPtr.Size); for (int slot = 0; slot < slotCount; slot++) { int offset = slot * 1000; methods[slot] = (self, addend) => self == Address ? offset + addend : -1; IntPtr method = Marshal.GetFunctionPointerForDelegate(methods[slot]); Marshal.WriteIntPtr(table, slot * IntPtr.Size, method); } Address = Marshal.AllocHGlobal(IntPtr.Size); Marshal.WriteIntPtr(Address, table); } public void Dispose() { Marshal.FreeHGlobal(Address); Marshal.FreeHGlobal(table); } } public class BindTests : TestFixture { public void CallsTheMethodAtTheSlotWithTheInstanceAsItsFirstArgument() { using (FakeNativeObject fake = new FakeNativeObject(slotCount: 4)) { AddToSelfCall call = Vtable.Bind(fake.Address, slot: 3); CheckEqual(3007, call(fake.Address, 7)); } } public void SlotZero_IsTheFirstMethod() { using (FakeNativeObject fake = new FakeNativeObject(slotCount: 2)) { AddToSelfCall call = Vtable.Bind(fake.Address, slot: 0); CheckEqual(1, call(fake.Address, 1)); } } public void NullInstance_Throws() { CheckThrow(typeof(ArgumentException)); Vtable.Bind(IntPtr.Zero, slot: 0); } public void NegativeSlot_Throws() { using (FakeNativeObject fake = new FakeNativeObject(slotCount: 1)) { CheckThrow(typeof(ArgumentOutOfRangeException)); Vtable.Bind(fake.Address, slot: -1); } } } } }