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);
}
}
}
}
}