Fix credential event callback reference lifetime
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[ComVisible(true)]
|
||||||
|
[ClassInterface(ClassInterfaceType.None)]
|
||||||
|
[Guid("2C87FFBB-6497-4D1A-8291-74B5DB3A0C3A")]
|
||||||
|
internal sealed class AbiTestCredentialProvider3 : CredentialProviderBase
|
||||||
|
{
|
||||||
|
public AbiTestCredentialProvider3()
|
||||||
|
{
|
||||||
|
this.Field = new SmallLabelControl("message", "Callback ABI test");
|
||||||
|
this.Logo = new CredentialProviderLogoControl("logo", "Callback ABI logo");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SmallLabelControl Field { get; }
|
||||||
|
|
||||||
|
public CredentialProviderLogoControl Logo { get; }
|
||||||
|
|
||||||
|
public AbiTestCredentialTile3 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, this.Logo };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldIncludeUserTile(CredentialProviderUser user)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldIncludeGenericTile()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CredentialTile CreateGenericTile()
|
||||||
|
{
|
||||||
|
this.Tile = new AbiTestCredentialTile3(this);
|
||||||
|
return this.Tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CredentialTile2 CreateUserTile(CredentialProviderUser user)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
internal sealed class AbiTestCredentialTile3 : CredentialTile3
|
||||||
|
{
|
||||||
|
public AbiTestCredentialTile3(CredentialProviderBase credentialProvider) : base(credentialProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ThrowOnLoad { get; set; }
|
||||||
|
|
||||||
|
public override void OnLoad()
|
||||||
|
{
|
||||||
|
if (this.ThrowOnLoad)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Callback ABI test OnLoad failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CredentialResponseBase GetCredentials()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int CredentialAdviseDelegate(IntPtr instance, IntPtr credentialEvents);
|
||||||
|
}
|
||||||
+295
@@ -0,0 +1,295 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[NonParallelizable]
|
||||||
|
[Apartment(ApartmentState.STA)]
|
||||||
|
public class CredentialCallbackAbiTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void RawEventInterfacesPreserveQueryInterfaceIdentityAndReferenceCounts()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events = new RawCredentialEvents(3);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AssertIUnknownIdentity(events, events.Events1Interface);
|
||||||
|
AssertIUnknownIdentity(events, events.Events2Interface);
|
||||||
|
AssertIUnknownIdentity(events, events.Events3Interface);
|
||||||
|
|
||||||
|
RawQueryInterfaceDelegate queryInterface = GetRawMethod<RawQueryInterfaceDelegate>(events.Events1Interface, 0);
|
||||||
|
Guid events2Id = RawCredentialEventAbi.ICredentialProviderCredentialEvents2;
|
||||||
|
int beforeEvents2Query = events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(queryInterface(events.Events1Interface, ref events2Id, out IntPtr events2Pointer), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
Assert.That(events2Pointer, Is.EqualTo(events.Events2Interface));
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(beforeEvents2Query + 1));
|
||||||
|
ReleaseRaw(events2Pointer);
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(beforeEvents2Query));
|
||||||
|
|
||||||
|
Guid unsupportedId = new Guid("757DA58F-742C-4840-9692-10A9D6392E93");
|
||||||
|
int beforeUnsupportedQuery = events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(queryInterface(events.Events1Interface, ref unsupportedId, out IntPtr unsupportedPointer), Is.EqualTo(RawCredentialEventAbi.E_NOINTERFACE));
|
||||||
|
Assert.That(unsupportedPointer, Is.EqualTo(IntPtr.Zero));
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(beforeUnsupportedQuery));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
events.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FailedAdviseReleasesCallbackState()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events = ExerciseFailedAdvise();
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RepeatedAdviseReplacesTheExistingCallbackReference()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events;
|
||||||
|
|
||||||
|
using (CredentialCallbackScenario scenario = new CredentialCallbackScenario(2))
|
||||||
|
{
|
||||||
|
events = scenario.Events;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterFirstAdvise = events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterSecondAdvise = events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(afterSecondAdvise, Is.EqualTo(afterFirstAdvise));
|
||||||
|
Assert.That(scenario.UnAdvise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.LessThan(afterSecondAdvise));
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RawCredentialEvents ExerciseFailedAdvise()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events;
|
||||||
|
|
||||||
|
using (CredentialCallbackScenario scenario = new CredentialCallbackScenario(2))
|
||||||
|
{
|
||||||
|
events = scenario.Events;
|
||||||
|
scenario.Provider.Tile.ThrowOnLoad = true;
|
||||||
|
int releaseCallsBeforeAdvise = events.ReleaseCallCount;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.E_FAIL));
|
||||||
|
Assert.That(events.ReleaseCallCount, Is.GreaterThan(releaseCallsBeforeAdvise));
|
||||||
|
|
||||||
|
scenario.Provider.Field.Label = "No callback after failed Advise";
|
||||||
|
Assert.That(events.SetFieldStringCallCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Events1SetFieldStringPreservesSlotArgumentsAndLifetimePhases()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events;
|
||||||
|
|
||||||
|
using (CredentialCallbackScenario scenario = new CredentialCallbackScenario(1))
|
||||||
|
{
|
||||||
|
events = scenario.Events;
|
||||||
|
int beforeAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
SmallLabelControl field = scenario.Provider.Tile.Controls.GetControl<SmallLabelControl>("message");
|
||||||
|
field.Label = "Updated through Events1";
|
||||||
|
int duringUpdate = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(scenario.Events.SetFieldStringCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(scenario.Events.LastCredential, Is.Not.EqualTo(IntPtr.Zero));
|
||||||
|
Assert.That(scenario.Events.LastFieldId, Is.EqualTo(field.Id));
|
||||||
|
Assert.That(scenario.Events.LastString, Is.EqualTo("Updated through Events1"));
|
||||||
|
Assert.That(scenario.Events.Events2QueryCount, Is.GreaterThan(0));
|
||||||
|
Assert.That(scenario.Events.Events3QueryCount, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
int releaseCallsBeforeUnAdvise = scenario.Events.ReleaseCallCount;
|
||||||
|
Assert.That(scenario.UnAdvise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterUnAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
AssertLifetimePhases(scenario.Events, beforeAdvise, afterAdvise, duringUpdate, afterUnAdvise, releaseCallsBeforeUnAdvise);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Events2FieldUpdatesPreserveSlotsArgumentsAndLifetimePhases()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events;
|
||||||
|
|
||||||
|
using (CredentialCallbackScenario scenario = new CredentialCallbackScenario(2))
|
||||||
|
{
|
||||||
|
events = scenario.Events;
|
||||||
|
int beforeAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
SmallLabelControl field = scenario.Provider.Tile.Controls.GetControl<SmallLabelControl>("message");
|
||||||
|
scenario.Provider.Tile.BeginBulkFieldUpdate();
|
||||||
|
field.Options = FieldOptions.Email;
|
||||||
|
scenario.Provider.Tile.EndBulkFieldUpdate();
|
||||||
|
int duringUpdate = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(scenario.Events.BeginFieldUpdatesCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(scenario.Events.EndFieldUpdatesCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(scenario.Events.SetFieldOptionsCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(scenario.Events.LastCredential, Is.Not.EqualTo(IntPtr.Zero));
|
||||||
|
Assert.That(scenario.Events.LastFieldId, Is.EqualTo(field.Id));
|
||||||
|
Assert.That(scenario.Events.LastUIntValue, Is.EqualTo((uint)FieldOptions.Email));
|
||||||
|
Assert.That(scenario.Events.Events2QueryCount, Is.GreaterThan(0));
|
||||||
|
Assert.That(scenario.Events.Events3QueryCount, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
int releaseCallsBeforeUnAdvise = scenario.Events.ReleaseCallCount;
|
||||||
|
Assert.That(scenario.UnAdvise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterUnAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
AssertLifetimePhases(scenario.Events, beforeAdvise, afterAdvise, duringUpdate, afterUnAdvise, releaseCallsBeforeUnAdvise);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Events3BitmapBufferPreservesSlotArgumentsCallShapeAndLifetimePhases()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events = ExerciseEvents3BitmapBuffer();
|
||||||
|
|
||||||
|
CollectComWrappers();
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RawCredentialEvents ExerciseEvents3BitmapBuffer()
|
||||||
|
{
|
||||||
|
RawCredentialEvents events;
|
||||||
|
|
||||||
|
using (CredentialCallbackScenario scenario = new CredentialCallbackScenario(3))
|
||||||
|
{
|
||||||
|
events = scenario.Events;
|
||||||
|
int beforeAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(scenario.Advise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
CredentialProviderLogoControl logo = scenario.Provider.Tile.Controls.GetControl<CredentialProviderLogoControl>("logo");
|
||||||
|
using (Bitmap bitmap = new Bitmap(2, 2))
|
||||||
|
{
|
||||||
|
bitmap.SetPixel(0, 0, Color.Transparent);
|
||||||
|
bitmap.SetPixel(1, 0, Color.Red);
|
||||||
|
logo.Bitmap = bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
int duringUpdate = scenario.Events.CurrentReferenceCount;
|
||||||
|
byte[] buffer = scenario.Events.LastBitmapBuffer;
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(scenario.Events.SetFieldBitmapBufferCallCount, Is.EqualTo(1));
|
||||||
|
Assert.That(scenario.Events.LastCredential, Is.Not.EqualTo(IntPtr.Zero));
|
||||||
|
Assert.That(scenario.Events.LastFieldId, Is.EqualTo(logo.Id));
|
||||||
|
Assert.That(buffer, Is.Not.Null);
|
||||||
|
Assert.That(buffer.Length, Is.GreaterThan(8));
|
||||||
|
Assert.That(buffer[0], Is.EqualTo(0x89));
|
||||||
|
Assert.That(buffer[1], Is.EqualTo(0x50));
|
||||||
|
Assert.That(buffer[2], Is.EqualTo(0x4E));
|
||||||
|
Assert.That(buffer[3], Is.EqualTo(0x47));
|
||||||
|
Assert.That(buffer[4], Is.EqualTo(0x0D));
|
||||||
|
Assert.That(buffer[5], Is.EqualTo(0x0A));
|
||||||
|
Assert.That(buffer[6], Is.EqualTo(0x1A));
|
||||||
|
Assert.That(buffer[7], Is.EqualTo(0x0A));
|
||||||
|
Assert.That(scenario.Events.BitmapBufferReturnValuePointerWasPresent, Is.True);
|
||||||
|
Assert.That(scenario.Events.Events2QueryCount, Is.GreaterThan(0));
|
||||||
|
Assert.That(scenario.Events.Events3QueryCount, Is.GreaterThan(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
int releaseCallsBeforeUnAdvise = scenario.Events.ReleaseCallCount;
|
||||||
|
Assert.That(scenario.UnAdvise(), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
int afterUnAdvise = scenario.Events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
AssertLifetimePhases(scenario.Events, beforeAdvise, afterAdvise, duringUpdate, afterUnAdvise, releaseCallsBeforeUnAdvise);
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertLifetimePhases(RawCredentialEvents events, int beforeAdvise, int afterAdvise, int duringUpdate, int afterUnAdvise, int releaseCallsBeforeUnAdvise)
|
||||||
|
{
|
||||||
|
TestContext.WriteLine($"Callback reference counts: before Advise={beforeAdvise}, after Advise={afterAdvise}, during update={duringUpdate}, after UnAdvise={afterUnAdvise}; AddRef calls={events.AddRefCallCount}, Release calls={events.ReleaseCallCount}");
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beforeAdvise, Is.EqualTo(1));
|
||||||
|
Assert.That(afterAdvise, Is.GreaterThan(beforeAdvise));
|
||||||
|
Assert.That(duringUpdate, Is.GreaterThanOrEqualTo(afterAdvise));
|
||||||
|
Assert.That(afterUnAdvise, Is.LessThan(duringUpdate));
|
||||||
|
Assert.That(afterUnAdvise, Is.GreaterThanOrEqualTo(beforeAdvise));
|
||||||
|
Assert.That(events.ReleaseCallCount, Is.GreaterThan(releaseCallsBeforeUnAdvise));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertIUnknownIdentity(RawCredentialEvents events, IntPtr source)
|
||||||
|
{
|
||||||
|
RawQueryInterfaceDelegate queryInterface = GetRawMethod<RawQueryInterfaceDelegate>(source, 0);
|
||||||
|
Guid unknownId = RawCredentialEventAbi.IUnknown;
|
||||||
|
int beforeQuery = events.CurrentReferenceCount;
|
||||||
|
|
||||||
|
Assert.That(queryInterface(source, ref unknownId, out IntPtr unknown), Is.EqualTo(CredentialProviderAbi.S_OK));
|
||||||
|
Assert.That(unknown, Is.EqualTo(events.Events1Interface));
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(beforeQuery + 1));
|
||||||
|
|
||||||
|
ReleaseRaw(unknown);
|
||||||
|
Assert.That(events.CurrentReferenceCount, Is.EqualTo(beforeQuery));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TDelegate GetRawMethod<TDelegate>(IntPtr interfacePointer, int slot) where TDelegate : class
|
||||||
|
{
|
||||||
|
IntPtr vtable = Marshal.ReadIntPtr(interfacePointer);
|
||||||
|
IntPtr method = Marshal.ReadIntPtr(vtable, checked(slot * IntPtr.Size));
|
||||||
|
return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(method, typeof(TDelegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReleaseRaw(IntPtr interfacePointer)
|
||||||
|
{
|
||||||
|
RawReferenceDelegate release = GetRawMethod<RawReferenceDelegate>(interfacePointer, 2);
|
||||||
|
release(interfacePointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CollectComWrappers()
|
||||||
|
{
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
GC.Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
internal sealed class CredentialCallbackScenario : IDisposable
|
||||||
|
{
|
||||||
|
private readonly TestCredentialProviderUserArray users;
|
||||||
|
private readonly ComInterfacePointer credential;
|
||||||
|
private readonly CredentialAdviseDelegate advise;
|
||||||
|
private readonly CredentialUnAdviseDelegate unAdvise;
|
||||||
|
private bool advised;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public CredentialCallbackScenario(int maximumEventsVersion)
|
||||||
|
{
|
||||||
|
this.Provider = new AbiTestCredentialProvider3();
|
||||||
|
this.users = new TestCredentialProviderUserArray();
|
||||||
|
this.Events = new RawCredentialEvents(maximumEventsVersion);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.InitializeProvider();
|
||||||
|
this.credential = this.CreateCredentialInterface();
|
||||||
|
this.advise = this.credential.GetMethod<CredentialAdviseDelegate>(RawCredentialEventAbi.CredentialAdviseSlot);
|
||||||
|
this.unAdvise = this.credential.GetMethod<CredentialUnAdviseDelegate>(RawCredentialEventAbi.CredentialUnAdviseSlot);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.credential?.Dispose();
|
||||||
|
this.Events.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbiTestCredentialProvider3 Provider { get; }
|
||||||
|
|
||||||
|
public RawCredentialEvents Events { get; }
|
||||||
|
|
||||||
|
public int Advise()
|
||||||
|
{
|
||||||
|
this.ThrowIfDisposed();
|
||||||
|
int hresult = this.advise(this.credential.Value, this.Events.Events1Interface);
|
||||||
|
if (hresult == CredentialProviderAbi.S_OK)
|
||||||
|
{
|
||||||
|
this.advised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hresult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int UnAdvise()
|
||||||
|
{
|
||||||
|
this.ThrowIfDisposed();
|
||||||
|
int hresult = this.unAdvise(this.credential.Value);
|
||||||
|
if (hresult == CredentialProviderAbi.S_OK)
|
||||||
|
{
|
||||||
|
this.advised = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hresult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.advised)
|
||||||
|
{
|
||||||
|
this.unAdvise(this.credential.Value);
|
||||||
|
this.advised = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.credential.Dispose();
|
||||||
|
this.Events.Dispose();
|
||||||
|
this.disposed = true;
|
||||||
|
GC.KeepAlive(this.users);
|
||||||
|
GC.KeepAlive(this.Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeProvider()
|
||||||
|
{
|
||||||
|
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(this.Provider, CredentialProviderAbi.ICredentialProvider))
|
||||||
|
{
|
||||||
|
SetUsageScenarioDelegate setUsageScenario = providerInterface.GetMethod<SetUsageScenarioDelegate>(CredentialProviderAbi.SetUsageScenarioSlot);
|
||||||
|
this.ThrowIfFailed(setUsageScenario(providerInterface.Value, (int)UsageScenario.CredUI, 0), "SetUsageScenario");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (ComInterfacePointer setUserArrayInterface = ComInterfacePointer.Create(this.Provider, CredentialProviderAbi.ICredentialProviderSetUserArray))
|
||||||
|
using (ComInterfacePointer userArrayInterface = ComInterfacePointer.Create(this.users, CredentialProviderAbi.ICredentialProviderUserArray))
|
||||||
|
{
|
||||||
|
SetUserArrayDelegate setUserArray = setUserArrayInterface.GetMethod<SetUserArrayDelegate>(CredentialProviderAbi.SetUserArraySlot);
|
||||||
|
this.ThrowIfFailed(setUserArray(setUserArrayInterface.Value, userArrayInterface.Value), "SetUserArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ComInterfacePointer CreateCredentialInterface()
|
||||||
|
{
|
||||||
|
using (ComInterfacePointer providerInterface = ComInterfacePointer.Create(this.Provider, CredentialProviderAbi.ICredentialProvider))
|
||||||
|
{
|
||||||
|
GetCredentialCountDelegate getCredentialCount = providerInterface.GetMethod<GetCredentialCountDelegate>(CredentialProviderAbi.GetCredentialCountSlot);
|
||||||
|
GetCredentialAtDelegate getCredentialAt = providerInterface.GetMethod<GetCredentialAtDelegate>(CredentialProviderAbi.GetCredentialAtSlot);
|
||||||
|
|
||||||
|
this.ThrowIfFailed(getCredentialCount(providerInterface.Value, out uint count, out uint defaultCredential, out int autoLogonWithDefault), "GetCredentialCount");
|
||||||
|
if (count != 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"The callback ABI provider returned {count} credentials instead of one");
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr credentialPointer = IntPtr.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.ThrowIfFailed(getCredentialAt(providerInterface.Value, 0, out credentialPointer), "GetCredentialAt");
|
||||||
|
ComInterfacePointer result = ComInterfacePointer.TakeOwnership(credentialPointer);
|
||||||
|
credentialPointer = IntPtr.Zero;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (credentialPointer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Release(credentialPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowIfFailed(int hresult, string operation)
|
||||||
|
{
|
||||||
|
if (hresult != CredentialProviderAbi.S_OK)
|
||||||
|
{
|
||||||
|
throw new COMException($"{operation} failed", hresult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowIfDisposed()
|
||||||
|
{
|
||||||
|
if (this.disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(CredentialCallbackScenario));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int CredentialUnAdviseDelegate(IntPtr instance);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
internal static class RawCredentialEventAbi
|
||||||
|
{
|
||||||
|
public static readonly Guid IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
|
||||||
|
|
||||||
|
public static readonly Guid ICredentialProviderCredentialEvents = new Guid("FA6FA76B-66B7-4B11-95F1-86171118E816");
|
||||||
|
|
||||||
|
public static readonly Guid ICredentialProviderCredentialEvents2 = new Guid("B53C00B6-9922-4B78-B1F4-DDFE774DC39B");
|
||||||
|
|
||||||
|
public static readonly Guid ICredentialProviderCredentialEvents3 = new Guid("2D8DEEB8-1322-4973-8DF9-B282F2468290");
|
||||||
|
|
||||||
|
public const int CredentialAdviseSlot = 3;
|
||||||
|
|
||||||
|
public const int CredentialUnAdviseSlot = 4;
|
||||||
|
|
||||||
|
public const int SetFieldStringSlot = 5;
|
||||||
|
|
||||||
|
public const int BeginFieldUpdatesSlot = 13;
|
||||||
|
|
||||||
|
public const int EndFieldUpdatesSlot = 14;
|
||||||
|
|
||||||
|
public const int SetFieldOptionsSlot = 15;
|
||||||
|
|
||||||
|
public const int SetFieldBitmapBufferSlot = 16;
|
||||||
|
|
||||||
|
public const int Events1SlotCount = 13;
|
||||||
|
|
||||||
|
public const int Events2SlotCount = 16;
|
||||||
|
|
||||||
|
public const int Events3SlotCount = 17;
|
||||||
|
|
||||||
|
public const int E_NOINTERFACE = unchecked((int)0x80004002);
|
||||||
|
|
||||||
|
public const int E_POINTER = unchecked((int)0x80004003);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
// The trailing returnValue pointer characterizes the current CLR projection without PreserveSig.
|
||||||
|
// It does not establish the native ABI of the undocumented Windows Events3 interface.
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventBitmapBufferDelegate(IntPtr instance, IntPtr credential, uint fieldId, uint imageBufferSize, IntPtr imageBuffer, IntPtr returnValue);
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventFieldCheckboxDelegate(IntPtr instance, IntPtr credential, uint fieldId, int isChecked, IntPtr label);
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventFieldPointerDelegate(IntPtr instance, IntPtr credential, uint fieldId, IntPtr value);
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventFieldUIntDelegate(IntPtr instance, IntPtr credential, uint fieldId, uint value);
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventNoArgumentsDelegate(IntPtr instance);
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawCredentialEventOnCreatingWindowDelegate(IntPtr instance, out IntPtr ownerWindow);
|
||||||
|
}
|
||||||
@@ -0,0 +1,430 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
internal sealed class RawCredentialEvents : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly object RegistryLock = new object();
|
||||||
|
private static readonly HashSet<RawCredentialEvents> Registry = new HashSet<RawCredentialEvents>();
|
||||||
|
|
||||||
|
private readonly object observationLock = new object();
|
||||||
|
private readonly int maximumVersion;
|
||||||
|
private readonly RawQueryInterfaceDelegate queryInterfaceDelegate;
|
||||||
|
private readonly RawReferenceDelegate addRefDelegate;
|
||||||
|
private readonly RawReferenceDelegate releaseDelegate;
|
||||||
|
private readonly RawCredentialEventFieldUIntDelegate fieldUIntStubDelegate;
|
||||||
|
private readonly RawCredentialEventFieldPointerDelegate fieldPointerStubDelegate;
|
||||||
|
private readonly RawCredentialEventFieldCheckboxDelegate fieldCheckboxStubDelegate;
|
||||||
|
private readonly RawCredentialEventFieldPointerDelegate setFieldStringDelegate;
|
||||||
|
private readonly RawCredentialEventNoArgumentsDelegate beginFieldUpdatesDelegate;
|
||||||
|
private readonly RawCredentialEventNoArgumentsDelegate endFieldUpdatesDelegate;
|
||||||
|
private readonly RawCredentialEventFieldUIntDelegate setFieldOptionsDelegate;
|
||||||
|
private readonly RawCredentialEventOnCreatingWindowDelegate onCreatingWindowDelegate;
|
||||||
|
private readonly RawCredentialEventBitmapBufferDelegate setFieldBitmapBufferDelegate;
|
||||||
|
|
||||||
|
private IntPtr events1Vtable;
|
||||||
|
private IntPtr events2Vtable;
|
||||||
|
private IntPtr events3Vtable;
|
||||||
|
private IntPtr events1Interface;
|
||||||
|
private IntPtr events2Interface;
|
||||||
|
private IntPtr events3Interface;
|
||||||
|
private int referenceCount;
|
||||||
|
private int addRefCallCount;
|
||||||
|
private int releaseCallCount;
|
||||||
|
private int events2QueryCount;
|
||||||
|
private int events3QueryCount;
|
||||||
|
private int setFieldStringCallCount;
|
||||||
|
private int beginFieldUpdatesCallCount;
|
||||||
|
private int endFieldUpdatesCallCount;
|
||||||
|
private int setFieldOptionsCallCount;
|
||||||
|
private int setFieldBitmapBufferCallCount;
|
||||||
|
private int ownerReferenceReleased;
|
||||||
|
private IntPtr lastCredential;
|
||||||
|
private uint lastFieldId;
|
||||||
|
private uint lastUIntValue;
|
||||||
|
private string lastString;
|
||||||
|
private byte[] lastBitmapBuffer;
|
||||||
|
private bool bitmapBufferReturnValuePointerWasPresent;
|
||||||
|
|
||||||
|
public RawCredentialEvents(int maximumVersion)
|
||||||
|
{
|
||||||
|
if (maximumVersion < 1 || maximumVersion > 3)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maximumVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maximumVersion = maximumVersion;
|
||||||
|
this.referenceCount = 1;
|
||||||
|
this.queryInterfaceDelegate = this.QueryInterface;
|
||||||
|
this.addRefDelegate = this.AddRef;
|
||||||
|
this.releaseDelegate = this.Release;
|
||||||
|
this.fieldUIntStubDelegate = this.FieldUIntStub;
|
||||||
|
this.fieldPointerStubDelegate = this.FieldPointerStub;
|
||||||
|
this.fieldCheckboxStubDelegate = this.FieldCheckboxStub;
|
||||||
|
this.setFieldStringDelegate = this.SetFieldString;
|
||||||
|
this.beginFieldUpdatesDelegate = this.BeginFieldUpdates;
|
||||||
|
this.endFieldUpdatesDelegate = this.EndFieldUpdates;
|
||||||
|
this.setFieldOptionsDelegate = this.SetFieldOptions;
|
||||||
|
this.onCreatingWindowDelegate = this.OnCreatingWindow;
|
||||||
|
this.setFieldBitmapBufferDelegate = this.SetFieldBitmapBuffer;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.events1Vtable = this.CreateVtable(RawCredentialEventAbi.Events1SlotCount);
|
||||||
|
this.events1Interface = this.CreateInterface(this.events1Vtable);
|
||||||
|
|
||||||
|
if (maximumVersion >= 2)
|
||||||
|
{
|
||||||
|
this.events2Vtable = this.CreateVtable(RawCredentialEventAbi.Events2SlotCount);
|
||||||
|
this.events2Interface = this.CreateInterface(this.events2Vtable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximumVersion >= 3)
|
||||||
|
{
|
||||||
|
this.events3Vtable = this.CreateVtable(RawCredentialEventAbi.Events3SlotCount);
|
||||||
|
this.events3Interface = this.CreateInterface(this.events3Vtable);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (RegistryLock)
|
||||||
|
{
|
||||||
|
Registry.Add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.FreeNativeMemory();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr Events1Interface => this.events1Interface;
|
||||||
|
|
||||||
|
public IntPtr Events2Interface => this.events2Interface;
|
||||||
|
|
||||||
|
public IntPtr Events3Interface => this.events3Interface;
|
||||||
|
|
||||||
|
public int CurrentReferenceCount => Volatile.Read(ref this.referenceCount);
|
||||||
|
|
||||||
|
public int AddRefCallCount => Volatile.Read(ref this.addRefCallCount);
|
||||||
|
|
||||||
|
public int ReleaseCallCount => Volatile.Read(ref this.releaseCallCount);
|
||||||
|
|
||||||
|
public int Events2QueryCount => Volatile.Read(ref this.events2QueryCount);
|
||||||
|
|
||||||
|
public int Events3QueryCount => Volatile.Read(ref this.events3QueryCount);
|
||||||
|
|
||||||
|
public int SetFieldStringCallCount => Volatile.Read(ref this.setFieldStringCallCount);
|
||||||
|
|
||||||
|
public int BeginFieldUpdatesCallCount => Volatile.Read(ref this.beginFieldUpdatesCallCount);
|
||||||
|
|
||||||
|
public int EndFieldUpdatesCallCount => Volatile.Read(ref this.endFieldUpdatesCallCount);
|
||||||
|
|
||||||
|
public int SetFieldOptionsCallCount => Volatile.Read(ref this.setFieldOptionsCallCount);
|
||||||
|
|
||||||
|
public int SetFieldBitmapBufferCallCount => Volatile.Read(ref this.setFieldBitmapBufferCallCount);
|
||||||
|
|
||||||
|
public IntPtr LastCredential
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.lastCredential;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint LastFieldId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.lastFieldId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint LastUIntValue
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.lastUIntValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.lastString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] LastBitmapBuffer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.lastBitmapBuffer == null ? null : (byte[])this.lastBitmapBuffer.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BitmapBufferReturnValuePointerWasPresent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
return this.bitmapBufferReturnValuePointerWasPresent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref this.ownerReferenceReleased, 1) == 0)
|
||||||
|
{
|
||||||
|
this.Release(IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr CreateVtable(int slotCount)
|
||||||
|
{
|
||||||
|
IntPtr vtable = Marshal.AllocHGlobal(checked(slotCount * IntPtr.Size));
|
||||||
|
|
||||||
|
for (int slot = 0; slot < slotCount; slot++)
|
||||||
|
{
|
||||||
|
Marshal.WriteIntPtr(vtable, checked(slot * IntPtr.Size), IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.WriteMethod(vtable, 0, this.queryInterfaceDelegate);
|
||||||
|
this.WriteMethod(vtable, 1, this.addRefDelegate);
|
||||||
|
this.WriteMethod(vtable, 2, this.releaseDelegate);
|
||||||
|
this.WriteMethod(vtable, 3, this.fieldUIntStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 4, this.fieldUIntStubDelegate);
|
||||||
|
this.WriteMethod(vtable, RawCredentialEventAbi.SetFieldStringSlot, this.setFieldStringDelegate);
|
||||||
|
this.WriteMethod(vtable, 6, this.fieldCheckboxStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 7, this.fieldPointerStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 8, this.fieldUIntStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 9, this.fieldUIntStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 10, this.fieldPointerStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 11, this.fieldUIntStubDelegate);
|
||||||
|
this.WriteMethod(vtable, 12, this.onCreatingWindowDelegate);
|
||||||
|
|
||||||
|
if (slotCount >= RawCredentialEventAbi.Events2SlotCount)
|
||||||
|
{
|
||||||
|
this.WriteMethod(vtable, RawCredentialEventAbi.BeginFieldUpdatesSlot, this.beginFieldUpdatesDelegate);
|
||||||
|
this.WriteMethod(vtable, RawCredentialEventAbi.EndFieldUpdatesSlot, this.endFieldUpdatesDelegate);
|
||||||
|
this.WriteMethod(vtable, RawCredentialEventAbi.SetFieldOptionsSlot, this.setFieldOptionsDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slotCount >= RawCredentialEventAbi.Events3SlotCount)
|
||||||
|
{
|
||||||
|
this.WriteMethod(vtable, RawCredentialEventAbi.SetFieldBitmapBufferSlot, this.setFieldBitmapBufferDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vtable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr CreateInterface(IntPtr vtable)
|
||||||
|
{
|
||||||
|
IntPtr result = Marshal.AllocHGlobal(IntPtr.Size);
|
||||||
|
Marshal.WriteIntPtr(result, vtable);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteMethod(IntPtr vtable, int slot, Delegate method)
|
||||||
|
{
|
||||||
|
Marshal.WriteIntPtr(vtable, checked(slot * IntPtr.Size), Marshal.GetFunctionPointerForDelegate(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int QueryInterface(IntPtr instance, ref Guid interfaceId, out IntPtr interfacePointer)
|
||||||
|
{
|
||||||
|
interfacePointer = IntPtr.Zero;
|
||||||
|
|
||||||
|
if (interfaceId == RawCredentialEventAbi.IUnknown || interfaceId == RawCredentialEventAbi.ICredentialProviderCredentialEvents)
|
||||||
|
{
|
||||||
|
interfacePointer = this.events1Interface;
|
||||||
|
}
|
||||||
|
else if (interfaceId == RawCredentialEventAbi.ICredentialProviderCredentialEvents2)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.events2QueryCount);
|
||||||
|
if (this.maximumVersion >= 2)
|
||||||
|
{
|
||||||
|
interfacePointer = this.events2Interface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (interfaceId == RawCredentialEventAbi.ICredentialProviderCredentialEvents3)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.events3QueryCount);
|
||||||
|
if (this.maximumVersion >= 3)
|
||||||
|
{
|
||||||
|
interfacePointer = this.events3Interface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfacePointer == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return RawCredentialEventAbi.E_NOINTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.AddRef(instance);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint AddRef(IntPtr instance)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.addRefCallCount);
|
||||||
|
return checked((uint)Interlocked.Increment(ref this.referenceCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint Release(IntPtr instance)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.releaseCallCount);
|
||||||
|
int result = Interlocked.Decrement(ref this.referenceCount);
|
||||||
|
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
lock (RegistryLock)
|
||||||
|
{
|
||||||
|
Registry.Remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FreeNativeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return checked((uint)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SetFieldString(IntPtr instance, IntPtr credential, uint fieldId, IntPtr value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
this.lastCredential = credential;
|
||||||
|
this.lastFieldId = fieldId;
|
||||||
|
this.lastString = value == IntPtr.Zero ? null : Marshal.PtrToStringUni(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Increment(ref this.setFieldStringCallCount);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return CredentialProviderAbi.E_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int BeginFieldUpdates(IntPtr instance)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.beginFieldUpdatesCallCount);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int EndFieldUpdates(IntPtr instance)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref this.endFieldUpdatesCallCount);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SetFieldOptions(IntPtr instance, IntPtr credential, uint fieldId, uint value)
|
||||||
|
{
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
this.lastCredential = credential;
|
||||||
|
this.lastFieldId = fieldId;
|
||||||
|
this.lastUIntValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Increment(ref this.setFieldOptionsCallCount);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SetFieldBitmapBuffer(IntPtr instance, IntPtr credential, uint fieldId, uint imageBufferSize, IntPtr imageBuffer, IntPtr returnValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (returnValue == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return RawCredentialEventAbi.E_POINTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[checked((int)imageBufferSize)];
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
Marshal.Copy(imageBuffer, buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.WriteInt32(returnValue, CredentialProviderAbi.S_OK);
|
||||||
|
|
||||||
|
lock (this.observationLock)
|
||||||
|
{
|
||||||
|
this.lastCredential = credential;
|
||||||
|
this.lastFieldId = fieldId;
|
||||||
|
this.lastBitmapBuffer = buffer;
|
||||||
|
this.bitmapBufferReturnValuePointerWasPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Increment(ref this.setFieldBitmapBufferCallCount);
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return CredentialProviderAbi.E_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FieldUIntStub(IntPtr instance, IntPtr credential, uint fieldId, uint value)
|
||||||
|
{
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FieldPointerStub(IntPtr instance, IntPtr credential, uint fieldId, IntPtr value)
|
||||||
|
{
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FieldCheckboxStub(IntPtr instance, IntPtr credential, uint fieldId, int isChecked, IntPtr label)
|
||||||
|
{
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int OnCreatingWindow(IntPtr instance, out IntPtr ownerWindow)
|
||||||
|
{
|
||||||
|
ownerWindow = IntPtr.Zero;
|
||||||
|
return CredentialProviderAbi.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FreeNativeMemory()
|
||||||
|
{
|
||||||
|
this.FreeNativeMemory(ref this.events1Interface);
|
||||||
|
this.FreeNativeMemory(ref this.events2Interface);
|
||||||
|
this.FreeNativeMemory(ref this.events3Interface);
|
||||||
|
this.FreeNativeMemory(ref this.events1Vtable);
|
||||||
|
this.FreeNativeMemory(ref this.events2Vtable);
|
||||||
|
this.FreeNativeMemory(ref this.events3Vtable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FreeNativeMemory(ref IntPtr pointer)
|
||||||
|
{
|
||||||
|
if (pointer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(pointer);
|
||||||
|
pointer = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate int RawQueryInterfaceDelegate(IntPtr instance, ref Guid interfaceId, out IntPtr interfacePointer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests.ComInterop
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
internal delegate uint RawReferenceDelegate(IntPtr instance);
|
||||||
|
}
|
||||||
@@ -12,14 +12,17 @@ namespace Lithnet.CredentialProvider
|
|||||||
{
|
{
|
||||||
this.logger.LogTrace("Advise");
|
this.logger.LogTrace("Advise");
|
||||||
|
|
||||||
|
if (this.eventsUnknown != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
this.ReleaseCredentialEvents();
|
||||||
|
}
|
||||||
|
|
||||||
if (pcpce != null)
|
if (pcpce != null)
|
||||||
{
|
{
|
||||||
|
this.eventsUnknown = Marshal.GetIUnknownForObject(pcpce);
|
||||||
this.Controls.AssignEvents(pcpce);
|
this.Controls.AssignEvents(pcpce);
|
||||||
this.events = pcpce;
|
this.events = pcpce;
|
||||||
this.events2 = pcpce as ICredentialProviderCredentialEvents2;
|
this.events2 = pcpce as ICredentialProviderCredentialEvents2;
|
||||||
|
|
||||||
var intPtr = Marshal.GetIUnknownForObject(pcpce);
|
|
||||||
Marshal.AddRef(intPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.OnLoad();
|
this.OnLoad();
|
||||||
@@ -28,6 +31,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
this.ReleaseCredentialEvents();
|
||||||
this.logger.LogError(ex, "Advise failed");
|
this.logger.LogError(ex, "Advise failed");
|
||||||
return HRESULT.E_FAIL;
|
return HRESULT.E_FAIL;
|
||||||
}
|
}
|
||||||
@@ -39,12 +43,9 @@ namespace Lithnet.CredentialProvider
|
|||||||
{
|
{
|
||||||
this.logger.LogTrace("Unadvise");
|
this.logger.LogTrace("Unadvise");
|
||||||
|
|
||||||
if (this.events != null)
|
if (this.eventsUnknown != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
this.Controls.UnassignEvents();
|
this.ReleaseCredentialEvents();
|
||||||
var intPtr = Marshal.GetIUnknownForObject(this.events);
|
|
||||||
Marshal.Release(intPtr);
|
|
||||||
this.events = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.OnUnload();
|
this.OnUnload();
|
||||||
@@ -58,6 +59,26 @@ namespace Lithnet.CredentialProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReleaseCredentialEvents()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.Controls.UnassignEvents();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.events2 = null;
|
||||||
|
this.events = null;
|
||||||
|
|
||||||
|
if (this.eventsUnknown != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
IntPtr unknown = this.eventsUnknown;
|
||||||
|
this.eventsUnknown = IntPtr.Zero;
|
||||||
|
Marshal.Release(unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int ICredentialProviderCredential.SetSelected(out int pbAutoLogon)
|
int ICredentialProviderCredential.SetSelected(out int pbAutoLogon)
|
||||||
{
|
{
|
||||||
pbAutoLogon = 0;
|
pbAutoLogon = 0;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
public abstract partial class CredentialTile
|
public abstract partial class CredentialTile
|
||||||
{
|
{
|
||||||
private protected readonly ICredentialProviderLogger logger;
|
private protected readonly ICredentialProviderLogger logger;
|
||||||
|
private IntPtr eventsUnknown;
|
||||||
private protected ICredentialProviderCredentialEvents events;
|
private protected ICredentialProviderCredentialEvents events;
|
||||||
private protected ICredentialProviderCredentialEvents2 events2;
|
private protected ICredentialProviderCredentialEvents2 events2;
|
||||||
private protected ControlCollection controls;
|
private protected ControlCollection controls;
|
||||||
|
|||||||
Reference in New Issue
Block a user