Skip to content

Commit

Permalink
AnyCpu - Add ModuleInitializer to resolve AnyCpu dlls at runtime
Browse files Browse the repository at this point in the history
- Checks if the Assembly.GetEntryAssembly() is MSIL
- Change CefSharp.Core.csproj to use LangVersion 9 so we can get the compiler to output
  the ModuleInitializer
- Remove CefSharpPlatformCheck and other CefSharpAnyCpuSupport checks
- CefSharp.Common.targets still has CefSharpAnyCpuSupport references for backwards compatibility with those with it set and Prefer32bit
  The default behaviour for this case is to simply copy the 32bit libs
- Calling CefRuntime.SubscribeAnyCpuAssemblyResolver multiple times now will simply Unsubscribe the previous resolver

This change should remove the need for users to perform any action when supporting AnyCpu.
  • Loading branch information
amaitland committed Apr 13, 2022
1 parent 59f9608 commit 0849c5f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 27 deletions.
43 changes: 43 additions & 0 deletions CefSharp.Core/CefRuntimeAnyCpuInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright © 2022 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System.Reflection;
using System.Runtime.CompilerServices;

namespace CefSharp
{
/// <summary>
/// When targeting NETFRAMEWORK this Module Initializer
/// will call <see cref="CefRuntime.SubscribeAnyCpuAssemblyResolver(string)"/>
/// when the <see cref="Assembly.GetEntryAssembly"/> has a <see cref="ProcessorArchitecture.MSIL"/>.
/// </summary>
public static class CefRuntimeAnyCpuInitializer
{
/// <summary>
/// True if the AnyCpu resolver was attached via this Module Initializer
/// </summary>
public static bool AssemblyResolverAttached { get; private set; }

[ModuleInitializer]
internal static void ModuleInitializer()
{
var executingAssembly = Assembly.GetEntryAssembly();
//The GetEntryAssembly method can return null when a managed assembly has been loaded from an unmanaged application.
//https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getentryassembly
if (executingAssembly != null && CefRuntime.SubscribeToAnyCpuResolverInModuleInitializer)
{
var name = executingAssembly.GetName();
if (name.ProcessorArchitecture == ProcessorArchitecture.MSIL)
{
if (!CefRuntime.IsAnyCpuAssemblyResolverAttached)
{
AssemblyResolverAttached = true;

CefRuntime.SubscribeAnyCpuAssemblyResolver();
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions CefSharp.Core/CefSharp.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net452</TargetFramework>
<OutputType>Library</OutputType>
Expand All @@ -8,6 +8,7 @@
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\CefSharp.snk</AssemblyOriginatorKeyFile>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>9</LangVersion>
<!--
Stop MSBuild from appending TargetFramework to output path.
Remove if we use TargetFrameworks
Expand All @@ -34,7 +35,6 @@
<ItemGroup>
<Compile Remove="BrowserSettings.netcore.cs" />
<Compile Remove="Initializer.cs" />
<Compile Remove="ModuleInitializerAttribute.cs" />
<Compile Remove="PostData.netcore.cs" />
<Compile Remove="PostDataElement.netcore.cs" />
<Compile Remove="Request.netcore.cs" />
Expand Down
40 changes: 31 additions & 9 deletions CefSharp/CefRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,27 @@ namespace CefSharp
/// </summary>
public static class CefRuntime
{
private static ResolveEventHandler currentDomainAssemblyResolveHandler;
private static ResolveEventHandler CurrentDomainAssemblyResolveHandler;

/// <summary>
/// For AnyCPU support a ModileInitializer in CefSharp.Core.dll will run when the dll is
/// loaded and call <see cref="SubscribeAnyCpuAssemblyResolver(string)"/> if tge dll is <see cref="ProcessorArchitecture.MSIL"/>
/// Set this to false before attempting to load any other CefSharp resources to avoid attaching the resolver.
/// Alternatively call <see cref="UnsubscribeAnyCpuAssemblyResolver"/> to remove the resolver.
/// </summary>
public static bool SubscribeToAnyCpuResolverInModuleInitializer { get; set; } = true;

/// <summary>
/// Check if an AnyCpu resolver has already been attached (subscribed to the
/// <see cref="AppDomain.AssemblyResolve"/> event for the <see cref="AppDomain.CurrentDomain"/>
/// </summary>
public static bool IsAnyCpuAssemblyResolverAttached
{
get
{
return CurrentDomainAssemblyResolveHandler != null;
}
}

/// <summary>
/// When using AnyCPU the architecture specific version of CefSharp.Core.Runtime.dll
Expand All @@ -24,25 +44,27 @@ public static class CefRuntime
/// based on <see cref="Environment.Is64BitProcess"/>.
/// This method MUST be called before you call Cef.Initialize, create your first ChromiumWebBrowser instance, basically
/// before anything CefSharp related happens. This method is part of CefSharp.dll which is an AnyCPU library and
/// doesn't have any references to the CefSharp.Core.Runtime.dll so it's safe to use.
/// doesn't have any references to the CefSharp.Core.Runtime.dll so it's safe to use. Multiple calls will
/// result in <see cref="UnsubscribeAnyCpuAssemblyResolver"/> being called if <see cref="IsAnyCpuAssemblyResolverAttached"/>
/// is true.
/// </summary>
/// <param name="basePath">
/// The path containing the x64/x86 folders which contain the CefSharp/CEF resources.
/// If null then AppDomain.CurrentDomain.SetupInformation.ApplicationBase will be used as the path.
/// (</param>
/// </param>
public static void SubscribeAnyCpuAssemblyResolver(string basePath = null)
{
if(currentDomainAssemblyResolveHandler != null)
if(IsAnyCpuAssemblyResolverAttached)
{
throw new Exception("UseAnyCpuAssemblyResolver has already been called, call ");
UnsubscribeAnyCpuAssemblyResolver();
}

if(basePath == null)
{
basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
}

currentDomainAssemblyResolveHandler = (sender, args) =>
CurrentDomainAssemblyResolveHandler = (sender, args) =>
{
if (args.Name.StartsWith("CefSharp.Core.Runtime"))
{
Expand All @@ -59,7 +81,7 @@ public static void SubscribeAnyCpuAssemblyResolver(string basePath = null)
return null;
};

AppDomain.CurrentDomain.AssemblyResolve += currentDomainAssemblyResolveHandler;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolveHandler;
}

/// <summary>
Expand All @@ -68,9 +90,9 @@ public static void SubscribeAnyCpuAssemblyResolver(string basePath = null)
/// </summary>
public static void UnsubscribeAnyCpuAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve -= currentDomainAssemblyResolveHandler;
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainAssemblyResolveHandler;

currentDomainAssemblyResolveHandler = null;
CurrentDomainAssemblyResolveHandler = null;
}

/// <summary>
Expand Down
19 changes: 3 additions & 16 deletions NuGet/CefSharp.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,6 @@
https://github.com/dotnet/project-system/issues/4158
-->
<CefSharpPropertiesLoaded>true</CefSharpPropertiesLoaded>

<!--
For PackageReference library projects where buildTransitive is supported we'll default to setting CefSharpAnyCpuSupport to true
This should only require WinExe and Exe projects to specify CefSharpAnyCpuSupport rather than every project in a solution.
Only for PackageReference projects where NuGetToolVersion > 5.0 and OutputType is library.
Defaulting CefSharpAnyCpuSupport to true is simpler than modifying the already complex Condition for the CefSharpPlatformCheck
target below.
https://github.com/cefsharp/CefSharp/issues/3622
-->
<CefSharpAnyCpuSupport Condition="'$(CefSharpAnyCpuSupport)' != 'true' AND '$(NuGetProjectStyle)' == 'PackageReference' AND $(NuGetToolVersion) > '5.0' AND '$(OutputType)' == 'Library'">true</CefSharpAnyCpuSupport>
</PropertyGroup>

<Choose>
Expand Down Expand Up @@ -359,20 +349,17 @@
</Choose>

<!--
This Transform is no longer used by default, the project was changed to use a ModuleInitializer to resolve the references
at runtime. The transform remains here for anyone wishing to manually call
For AnyCPU we use a Transform to add entries to app.config if possible
Otherwise throw error to alert user they need to perform additional action
-->
<UsingTask TaskName="TransformXml" AssemblyFile="$(CefSharpTransformXmlDllPath)" Condition="Exists('$(CefSharpTransformXmlDllPath)') AND '$(CefSharpPlatformTarget)' == 'AnyCPU' AND '$(CefSharpAnyCpuSupport)' == '' AND '$(CefSharpBuildAction)' != 'NoAction'" />

<Target Name="CefSharpCommonAnyCPUConfigTransform" AfterTargets="_CopyAppConfigFile" Condition="'@(AppConfigWithTargetPath)' != '' AND Exists('$(CefSharpTransformXmlDllPath)') AND '$(CefSharpPlatformTarget)' == 'AnyCPU' AND '$(CefSharpAnyCpuSupport)' == '' AND '$(CefSharpBuildAction)' != 'NoAction'">
<Target Name="CefSharpCommonAnyCPUConfigTransform">
<TransformXml Source="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" Transform="$(MSBuildThisFileDirectory)..\build\app.config.x86.transform" Destination="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')"/>
<TransformXml Source="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" Transform="$(MSBuildThisFileDirectory)..\build\app.config.x64.transform" Destination="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')"/>
</Target>

<Target Name="CefSharpPlatformCheck" BeforeTargets="ResolveAssemblyReferences" Condition="('@(AppConfigWithTargetPath)' == '' OR !Exists('$(CefSharpTransformXmlDllPath)')) AND '$(CefSharpPlatformTarget)' == 'AnyCPU' AND '$(CefSharpAnyCpuSupport)' != 'true' AND '$(CefSharpBuildAction)' != 'NoAction'">
<Error Text="$(MSBuildThisFileName) is unable to proceeed as your current PlatformTarget is '$(CefSharpPlatformTarget)'. To target AnyCPU please read https://github.com/cefsharp/CefSharp/issues/1714. Alternatively change your PlatformTarget to x86 or x64 and the relevant files will be copied automatically." HelpKeyword="CefSharpSolutionPlatformCheck" />
</Target>

<!--
Issue https://github.com/dotnet/project-system/issues/4158
The None/Content entries aren't picked up as the .targets file doesn't exist before the Nuget restore (only when using packages.config)
Expand Down

0 comments on commit 0849c5f

Please sign in to comment.