Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move ActivationFactory into WinRT.Runtime.dll #1390

Merged
merged 5 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 289 additions & 0 deletions src/WinRT.Runtime/ActivationFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using WinRT.Interop;

namespace WinRT
{
internal unsafe sealed class DllModule
{
private static readonly string _currentModuleDirectory = AppContext.BaseDirectory;

private static readonly Dictionary<string, DllModule> _cache = new Dictionary<string, DllModule>(StringComparer.Ordinal);

private readonly string _fileName;
jlaanstra marked this conversation as resolved.
Show resolved Hide resolved
private readonly IntPtr _moduleHandle;
private readonly delegate* unmanaged[Stdcall]<IntPtr, IntPtr*, int> _GetActivationFactory;
private readonly delegate* unmanaged[Stdcall]<int> _CanUnloadNow; // TODO: Eventually periodically call

public static bool TryLoad(string fileName, out DllModule module)
{
lock (_cache)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is now more likely that we see parallel calls here due to different threads activating different types. Given these are already cached too, it shouldn't have too much of an impact, but will need to monitor perf traces to see if this shows up and if we need to improve the locking here. But for now, this is fine.

{
if (_cache.TryGetValue(fileName, out module))
{
return true;
}
else if (TryCreate(fileName, out module))
{
_cache[fileName] = module;
return true;
}
return false;
}
}

private static bool TryCreate(string fileName, out DllModule module)
{
// Explicitly look for module in the same directory as this one, and
// use altered search path to ensure any dependencies in the same directory are found.
IntPtr moduleHandle = IntPtr.Zero;
moduleHandle = Platform.LoadLibraryExW(System.IO.Path.Combine(_currentModuleDirectory, fileName), IntPtr.Zero, /* LOAD_WITH_ALTERED_SEARCH_PATH */ 8);
#if NET
if (moduleHandle == IntPtr.Zero)
{
NativeLibrary.TryLoad(fileName, Assembly.GetExecutingAssembly(), null, out moduleHandle);
}
#endif
if (moduleHandle == IntPtr.Zero)
{
module = null;
return false;
}

void* getActivationFactory = null;

#if NET7_0_OR_GREATER || CsWinRT_LANG_11_FEATURES
ReadOnlySpan<byte> functionName = "DllGetActivationFactory"u8;
#else
string functionName = "DllGetActivationFactory";
#endif
getActivationFactory = Platform.TryGetProcAddress(moduleHandle, functionName);
if (getActivationFactory == null)
{
module = null;
return false;
}

module = new DllModule(
fileName,
moduleHandle,
getActivationFactory);
return true;
}

private DllModule(string fileName, IntPtr moduleHandle, void* getActivationFactory)
{
_fileName = fileName;
_moduleHandle = moduleHandle;
_GetActivationFactory = (delegate* unmanaged[Stdcall]<IntPtr, IntPtr*, int>)getActivationFactory;

void* canUnloadNow = null;
#if NET7_0_OR_GREATER || CsWinRT_LANG_11_FEATURES
ReadOnlySpan<byte> functionName = "DllCanUnloadNow"u8;
#else
string functionName = "DllCanUnloadNow";
#endif
canUnloadNow = Platform.TryGetProcAddress(_moduleHandle, functionName);

if (canUnloadNow != null)
{
_CanUnloadNow = (delegate* unmanaged[Stdcall]<int>)canUnloadNow;
}
}

public (ObjectReference<IActivationFactoryVftbl> obj, int hr) GetActivationFactory(string runtimeClassId)
{
IntPtr instancePtr = IntPtr.Zero;
try
{
MarshalString.Pinnable __runtimeClassId = new(runtimeClassId);
fixed (void* ___runtimeClassId = __runtimeClassId)
{
int hr = _GetActivationFactory(MarshalString.GetAbi(ref __runtimeClassId), &instancePtr);
if (hr == 0)
{
var objRef = ObjectReference<IActivationFactoryVftbl>.Attach(ref instancePtr);
return (objRef, hr);
}
else
{
return (null, hr);
}
}
}
finally
{
MarshalInspectable<object>.DisposeAbi(instancePtr);
}
}

~DllModule()
{
System.Diagnostics.Debug.Assert(_CanUnloadNow == null || _CanUnloadNow() == 0); // S_OK
lock (_cache)
{
_cache.Remove(_fileName);
jlaanstra marked this conversation as resolved.
Show resolved Hide resolved
}
if ((_moduleHandle != IntPtr.Zero) && !Platform.FreeLibrary(_moduleHandle))
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
}

internal sealed class WinRTModule
{
private static volatile WinRTModule _instance;
private readonly IntPtr _mtaCookie;
private static WinRTModule MakeWinRTModule()
{
global::System.Threading.Interlocked.CompareExchange(ref _instance, new WinRTModule(), null);
return _instance;
}
public static WinRTModule Instance => _instance ?? MakeWinRTModule();

public unsafe WinRTModule()
{
IntPtr mtaCookie;
Marshal.ThrowExceptionForHR(Platform.CoIncrementMTAUsage(&mtaCookie));
_mtaCookie = mtaCookie;
}

public static unsafe (ObjectReference<I> obj, int hr) GetActivationFactory<
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
#if NET
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors)]
#endif
I>(string runtimeClassId, Guid iid)
{
var module = Instance; // Ensure COM is initialized
IntPtr instancePtr = IntPtr.Zero;
try
{
MarshalString.Pinnable __runtimeClassId = new(runtimeClassId);
fixed (void* ___runtimeClassId = __runtimeClassId)
{
int hr = Platform.RoGetActivationFactory(MarshalString.GetAbi(ref __runtimeClassId), &iid, &instancePtr);
if (hr == 0)
{
var objRef = ObjectReference<I>.Attach(ref instancePtr);
return (objRef, hr);
}
else
{
return (null, hr);
}
}
}
finally
{
MarshalInspectable<object>.DisposeAbi(instancePtr);
}
}

~WinRTModule()
{
Marshal.ThrowExceptionForHR(Platform.CoDecrementMTAUsage(_mtaCookie));
}
}

