Expand framework support and COM ABI coverage

Target .NET 8, 9, 10, Framework 4.7.2, and Framework 4.8. Rename samples to stable Core and Framework names. Add raw-vtable COM tests for x64, x86, and ARM64, and require the Azure architecture matrix before packaging.
This commit is contained in:
Ryan Newington
2026-08-28 11:22:53 +10:00
parent be65cbaa93
commit 43af44f87f
52 changed files with 1116 additions and 128 deletions
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("2A83F3F8-A46C-4104-8FFB-BD9979279450")]
internal sealed class AbiTestCredentialProvider : CredentialProviderBase
{
public AbiTestCredentialProvider()
{
this.Field = new SmallLabelControl("message", "COM ABI test");
}
public SmallLabelControl Field { get; }
public AbiTestCredentialTile2 Tile { get; private set; }
public override bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags)
{
return cpus == UsageScenario.CredUI;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
return new ControlBase[] { this.Field };
}
public override bool ShouldIncludeUserTile(CredentialProviderUser user)
{
return false;
}
public override bool ShouldIncludeGenericTile()
{
return true;
}
public override CredentialTile CreateGenericTile()
{
this.Tile = new AbiTestCredentialTile2(this);
return this.Tile;
}
public override CredentialTile2 CreateUserTile(CredentialProviderUser user)
{
return null;
}
}
}
@@ -0,0 +1,14 @@
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal sealed class AbiTestCredentialTile2 : CredentialTile2
{
public AbiTestCredentialTile2(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
protected override CredentialResponseBase GetCredentials()
{
return null;
}
}
}
@@ -0,0 +1,79 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal sealed class ComInterfacePointer : IDisposable
{
private IntPtr value;
private ComInterfacePointer(IntPtr value)
{
this.value = value;
}
public IntPtr Value
{
get
{
if (this.value == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(ComInterfacePointer));
}
return this.value;
}
}
public static ComInterfacePointer Create(object instance, Guid interfaceId)
{
IntPtr unknown = Marshal.GetIUnknownForObject(instance);
try
{
int hresult = ComMarshal.QueryInterface(unknown, interfaceId, out IntPtr interfacePointer);
if (hresult != CredentialProviderAbi.S_OK)
{
if (interfacePointer != IntPtr.Zero)
{
Marshal.Release(interfacePointer);
}
throw new COMException("The COM interface was not available", hresult);
}
return new ComInterfacePointer(interfacePointer);
}
finally
{
Marshal.Release(unknown);
}
}
public static ComInterfacePointer TakeOwnership(IntPtr value)
{
if (value == IntPtr.Zero)
{
throw new ArgumentException("The COM interface pointer cannot be zero", nameof(value));
}
return new ComInterfacePointer(value);
}
public TDelegate GetMethod<TDelegate>(int slot) where TDelegate : class
{
IntPtr vtable = Marshal.ReadIntPtr(this.Value);
IntPtr method = Marshal.ReadIntPtr(vtable, checked(slot * IntPtr.Size));
return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(method, typeof(TDelegate));
}
public void Dispose()
{
if (this.value != IntPtr.Zero)
{
Marshal.Release(this.value);
this.value = IntPtr.Zero;
}
}
}
}
@@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal static class ComMarshal
{
public static int QueryInterface(IntPtr unknown, Guid interfaceId, out IntPtr interfacePointer)
{
#if NET9_0_OR_GREATER
return Marshal.QueryInterface(unknown, in interfaceId, out interfacePointer);
#else
return Marshal.QueryInterface(unknown, ref interfaceId, out interfacePointer);
#endif
}
}
}
@@ -0,0 +1,59 @@
using System;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
internal static class CredentialProviderAbi
{
public static readonly Guid ICredentialProvider = new Guid("D27C3481-5A1C-45B2-8AAA-C20EBBE8229E");
public static readonly Guid ICredentialProviderCredential = new Guid("63913A93-40C1-481A-818D-4072FF8C70CC");
public static readonly Guid ICredentialProviderCredential2 = new Guid("FD672C54-40EA-4D6E-9B49-CFB1A7507BD7");
public static readonly Guid ICredentialProviderSetUserArray = new Guid("095C1484-1C0C-4388-9C6D-500E61BF84BD");
public static readonly Guid ICredentialProviderUserArray = new Guid("90C119AE-0F18-4520-A1F1-114366A40FE8");
public const int SetUsageScenarioSlot = 3;
public const int GetFieldDescriptorCountSlot = 7;
public const int GetFieldDescriptorAtSlot = 8;
public const int GetCredentialCountSlot = 9;
public const int GetCredentialAtSlot = 10;
public const int SetUserArraySlot = 3;
public const int UserArrayGetCountSlot = 5;
public const int SetSelectedSlot = 5;
public const int SetDeselectedSlot = 6;
public const int GetFieldStateSlot = 7;
public const int GetStringValueSlot = 8;
public const int GetUserSidSlot = 20;
public const int SmallTextFieldType = 2;
public const int DisplayInSelectedTileFieldState = 1;
public const int NoInteractiveFieldState = 0;
public const uint NoDefaultCredential = 0xFFFFFFFF;
public const int S_OK = 0;
public const int S_FALSE = 1;
public const int E_FAIL = unchecked((int)0x80004005);
public const int E_INVALIDARG = unchecked((int)0x80070057);
public const int E_NOTIMPL = unchecked((int)0x80004001);
}
}
@@ -0,0 +1,450 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using NUnit.Framework;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[TestFixture]
[NonParallelizable]
[Apartment(ApartmentState.STA)]
public class CredentialProviderAbiTests
{
[Test]
public void CredentialProviderInterfaceCanBeQueried()
{
var provider = new AbiTestCredentialProvider();
IntPtr unknown = Marshal.GetIUnknownForObject(provider);
IntPtr providerInterface = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(unknown, CredentialProviderAbi.ICredentialProvider, out providerInterface);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(providerInterface, Is.Not.EqualTo(IntPtr.Zero));
}
finally
{
if (providerInterface != IntPtr.Zero)
{
Marshal.Release(providerInterface);
}
Marshal.Release(unknown);
GC.KeepAlive(provider);
}
}
[Test]
public void SetUsageScenarioPreservesHResultAndArguments()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
SetUsageScenarioDelegate setUsageScenario = providerInterface.GetMethod<SetUsageScenarioDelegate>(CredentialProviderAbi.SetUsageScenarioSlot);
int hresult = setUsageScenario(providerInterface.Value, (int)UsageScenario.CredUI, (uint)CredUIWinFlags.CREDUIWIN_SECURE_PROMPT);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(provider.UsageScenario, Is.EqualTo(UsageScenario.CredUI));
Assert.That(provider.CredUIFlags, Is.EqualTo(CredUIWinFlags.CREDUIWIN_SECURE_PROMPT));
hresult = setUsageScenario(providerInterface.Value, (int)UsageScenario.Logon, 0);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_NOTIMPL));
Assert.That(provider.UsageScenario, Is.EqualTo(UsageScenario.Logon));
Assert.That(provider.CredUIFlags, Is.EqualTo((CredUIWinFlags)0));
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorCountReturnsProviderControlCount()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
int hresult = getFieldDescriptorCount(providerInterface.Value, out uint count);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorAtReturnsWindowsSdkLayout()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
GetFieldDescriptorAtDelegate getFieldDescriptorAt = providerInterface.GetMethod<GetFieldDescriptorAtDelegate>(CredentialProviderAbi.GetFieldDescriptorAtSlot);
Assert.That(getFieldDescriptorCount(providerInterface.Value, out uint count), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
IntPtr descriptorPointer = IntPtr.Zero;
IntPtr labelPointer = IntPtr.Zero;
try
{
int hresult = getFieldDescriptorAt(providerInterface.Value, 0, out descriptorPointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(descriptorPointer, Is.Not.EqualTo(IntPtr.Zero));
NativeCredentialProviderFieldDescriptor descriptor = Marshal.PtrToStructure<NativeCredentialProviderFieldDescriptor>(descriptorPointer);
labelPointer = descriptor.Label;
Assert.That(descriptor.FieldId, Is.EqualTo(provider.Field.Id));
Assert.That(descriptor.FieldType, Is.EqualTo(CredentialProviderAbi.SmallTextFieldType));
Assert.That(Marshal.PtrToStringUni(labelPointer), Is.EqualTo("COM ABI test"));
Assert.That(descriptor.FieldTypeGuid, Is.EqualTo(Guid.Empty));
}
finally
{
if (labelPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(labelPointer);
}
if (descriptorPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(descriptorPointer);
}
}
}
GC.KeepAlive(provider);
}
[Test]
public void GetFieldDescriptorAtRejectsInvalidIndex()
{
var provider = new AbiTestCredentialProvider();
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetFieldDescriptorCountDelegate getFieldDescriptorCount = providerInterface.GetMethod<GetFieldDescriptorCountDelegate>(CredentialProviderAbi.GetFieldDescriptorCountSlot);
GetFieldDescriptorAtDelegate getFieldDescriptorAt = providerInterface.GetMethod<GetFieldDescriptorAtDelegate>(CredentialProviderAbi.GetFieldDescriptorAtSlot);
Assert.That(getFieldDescriptorCount(providerInterface.Value, out uint count), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
int hresult = getFieldDescriptorAt(providerInterface.Value, count, out IntPtr descriptorPointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_INVALIDARG));
Assert.That(descriptorPointer, Is.EqualTo(IntPtr.Zero));
}
GC.KeepAlive(provider);
}
[Test]
public void CredentialProviderSetUserArrayInterfaceCanBeQueried()
{
var provider = new AbiTestCredentialProvider();
IntPtr unknown = Marshal.GetIUnknownForObject(provider);
IntPtr setUserArrayInterface = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(unknown, CredentialProviderAbi.ICredentialProviderSetUserArray, out setUserArrayInterface);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(setUserArrayInterface, Is.Not.EqualTo(IntPtr.Zero));
}
finally
{
if (setUserArrayInterface != IntPtr.Zero)
{
Marshal.Release(setUserArrayInterface);
}
Marshal.Release(unknown);
GC.KeepAlive(provider);
}
}
[Test]
public void TestUserArrayUsesWindowsSdkGetCountSlot()
{
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer userArrayInterface = ComInterfacePointer.Create(users, CredentialProviderAbi.ICredentialProviderUserArray))
{
GetUserCountDelegate getCount = userArrayInterface.GetMethod<GetUserCountDelegate>(CredentialProviderAbi.UserArrayGetCountSlot);
int hresult = getCount(userArrayInterface.Value, out uint count);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(0));
Assert.That(users.GetCountCallCount, Is.EqualTo(1));
}
GC.KeepAlive(users);
}
[Test]
public void SetUserArrayInvokesWindowsSdkUserArrayGetCount()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
Assert.That(users.GetCountCallCount, Is.EqualTo(1));
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void GetCredentialCountReturnsGenericTileWithoutDefault()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
int hresult = getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
Assert.That(defaultCredential, Is.EqualTo(CredentialProviderAbi.NoDefaultCredential));
Assert.That(autoLogonWithDefault, Is.EqualTo(0));
Assert.That(users.GetCountCallCount, Is.EqualTo(2));
Assert.That(provider.Tile, Is.Not.Null);
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void GetCredentialAtRejectsInvalidIndex()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
GetCredentialAtDelegate getCredentialAt = providerInterface.GetMethod<GetCredentialAtDelegate>(CredentialProviderAbi.GetCredentialAtSlot);
Assert.That(getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault), Is.EqualTo(CredentialProviderAbi.S_OK));
int hresult = getCredentialAt(providerInterface.Value, count, out IntPtr credential);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.E_FAIL));
Assert.That(credential, Is.EqualTo(IntPtr.Zero));
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1SelectionMethodsPreserveStateAndHResults()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
SetSelectedDelegate setSelected = credential.GetMethod<SetSelectedDelegate>(CredentialProviderAbi.SetSelectedSlot);
SetDeselectedDelegate setDeselected = credential.GetMethod<SetDeselectedDelegate>(CredentialProviderAbi.SetDeselectedSlot);
int hresult = setSelected(credential.Value, out int autoLogon);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(autoLogon, Is.EqualTo(0));
Assert.That(provider.Tile.IsSelected, Is.True);
hresult = setDeselected(credential.Value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(provider.Tile.IsSelected, Is.False);
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1GetFieldStateUsesWindowsSdkEnumValues()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
GetFieldStateDelegate getFieldState = credential.GetMethod<GetFieldStateDelegate>(CredentialProviderAbi.GetFieldStateSlot);
int hresult = getFieldState(credential.Value, provider.Field.Id, out int fieldState, out int interactiveState);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(fieldState, Is.EqualTo(CredentialProviderAbi.DisplayInSelectedTileFieldState));
Assert.That(interactiveState, Is.EqualTo(CredentialProviderAbi.NoInteractiveFieldState));
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV1GetStringValueReturnsComTaskMemory()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
GetStringValueDelegate getStringValue = credential.GetMethod<GetStringValueDelegate>(CredentialProviderAbi.GetStringValueSlot);
IntPtr value = IntPtr.Zero;
try
{
int hresult = getStringValue(credential.Value, provider.Field.Id, out value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(value, Is.Not.EqualTo(IntPtr.Zero));
Assert.That(Marshal.PtrToStringUni(value), Is.EqualTo("COM ABI test"));
}
finally
{
if (value != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(value);
}
}
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void CredentialV2GetUserSidUsesInheritedVtableOrder()
{
var provider = new AbiTestCredentialProvider();
var users = new TestCredentialProviderUserArray();
using (ComInterfacePointer credential = CreateCredentialInterface(provider, users))
{
IntPtr credential2Pointer = IntPtr.Zero;
try
{
int hresult = ComMarshal.QueryInterface(credential.Value, CredentialProviderAbi.ICredentialProviderCredential2, out credential2Pointer);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(credential2Pointer, Is.Not.EqualTo(IntPtr.Zero));
using (ComInterfacePointer credential2 = ComInterfacePointer.TakeOwnership(credential2Pointer))
{
credential2Pointer = IntPtr.Zero;
GetUserSidDelegate getUserSid = credential2.GetMethod<GetUserSidDelegate>(CredentialProviderAbi.GetUserSidSlot);
IntPtr sid = IntPtr.Zero;
try
{
hresult = getUserSid(credential2.Value, out sid);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_FALSE));
Assert.That(sid, Is.EqualTo(IntPtr.Zero));
}
finally
{
if (sid != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(sid);
}
}
}
}
finally
{
if (credential2Pointer != IntPtr.Zero)
{
Marshal.Release(credential2Pointer);
}
}
}
GC.KeepAlive(users);
GC.KeepAlive(provider);
}
[Test]
public void FieldDescriptorDeclarationMatchesWindowsSdkSize()
{
int expectedSize = IntPtr.Size == 4 ? 28 : 32;
Assert.That(Marshal.SizeOf<NativeCredentialProviderFieldDescriptor>(), Is.EqualTo(expectedSize));
}
private static void SetEmptyUserArray(AbiTestCredentialProvider provider, TestCredentialProviderUserArray users)
{
using (ComInterfacePointer setUserArrayInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProviderSetUserArray))
using (ComInterfacePointer userArrayInterface = ComInterfacePointer.Create(users, CredentialProviderAbi.ICredentialProviderUserArray))
{
SetUserArrayDelegate setUserArray = setUserArrayInterface.GetMethod<SetUserArrayDelegate>(CredentialProviderAbi.SetUserArraySlot);
int hresult = setUserArray(setUserArrayInterface.Value, userArrayInterface.Value);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
}
}
private static ComInterfacePointer CreateCredentialInterface(AbiTestCredentialProvider provider, TestCredentialProviderUserArray users)
{
SetEmptyUserArray(provider, users);
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(provider, CredentialProviderAbi.ICredentialProvider))
{
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
GetCredentialAtDelegate getCredentialAt = providerInterface.GetMethod<GetCredentialAtDelegate>(CredentialProviderAbi.GetCredentialAtSlot);
Assert.That(getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault), Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(count, Is.EqualTo(1));
IntPtr credential = IntPtr.Zero;
try
{
int hresult = getCredentialAt(providerInterface.Value, 0, out credential);
Assert.That(hresult, Is.EqualTo(CredentialProviderAbi.S_OK));
Assert.That(credential, Is.Not.EqualTo(IntPtr.Zero));
ComInterfacePointer result = ComInterfacePointer.TakeOwnership(credential);
credential = IntPtr.Zero;
return result;
}
finally
{
if (credential != IntPtr.Zero)
{
Marshal.Release(credential);
}
}
}
}
}
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetCredentialAtDelegate(IntPtr instance, uint index, out IntPtr credential);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetCredentialCountDelegate(IntPtr instance, out uint count, out uint defaultCredential, out int autoLogonWithDefault);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldDescriptorAtDelegate(IntPtr instance, uint index, out IntPtr descriptor);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldDescriptorCountDelegate(IntPtr instance, out uint count);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetFieldStateDelegate(IntPtr instance, uint fieldId, out int fieldState, out int interactiveState);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetStringValueDelegate(IntPtr instance, uint fieldId, out IntPtr value);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetUserCountDelegate(IntPtr instance, out uint count);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int GetUserSidDelegate(IntPtr instance, out IntPtr sid);
}
@@ -0,0 +1,27 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[Guid("90C119AE-0F18-4520-A1F1-114366A40FE8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestCredentialProviderUserArray
{
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int SetProviderFilter(ref Guid providerToFilterTo);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetAccountOptions(out int accountOptions);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetCount(out uint userCount);
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int GetAt(uint userIndex, out IntPtr user);
}
}
@@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[StructLayout(LayoutKind.Sequential)]
internal struct NativeCredentialProviderFieldDescriptor
{
public uint FieldId;
public int FieldType;
public IntPtr Label;
public Guid FieldTypeGuid;
}
}
@@ -0,0 +1,27 @@
using System;
using NUnit.Framework;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[TestFixture]
public class ProcessArchitectureTests
{
[Test]
public void TestHostUsesRequestedArchitecture()
{
#if TEST_X86
const string expectedArchitecture = "x86";
#elif TEST_X64
const string expectedArchitecture = "AMD64";
#elif TEST_ARM64
const string expectedArchitecture = "ARM64";
#else
#error A test process architecture must be defined by the project.
#endif
string actualArchitecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
Assert.That(actualArchitecture, Is.EqualTo(expectedArchitecture).IgnoreCase);
}
}
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetDeselectedDelegate(IntPtr instance);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetSelectedDelegate(IntPtr instance, out int autoLogon);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetUsageScenarioDelegate(IntPtr instance, int usageScenario, uint flags);
}
@@ -0,0 +1,8 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int SetUserArrayDelegate(IntPtr instance, IntPtr users);
}
@@ -0,0 +1,51 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
// The in-process call also requires the callback to implement the parameter's managed interface type.
// ITestCredentialProviderUserArray keeps the COM contract under test independent of that adapter.
internal sealed class TestCredentialProviderUserArray : ITestCredentialProviderUserArray, ICredentialProviderUserArray
{
public int GetCountCallCount { get; private set; }
public int SetProviderFilter(ref Guid providerToFilterTo)
{
return CredentialProviderAbi.E_NOTIMPL;
}
public int GetAccountOptions(out int accountOptions)
{
accountOptions = 0;
return CredentialProviderAbi.S_OK;
}
public int GetCount(out uint userCount)
{
this.GetCountCallCount++;
userCount = 0;
return CredentialProviderAbi.S_OK;
}
public int GetAt(uint userIndex, out IntPtr user)
{
user = IntPtr.Zero;
return CredentialProviderAbi.E_INVALIDARG;
}
int ICredentialProviderUserArray.GetAccountOptions(out AccountOptions accountOptions)
{
accountOptions = AccountOptions.None;
return CredentialProviderAbi.S_OK;
}
int ICredentialProviderUserArray.GetAt(uint userIndex, out ICredentialProviderUser user)
{
user = null;
return CredentialProviderAbi.E_INVALIDARG;
}
}
}
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;net461;net48</TargetFrameworks>
<TargetFrameworks>net8.0-windows;net9.0-windows;net10.0-windows;net472;net48</TargetFrameworks>
<IsPackable>false</IsPackable>
<PlatformTargets>x64</PlatformTargets>
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);TEST_X64</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
</ItemGroup>
<ItemGroup>