#if EMBED
internal
jlaanstra marked this conversation as resolved.
Show resolved Hide resolved
#else
public
#endif
static class ActivationFactory
{
public static IObjectReference Get(string typeName)
{
// Prefer the RoGetActivationFactory HRESULT failure over the LoadLibrary/etc. failure
int hr;
ObjectReference<IActivationFactoryVftbl> factory;
(factory, hr) = WinRTModule.GetActivationFactory<IActivationFactoryVftbl>(typeName, InterfaceIIDs.IActivationFactory_IID);
if (factory != null)
{
return factory;
}

var moduleName = typeName;
while (true)
{
var lastSegment = moduleName.LastIndexOf(".", StringComparison.Ordinal);
if (lastSegment <= 0)
{
Marshal.ThrowExceptionForHR(hr);
}
moduleName = moduleName.Remove(lastSegment);

DllModule module = null;
if (DllModule.TryLoad(moduleName + ".dll", out module))
{
(factory, hr) = module.GetActivationFactory(typeName);
if (factory != null)
{
return factory;
}
}
}
}

#if NET
public static IObjectReference Get(string typeName, Guid iid)
#else
public static ObjectReference<I> Get<I>(string typeName, Guid iid)
jlaanstra marked this conversation as resolved.
Show resolved Hide resolved
#endif
{
// Prefer the RoGetActivationFactory HRESULT failure over the LoadLibrary/etc. failure
int hr;
#if NET
ObjectReference<IUnknownVftbl> factory;
(factory, hr) = WinRTModule.GetActivationFactory<IUnknownVftbl>(typeName, iid);
if (factory != null)
{
return factory;
}
#else
ObjectReference<I> factory;
(factory, hr) = WinRTModule.GetActivationFactory<I>(typeName, iid);
if (factory != null)
{
return factory;
}
#endif

var moduleName = typeName;
while (true)
{
var lastSegment = moduleName.LastIndexOf(".", StringComparison.Ordinal);
if (lastSegment <= 0)
{
Marshal.ThrowExceptionForHR(hr);
}
moduleName = moduleName.Remove(lastSegment);

DllModule module = null;
if (DllModule.TryLoad(moduleName + ".dll", out module))
{
ObjectReference<IActivationFactoryVftbl> activationFactory;
(activationFactory, hr) = module.GetActivationFactory(typeName);
if (activationFactory != null)
{
using (activationFactory)
{
#if NET
return activationFactory.As<IUnknownVftbl>(iid);
#else
return activationFactory.As<I>(iid);
#endif
}
}
}
}
}
}
}
12 changes: 11 additions & 1 deletion src/WinRT.Runtime/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@

namespace WinRT
{
static partial class Context
internal static partial class Context
{
[DllImport("api-ms-win-core-com-l1-1-0.dll")]
private static extern unsafe int CoGetContextToken(IntPtr* contextToken);

public unsafe static IntPtr GetContextToken()
{
IntPtr contextToken;
Marshal.ThrowExceptionForHR(CoGetContextToken(&contextToken));
return contextToken;
}

[DllImport("api-ms-win-core-com-l1-1-0.dll")]
private static extern unsafe int CoGetObjectContext(Guid* riid, IntPtr* ppv);

Expand Down
3 changes: 2 additions & 1 deletion src/WinRT.Runtime/MatchingRefApiCompatBaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,5 @@ MembersMustExist : Member 'public System.Boolean WinRT.IObjectReference.IsInCurr
MembersMustExist : Member 'public WinRT.ObjectReference<T> WinRT.ObjectReference<T>.Attach(System.IntPtr, System.Guid)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public WinRT.ObjectReference<T> WinRT.ObjectReference<T>.FromAbi(System.IntPtr, System.Guid)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public WinRT.ObjectReference<T> WinRT.ObjectReference<T>.FromAbi(System.IntPtr, T, System.Guid)' does not exist in the reference but it does exist in the implementation.
Total Issues: 137
TypesMustExist : Type 'WinRT.ActivationFactory' does not exist in the reference but it does exist in the implementation.
Total Issues: 138
Loading