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

Feature Request - Add .Net Core 3.1 Support #2796

Closed
3 tasks done
amaitland opened this issue May 31, 2019 · 61 comments
Closed
3 tasks done

Feature Request - Add .Net Core 3.1 Support #2796

amaitland opened this issue May 31, 2019 · 61 comments
Assignees
Milestone

Comments

@amaitland
Copy link
Member

amaitland commented May 31, 2019

With .Net Core 3.0 adding support for WPF and WinForms I'm seeing more and more queries about running CefSharp using .Net Core 3.0. This issue will track the official progress, there is no timeframe yet, please don't ask when this will be implemented, .Net Core isn't even officially out yet. As per https://github.com/dotnet/core/blob/master/roadmap.md#upcoming-ship-dates the release is still some months away.

Microsoft has started implementing C++/CLI as part of .Net Core you can track the issue https://github.com/dotnet/coreclr/issues/18013 They are only adding support for Windows initially. See also https://github.com/dotnet/coreclr/issues/659

.Net Core will support mixed mode assemblies on Windows Only.

https://devblogs.microsoft.com/dotnet/net-core-3-and-support-for-windows-desktop-applications/

The sync JavaScript Binding implementation will not work as Microsoft is not supporting WCF in .Net Core 3.0.

@amaitland amaitland pinned this issue May 31, 2019
This was referenced May 31, 2019
@amaitland
Copy link
Member Author

With #2767 it may be possible to load the next CefSharp release (75 at time of writing) with .Net Core 3.0 referencing the current .Net 4.5.2 framework assemblies. I have no personally tested this personally yet. You will have to avoid using the sync JavaScript Binding as it relies on WCF.

@michalczerwinski

This comment has been minimized.

@kpreisser
Copy link
Contributor

kpreisser commented Jun 26, 2019

Hi,
first, many thanks for this great project!

I was able to successfully use CefSharp.WinForms 75.0.110-CI3174 on a .NET Core 3.0 (Preview 5) WinForms application (form title in the screenshot contains process filename and RuntimeInformation.FrameworkDescription when running with dotnet.exe):
cefsharp-windows

I also tested that the IDownloadHandler was working successfully.

However, directly adding the NuGet package to the .NET Core project did not work as it didn't resolve the dependencies (probably because they are only declared for .NET Framework 4.5.2).

Instead, I first added it to a .NET Framework project and built it (so all the necessary files would be copied into the bin folder), and then in the .NET Core 3.0 project I manually added references to the CefSharp.dll, CefSharp.Core.dll and CefSharp.WinForms.dll assemblies. Additionally, I copied all other files from the bin folder of the .NET Framework app to the bin folder of the .NET Core app.

Another thing to consider is the browser subprocess (CefSharp.BrowserSubprocess.exe) which is built for .NET Framework 4.5.2, meaning that you still would need to have .NET Framework 4.5.2 or higher installed to run the .NET Core 3.0 app.

In order to avoid a dependency on .NET Framework and only use .NET Core, I think you will probably have to provide your own browser subprocess, implemented as a .NET Core app (using the same .NET Core version as your main app so that it can share runtime files when publishing a self-contained app).
(I haven't tested this yet.)

Thank you!

@campersau
Copy link
Contributor

You can try setting <PackageTargetFallback>net452</PackageTargetFallback> in your project file which might restore the nuget package correctly.
https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#packagetargetfallback

@kpreisser
Copy link
Contributor

kpreisser commented Jun 27, 2019

Hi @campersau,

You can try setting <PackageTargetFallback>net452</PackageTargetFallback> in your project file which might restore the nuget package correctly.

Thanks for your suggestion! Unfortunately, it did not seem to have an effect when I added that setting to the project file - it still only added CefSharp.WinForms.dll but none of the dependencies. I guess the NuGet packages would need to have an additional .NET Core 3.0 target for this to work.


I also had a look at the browser subprocess, and I was also able to successfully provide the subprocess as .NET Core 3.0 app, to avoid a dependency on the .NET Framework 4.5.2.
However, actually using a different project for the subprocess might be a bit cumbersome with .NET Core because the build results (binaries) might different for a normal (framework-dependent) app (e.g. when debugging in Visual Studio) compared to a published (self-contained) app etc., and you would need to copy the correct subprocess binaries near your app binaries each time.

I think the easiest way to use CEFSharp including the browser subprocess in .NET Core 3.0 might be to use the same executable for both your regular WinForms app and the browser subprocess.

To test this, I added a reference to CefSharp.BrowserSubprocess.Core.dll to my .NET Core 3.0 WinForms project, set CefSettings.BrowserSubprocessPath = Process.GetCurrentProcess().MainModule.FileName, and then used a Main() method like this (as I noticed the subprocess will be called with a --type=.... argument):

    [STAThread]
    static int Main(string[] args)
    {
        if (args.Length > 0 && args[0].StartsWith(
            CefSharpArguments.SubProcessTypeArgument + "=", StringComparison.Ordinal))
        {
            // Run the CEFSharp browser subprocess.
            return RunBrowserSubprocess(args);
        }
        else
        {
            // Run the regular application.
            RunGui();
            return 0;
        }
    }

Here, RunBrowserSubprocess() would contain the code from the Main() method of CefSharp.BrowserSubprocess, and RunGui() would do the regular Application.Run(new Form1()).
(The code of RunBrowserSubprocess() could also be implemented as utiltiy method in the .NET Core 3.0 package of CefSharp.)

This means you won't have to bother with building and copying the correct subprocess executable before running your main application.
(A minor disadvantage of this is that you cannot implement the command line argument --type= for your own use.)

What do you think?
Thanks!

@amaitland
Copy link
Member Author

@kpreisser
Copy link
Contributor

kpreisser commented Aug 25, 2019

Regarding the subprocess handling for .NET Core, my idea was the following:

  • Switch project CefSharp.BrowserSubprocess to the .NET Core SDK and multi-target for net452 and netcoreapp3.0, so that for net452 the result is a EXE (like today) and for netcoreapp3.0 the result is a DLL, e.g.:
  <PropertyGroup>
    <TargetFrameworks>net452;netcoreapp3.0</TargetFrameworks>
    ...
  </PropertyGroup>

  <PropertyGroup Condition="'$(TargetFramework)'!='netcoreapp3.0'">
    <OutputType>WinExe</OutputType>
    <ApplicationManifest>app.manifest</ApplicationManifest>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>
  • Make the Program.Main method internal and add a public method like the following that is only compiled for .NET Core:
#if NETCOREAPP
    public static class CefSharpBrowserSubprocess
    {
        public static void HandleSubprocess()
        {
            // Check if the first argument starts with "--type=", in which case
            // we run the subprocess logic. Otherwise, we simply return and allow
            // the app run its own logic.
            var args = Environment.GetCommandLineArgs().Skip(1);
            if (args.HasArgument(CefSharpArguments.SubProcessTypeArgument + "="))
            {
                // Run the subprocess.
                int exitCode = Program.Main(args.ToArray());

                // Exit directly so that the remaining application logic is not run.
                Environment.Exit(exitCode);
            }
        }
    }
#endif
  • Adjust the NuGet files so that for projects targeting .NET Core, a reference to CefSharp.BrowserSubprocess.dll is added.
  • In CefSharp.Core, adjust CefSettings.BrowserSubprocessPath to specify "CefSharp.BrowserSubprocess.exe" when running on .NET Framework, and specify the current executable path when running on .NET Core (this can be done with a runtime check, e.g. using RuntimeInformation.FrameworkDescription; but that would require compiling for .NET Framework 4.7.1 or higher).

That way, a user could modify the Main method in a WinForms project like this when adding CefSharp:

         [STAThread]
         static void Main()
         {
             Application.SetHighDpiMode(HighDpiMode.SystemAware);
+            
+            // Handle the CefSharp browser subprocess logic on start-up.
+            CefSharpBrowserSubprocess.HandleSubprocess();
+            
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new Form1());
         }

In this case, the application executable would also run the browser subprocess logic, and a separate EXE is no longer needed.

With that change, I think the only other thing needed is to adjust the NuGet spec files to declare dependencies to .NETCoreApp3.0 so the dependencies will resolve when compiling for .NET Core.
(Note: The logic that currently allows to compile .NET Framework projects for AnyCPU might no longer work on .NET Core, but that shouldn't be a big issue because you will probably publish a self-contained application which is platform-specific.)


On a related note, I think using the same executable for the app and the browser subprocess will also solve a rendering issue that we experienced with a WPF app using CefSharp.WinForms: When moving the window between monitors with different DPI settings, the browser control starts to display some contents in an incorrect size when moving the mouse, like this:
grafik

This seems to occur because the subprocess executable (CefSharp.BrowserSubprocess.exe) declares <dpiAware>true/PM</dpiAware> (per-monitor DPI aware V1), whereas in our case the application only supports <dpiAware>true</dpiAware> (System DPI aware).

Whereas when using the same executable, both processes will use the same DPI settings, so this issue will no longer occur there.

What do you think?

Thanks!

@amaitland
Copy link
Member Author

@kpreisser Thanks for the very detailed summaries, some very useful insights 👍

  • Switch project CefSharp.BrowserSubprocess to the .NET Core SDK and multi-target for net452 and netcoreapp3.0, so that for net452 the result is a EXE (like today) and for netcoreapp3.0 the result is a DLL, e.g.:

For simplicity we'll just rewrite the Main method in VC++ and move the code into CefSharp.BrowserSubprocess.Core

  • Make the Program.Main method internal and add a public method like the following that is only compiled for .NET Core:

Will keep the changes to CefSharp.BrowserSubprocess at a minimum, replace the Main body with a simple call to a method in CefSharp.BrowserSubprocess.Core that codes the actual work, this can be reused. Will need some fairly descriptive comments.

  • Adjust the NuGet files so that for projects targeting .NET Core, a reference to CefSharp.BrowserSubprocess.dll is added.

My initial plan is to create a new CefSharp.DotNetCore Nuget package, it'll depend on CefSharp.Common and add the required include for CefSharp.BrowserSubprocess.Core.dll, only minor changes will need to be made to the Nuget packages to support this.

  • In CefSharp.Core, adjust CefSettings.BrowserSubprocessPath to specify "CefSharp.BrowserSubprocess.exe" when running on .NET Framework, and specify the current executable path when running on .NET Core (this can be done with a runtime check, e.g. using RuntimeInformation.FrameworkDescription; but that would require compiling for .NET Framework 4.7.1 or higher).

Some sort of runtime check would be idea, will have to investigate alternatives as .Net 4.5.2 is our current target (I still get people asking to target .Net 4.0). Alternatively we'll provide some sort of helper class in the new DotNetCore specific Nuget package.

With that change, I think the only other thing needed is to adjust the NuGet spec files to declare dependencies to .NETCoreApp3.0 so the dependencies will resolve when compiling for .NET Core.

What does declare dependencies to .NETCoreApp3.0 actually get us? As it stands I don't see any great benefit in compiling an actual .Net Core specific set of dlls as the current ones appear to load correctly. If we can work out a runtime check then we'll add that to any calls that would require WCF so that users get an exception.

This seems to occur because the subprocess executable (CefSharp.BrowserSubprocess.exe) declares <dpiAware>true/PM</dpiAware> (per-monitor DPI aware V1), whereas in our case the application only supports <dpiAware>true</dpiAware> (System DPI aware).

It's hard to please everyone, the general idea is to provide a sensible set of defaults and let you customise as required. I rewrote the entire CefSharp.BrowserSubprocess.exe quite some time ago so it's just a single file, makes it easy to implement your own when the need arises.

@kpreisser
Copy link
Contributor

kpreisser commented Aug 26, 2019

Hi @amaitland,

thanks for your reply!

For simplicity we'll just rewrite the Main method in VC++ and move the code into CefSharp.BrowserSubprocess.Core

Will keep the changes to CefSharp.BrowserSubprocess at a minimum, replace the Main body with a simple call to a method in CefSharp.BrowserSubprocess.Core that codes the actual work, this can be reused. Will need some fairly descriptive comments.

Agreed, that way no change to the project file for CefSharp.BrowserSubprocess is needed - the .NET Core project can then reference CefSharp.BrowserSubprocess.Core.dll to call that method.

What does declare dependencies to .NETCoreApp3.0 actually get us? As it stands I don't see any great benefit in compiling an actual .Net Core specific set of dlls as the current ones appear to load correctly. If we can work out a runtime check then we'll add that to any calls that would require WCF so that users get an exception.

Sorry, actually here I only meant that the .nuspec file probably needs a <group targetFramework=".NETCoreApp3.0"> element in addition to <group targetFramework=".NETFramework4.5.2">, so that the transitive dependencies to the other NuGet packages will be resolved correctly for .NET Core projects. The projects itself (CefSharp.WinForms etc.) don't need to be changed, because like you said the binaries for .NET Framework 4.5.2 already work on .NET Core 3.0.

For example, right now if I create a new .NET Core 3.0 project and add <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />, then the project will have a reference to CefSharp.WinForms, but is missing references to CefSharp.Common, CefSharp etc. and the redist files. Instead I currently have to manually add packages for all references:

  <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />
  <PackageReference Include="CefSharp.Common" Version="75.1.141" />
  <PackageReference Include="cef.redist.x64" Version="75.1.14" />
  <PackageReference Include="cef.redist.x86" Version="75.1.14" />

However, this also does not yet work in the .NET Core 3.0 project because at runtime it will not find CefSharp.WinForms.dll even though the file exists. I think this is because the reference is declared private in CefSharp.WinForms.props:

      <ItemGroup>
        <Reference Include="CefSharp.WinForms">
          <HintPath>$(MSBuildThisFileDirectory)..\CefSharp\x64\CefSharp.WinForms.dll</HintPath>
          <Private>False</Private>
        </Reference>
      </ItemGroup>

This seems to have the effect that the dependency is not specified in the .deps.json file when building the project, so that the .NET Core CLR doesn't load the assembly. It should work when using <Private>True</Private> (but I guess this change would mean that the support for AnyCPU would no longer work; but as said I think we can ignore that for .NET Core projects).


To summarize, CefSharp.WinForms already works today on a .NET Core 3.0 WinForms project (created with dotnet new winforms) when using a project file like the following, and setting the <Platform> explicitely to x64 or x86 depending on the used .NET Core runtime – provided that .NET Framework 4.5.2 or higher is installed on the machine as that is currently used by CefSharp.BrowserSubprocess.exe:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <RootNamespace>MyNetCoreApp</RootNamespace>
    <UseWindowsForms>true</UseWindowsForms>
    <Platforms>x86;x64</Platforms>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />
    <PackageReference Include="CefSharp.Common" Version="75.1.141" />
    <PackageReference Include="cef.redist.x64" Version="75.1.14" />
    <PackageReference Include="cef.redist.x86" Version="75.1.14" />
  </ItemGroup>

  <ItemGroup>
    <Reference Update="CefSharp">
      <Private>true</Private>
    </Reference>
    <Reference Update="CefSharp.Core">
      <Private>true</Private>
    </Reference>
    <Reference Update="CefSharp.WinForms">
      <Private>true</Private>
    </Reference>
  </ItemGroup>
</Project>

Thanks!

@kpreisser
Copy link
Contributor

Additionally, I found that the condition in CefSharp.WinForms.targets currently checks for the Platform Variable like this:

<When Condition="'$(Platform)' == 'x64'">

Maybe this needs to be changed to use PlatformTarget instead of Platform, because e.g. when you publish a self-contained .NET Core app with dotnet publish -f netcoreapp3.0 -r win-x64, the PlatformTarget will be x64, but Platform will still be AnyCPU; thus the check will currently fail.

Thanks!

@amaitland
Copy link
Member Author

For example, right now if I create a new .NET Core 3.0 project and add <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />, then the project will have a reference to CefSharp.WinForms, but is missing references to CefSharp.Common, CefSharp etc. and the redist files. Instead I currently have to manually add packages for all references:

It was my understanding (and that's a limited understanding at this point) that transitive dependencies meant that only top level references were included?

This seems to have the effect that the dependency is not specified in the .deps.json file when building the project, so that the .NET Core CLR doesn't load the assembly. It should work when using <Private>True</Private> (but I guess this change would mean that the support for AnyCPU would no longer work; but as said I think we can ignore that for .NET Core projects).

The files should be copied via the .targets file. What you are seeing may actually be the same as #2642

Additionally, I found that the condition in CefSharp.WinForms.targets currently checks for the Platform Variable like this:

The current packages require that you set the solution target, this is a current limitation.

Maybe this needs to be changed to use PlatformTarget instead of Platform, because e.g. when you publish a self-contained .NET Core app with dotnet publish -f netcoreapp3.0 -r win-x64, the PlatformTarget will be x64, but Platform will still be AnyCPU; thus the check will currently fail.

Last I checked this isn't possible, PlatformTarget is defined after the .props files are included, so it's not actually set early enough to be useful.

If you have the time to contribute a .Net Core example to https://github.com/cefsharp/CefSharp.MinimalExample that would make debugging/testing much easier.

@kpreisser
Copy link
Contributor

kpreisser commented Aug 28, 2019

Hi @amaitland,

For example, right now if I create a new .NET Core 3.0 project and add <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />, then the project will have a reference to CefSharp.WinForms, but is missing references to CefSharp.Common, CefSharp etc. and the redist files. Instead I currently have to manually add packages for all references:

It was my understanding (and that's a limited understanding at this point) that transitive dependencies meant that only top level references were included?

Sorry, I'm not sure if I fully understood what you say here. With 'transitive reference', I meant that if project MyWinFormsApp references CefSharp.WinForms, and CefSharp.WinForms references CefSharp.Common, then MyWinFormsApp will also get a (transitive) reference to CefSharp.Common.

I don't have detailed knowledge about how NuGet packages work, but I think that when the .nuspec file of CefSharp.WinForms would be changed to contain the following:

    <dependencies>
      <group targetFramework=".NETFramework4.5.2">
        <dependency id="CefSharp.Common" version="[75.1.141]" />
      </group>
      <group targetFramework=".NETCoreApp3.0">
        <dependency id="CefSharp.Common" version="[75.1.141]" />
      </group>
    </dependencies>

then CefSharp.Common should be correctly included when the project only contains <PackageReference Include="CefSharp.WinForms" Version="75.1.141" />. VS will then display the package references as follows:
grafik

Currently, when using a .NET Core 3.0 project I also have to add <PackageReference Include="CefSharp.Common" Version="75.1.141" />, and the VS will display the packages like this:
grafik

The files should be copied via the .targets file. What you are seeing may actually be the same as #2642

Actually, the files are copied correctly into the output directory, but because the <Reference> element contains <Private>false</Private>, the references to these DLLs will not be specified in the <exe>.deps.json file when compiling the project, which means the CoreCLR will not load them (as AFAIK it will only load libraries that are specified in that JSON file).

Last I checked this isn't possible, PlatformTarget is defined after the .props files are included, so it's not actually set early enough to be useful.

OK, thanks. It might be worth to check if this is also the case with SDK-style (.NET Core) projects.

If you have the time to contribute a .Net Core example to https://github.com/cefsharp/CefSharp.MinimalExample that would make debugging/testing much easier.

OK, I will submit a PR with a minimal .NET Core example.

Thank you!

@537mfb
Copy link

537mfb commented Sep 4, 2019

Was testing @kpreisser's method of adding all packages individually and adding the private true in a WPF project and got the following error:

Unable to find an entry point named 'CopyMemory' in DLL 'kernel32.dll'.'

Looking around i found [https://github.com/dotnet/coreclr/issues/24008]https://github.com/dotnet/coreclr/issues/24008 where @vatsan-madhavan points out that:

In .NET Framework there was a special case for a few function names and CopyMemory happened to be one of them. The special case was removed in .NET Core. In order to address this in your code, please update the attribute to include the following: [DllImport EntryPoint="RtlMoveMemory"]. If the EntryPoint property is set as above, the P/Invoke will work in both .NET Framework and .NET Core

Its not just CopyMemory, as you can see in [https://github.com/dotnet/pinvoke/issues/431]dotnet/pinvoke#431, other memory related functions are also affected - MoveMemory, CopyMemory, FillMemory and ZeroMemory at least

kpreisser added a commit to kpreisser/CefSharp that referenced this issue Sep 4, 2019
@kpreisser
Copy link
Contributor

@537mfb Thanks! I created #2885 to fix the CopyMemory declaration.

I also updated cefsharp/CefSharp.MinimalExample#57 to include a minimal WPF example using .NET Core 3.0 which should work once the CopyMemory declaration is fixed.

@amaitland
Copy link
Member Author

@537mfb Thanks for the links 👍
@kpreisser Thanks for the PR and updated WPF example 👍

I am planning on releasing 75.1.142 in the next couple of days, I've committed c11157f into the cefsharp/75 branch (copy and paste fail I've linked the incorrect issue, oh well) which contains the bare minimum required to get WPF working.

@537mfb
Copy link

537mfb commented Sep 5, 2019

@kpreisser Thanks for the quick PR
@amaitland Thanks for the update

amaitland pushed a commit that referenced this issue Sep 6, 2019
* Fix the entrypoint declaration for CopyMemory.

Contributes to #2796

* Follow-Up: Switch to RtlCopyMemory which is faster, but requires that the buffers do not overlap.
@amaitland
Copy link
Member Author

Started the refactor of the BrowserProcess see #2891 there is a little bit to go before it's done.

Sorry, actually here I only meant that the .nuspec file probably needs a <group targetFramework=".NETCoreApp3.0"> element in addition to <group targetFramework=".NETFramework4.5.2">, so that the transitive dependencies to the other NuGet packages will be resolved correctly for .NET Core projects.

@kpreisser Have you tested that adding .NETCoreApp3.0 to the group resolves the dependency issue? If so are you able to create a PR?

kpreisser added a commit to kpreisser/CefSharp that referenced this issue Sep 6, 2019
This ensures the transitive dependencies (CefSharp.Common) are correctly resolved in .NET Core 3.0 projects.

Contributes to cefsharp#2796
@kpreisser
Copy link
Contributor

kpreisser commented Sep 6, 2019

@kpreisser Have you tested that adding .NETCoreApp3.0 to the group resolves the dependency issue? If so are you able to create a PR?

Yes, I tested it with the minimal .NET Core examples and the transitive references were successfully resolved. I created PR #2894 to add these.
Thanks!

@vatsan-madhavan
Copy link

  • .Net Core will only load files that are included in .deps.json (as described in #2796 (comment)). This makes dynamically loading files are runtime near impossible.

@kpreisser I think I'm following what is being described in the comment, but I'm not 100% sure. if you can create a simple barebones project that illustrates the problem you are encountering, I can try to troubleshoot and see if we can come up with a solutions (or craft a viable workaround).

For your scenario of having an AnyCPU Class Library you'll have to do your own research to see what is possible. Personally I'd suggest keeping things simple by having an x86 and x64 target.

Have you had a chance to look at the cross platform targeting documentation yet?

If you don't have architecture specific code, then why not just build a netstandard2.1 targeted library?

If you truly have architecture specific code, then you'll probably need a code, build that generates reference-assemblies, RID-specific runtime assemblies and separately and also have a packaging architecture that uses the bait & switch technique_ (which is not very well documented). I wouldn't recommend going down this path if you can help it. Adding some complexity to make your library conform to nestandard2.1 would be worth it ultimately. But if you must, even this can be done with a little effort.
Microsoft.NetCore.App is a good example of a package that illustrates how this technique is used. You might find it in your NuGet cache already, or else you can download it from NuGet.org and peek into it using NuGetPackageExplorer. If you look into runtime.json in the package, you'll see how the base package 'links' to RID-specific packages. A project only needs to PackageReference a package such as this - the RID-specific packages would be automatically selected and restored by NuGet intelligently on a need basis. (Microsoft.NetCore.App itself is not intended to be used in PackageReference scenarios - I'm just calling attention to it to illustrate the bait & switch technique).

There is no probing privatePath option in .Net Core, support for loading from a sub directory is very limited currently see dotnet/sdk#10366

This can be worked around in an inline MSBuild task. Take a look at _AssemblyResolverForSystemReflectionMetadataLoadContext task here. There are several things that can trip you, but it can be done reliably if you are in reasonable control of some of the parameters involved.

@amaitland
Copy link
Member Author

@vatsan-madhavan Thanks for taking the time to comment 👍 Any insights you could give would be greatly appreciated. Even just a pointer to the appropriate place to ask some additional questions would be helpful.

I think I'm following what is being described in the comment, but I'm not 100% sure. if you can create a simple barebones project that illustrates the problem you are encountering, I can try to troubleshoot and see if we can come up with a solutions (or craft a viable workaround).

For the current set of Nuget packages (CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen) the project will compile, the dlls are included in the bin directory, they are however not included in .deps.json, which I believe is described in dotnet/sdk#2162 (comment)

  • The unmanaged dlls are included with <Private>false</Private>
  • They are copied with None entries in a .targets file.

The current Nuget packages allow for an old school bait and switch for AnyCPU support and an optional $(CefSharpTargetDir) property in MSBuild used to customise the file copy location. It's fairly common complain that CefSharp pollutes the bin folder with too many dlls.

I was hoping to create a new set of Nuget packages that utilise runtimes/{rid}/native for the unmanaged dlls and resources.

The folder structure used by the Chromium Embedded Framework(CEF) looks like the following screenshot
folderstructure

  • My first attempt included the Locales and Swiftshader folders in the native folder. Unfortunately the folder structure is not maintained as per Architecture-specific files folder hierarchy lost (native subfolders) NuGet/Home#7698
    • The Swiftshader folder must be relative to libcef.dll as Chromium decides at runtime which set of dlls are used to render WebGL (either hardware or software rendering).
  • Second attempt was to use contentFiles to copy the two folders into the native folder, this appeared to work initially, unfortunately it only works when the package is directly referenced, when it's included as a transitive reference then they folders aren't copied to the native folder.

If you have any suggestions or potential workarounds for this I'd be interested in testing them out, I can provide an example without too much effort.

Ignore the missing files for now (copying them manually to be precise) the Mixed Mode CLI/C++ assembly (CefSharp.Core.dll) is unable to automatically load libcef.dll when it's located in the runtimes/{rid}/native folder. If I manually call NativeLibrary to load the assembly then I can successfully load CefSharp.Core.dll. I know VC++ is fairly new to .Net Core so I'm wondering if the scenario is supported yet? I can provide an example, if required.

There are some other issues that I've run into along the way:

Have you had a chance to look at the cross platform targeting documentation yet?

I haven't yet no, I'll check it out thanks.

If you don't have architecture specific code, then why not just build a netstandard2.1 targeted library?

The situation as it stands

  • We target .Net 4.5.2
  • The Mixed Mode CLI/C++ dlls are built with VC++ 2015(v140)
  • Visual Studio 2013 can install/use the current set of Nuget packages.

Adding some complexity to make your library conform to nestandard2.1 would be worth it ultimately

  • As per https://docs.microsoft.com/en-us/dotnet/core/porting/cpp-cli#ccli-net-core-limitations
    • C++/CLI projects can't target .NET Standard, only .NET Core (or .NET Framework)., unless this has changed or I'm interpriting this incorrectly targeting .Net Standard isn't an option.
    • C++/CLI projects can't multitarget multiple .NET platforms. If you need to build a C++/CLI project for both .NET Framework and .NET Core, use separate project files., this isn't a show stopper, it's just a extra work to maintain two project files.
  • The project uses WCF currently, we'd have to conditionally compile all references

There are quite a few other build environment complexities that I won't get into now.

Microsoft.NetCore.App is a good example of a package that illustrates how this technique is used. You might find it in your NuGet cache already, or else you can download it from NuGet.org and peek into it using NuGetPackageExplorer. If you look into runtime.json in the package, you'll see how the base package 'links' to RID-specific packages. A project only needs to PackageReference a package such as this - the RID-specific packages would be automatically selected and restored by NuGet intelligently on a need basis. (Microsoft.NetCore.App itself is not intended to be used in PackageReference scenarios - I'm just calling attention to it to illustrate the bait & switch technique).

I downloaded microsoft.netcore.app.2.2.8.nupkg a few months ago as I started doing research into using a runtime.json file as I'd seen references suggesting it might be the solution, unfortunately I couldn't find any official documentation. Any chance there is some documentation somewhere that I missed?

I got stuck on the earlier issues and reverted to a more traditional .targets approach in the short term.

This can be worked around in an inline MSBuild task. Take a look at _AssemblyResolverForSystemReflectionMetadataLoadContext task here. There are several things that can trip you, but it can be done reliably if you are in reasonable control of some of the parameters involved.

Thanks for the reference, hopefully others will find this helpful 👍

@vatsan-madhavan
Copy link

I was hoping to create a new set of Nuget packages that utilise runtimes/{rid}/native for the unmanaged dlls and resources.

I took a look at CefSharp.Winforms, and I think you have already tried the one thing I would have suggested - which is to attempt <Reference Include="..." /> directly.

Attempting to build a normalized structure within NuGet package based on folder-conventions would be the next thing to try, which you've already figured out I think.

re: the swiftshader problem...

unfortunately it only works when the package is directly referenced, when it's included as a transitive reference then they folders aren't copied to the native folder.

Have you tried changing PrivateAssets (in the PackageReference?) to analyzers;build? The default normally includes contentfiles.

re: c++/cli: you're absolutely right, it can only build netcoreap* or netfx targets. I hadn't realized that you were dealing with c++/cli when I made that comment.

unfortunately I couldn't find any official documentation. Any chance there is some documentation somewhere that I missed?

You didn't miss any - there just isn't any out there. Folks including me who have implemented this have done so through trial and error.

The general idea goes like this:

  • Number of distinct packages needed and suggested nomenclature:
    • $(Package).$(version).nupkg
    • For each applicable $(RID)
      • runtime.$(RID).$(Package).$(version).nupkg (e.g., runtime.win-x64.$(Package).$(version).nupkg)
  • Content/Structure
    • Follow folder conventions
    • Main package $(Package).$(version).nupkg will have RID-neutral artifacts.
      • Notably, ref/$(tfm) content will be hosted in this bundle.
      • The main package will also contain a runtime.json file. It's content-structure will be described later.
      • The main package can be empty/devoid of any binaries, and may only carry text files (like LICENSE, COPYRIGHT etc.) and runtime.json.
    • The RID-specific packages will contain content that are specific to the $(RID) mentioned in the package name.
      • Notably, architecture specific binaries under runtimes/$(RID)/native path will be part of these packages.
    • runtime.json file.
      • The main $(Package).$(version).nupkg package contains a runtime.json file that associates this architecture-neutral package with various $(RID) specialized versions of the same package with names like runtime.$(RID).$(Package).$(version).nupkg.
      • The content/structure of this file is as follows:
  "runtimes": {
    "$(RID1)": {
      "$(PackageName)": {
        "runtime.$(RID1).$(PackageName)": "$(version)"
      }
    },
    "$(RID2)": {
      "$(PackageName)": {
        "runtime.$(RID2).$(PackageName)": "$(version)"
      }
    }, 
    "$(RID3)": {
       ..
    }
  }
}

A concrete example may look like this:

  "runtimes": {
    "win-x64": {
      "$(NormalizedPackageName)": {
        "runtime.win-x64.$(PackageName)": "$(PackageVersion)"
      }
    },
    "win-x86": {
      "$(NormalizedPackageName)": {
        "runtime.win-x86.$(PackageName)": "$(PackageVersion)"
      }
    }
  }
}

@amaitland
Copy link
Member Author

@vatsan-madhavan Thanks for the very detailed reply, greatly appreciated 👍

I took a look at CefSharp.Winforms,

I'll rework the demo to illustrate the problems I'm having using runtimes/{rid}/native with the Mixed Mode CLI/C++ dlls loading the unmanaged dlls, perhaps there is something trivial I'm missing or perhaps switching to the runtime.json will resolve the issues.

Hopefully have a demo of the problem shortly.

Have you tried changing PrivateAssets (in the PackageReference?) to analyzers;build? The default normally includes contentfiles.

I haven't yet no, I'll give that a try.

I did see the Nuget 5+ supports buildTransitive which should allow me to use a .targets file to copy the swiftshader and locales folders to the runtimes/{rid}/native folder, they're only required at runtime so this sounds workable to me.

Folks including me who have implemented this have done so through trial and error.

Thanks again for taking the time to write such a detailed post, I'm pretty sure I understand the basic concept 😄

Looking at the microsoft.netcore.app.2.2.8 package in a little more detail to understand how the bait and switch works for the managed dlls. From what I can tell it provides a set of compiler reference assemblies that likely target AnyCPU in the ref directory. Are you aware of any packages that use VC++ dlls? The From a convention-based working directory section for ref say the dlls should target AnyCPU. For CefSharp.Core.dll which is a Mixed Mode CLI/C++ dll I cannot compile as AnyCPU, it's required for both compile time and runtime.

@vatsan-madhavan
Copy link

vatsan-madhavan commented Apr 28, 2020

You're independently discovering many the problems we worked through with DirectWriteForwarder and System.Printing.dll (C++/CLI assemblies) in WPF 😉

So C++/CLI tooling is not very good at generating ref assemblies. In fact there is no support for it 😢

C#'s /refout and /refonly also have weaknesses. The main one is that if an assembly has an InternalsVisibleTo attribute, then /refout or /refonly will generate a ref assembly containing all the internal types/methods. Sometimes this is the desired outcome, and at other times one may not want internal types/methods in the ref assemblies. See dotnet/roslyn#36409 for the tracking bug for this problem.

GenApi is a tool/package that can generate a ref assembly from any [1] CLI assembly. It doesn't quite generate a ref assembly per se - it generates sources for a C# project that, when built, will produce an assembly that can stand-in for a ref-assembly. These sources would have the methods, getters, setters etc. all hollowed out, just like a real ref-assembly.

GenApi is based on Microsoft Common Compiler Infrastructure, which is deprecated in favor of Mono.Cecil. I recently started tinkering with the idea of writing something equivalent (RefGen) with mono.cecil + ICsharpCode.DeCompiler, but it hasn't seen much progress just yet - GenApi is the most mature solution I know today that's ready to be used in a product.

You can get Microsoft.DotNet.GenApi from https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json NuGet feed (browsable feed). Microsoft.DotNet.GenApi.targets should help you figure out how to use the package in your builds, and also these:

            CommandArgument assemblyArg = app.Argument("assembly", "Path for an specific assembly or a directory to get all assemblies.");
            assemblyArg.IsRequired();
            CommandOption libPath = app.Option("-l|--lib-path", "Delimited (',' or ';') set of paths to use for resolving assembly references", CommandOptionType.SingleValue);
            CommandOption apiList = app.Option("-a|--api-list", "Specify a api list in the DocId format of which APIs to include.", CommandOptionType.SingleValue);
            CommandOption outFilePath = app.Option("-o|--out", "Output path. Default is the console. Can specify an existing directory as well and then a file will be created for each assembly with the matching name of the assembly.", CommandOptionType.SingleValue);
            CommandOption headerFile = app.Option("-h|--header-file", "Specify a file with an alternate header content to prepend to output.", CommandOptionType.SingleValue);
            CommandOption<WriterType> writerType = app.Option<WriterType>("-w|--writer", "Specify the writer type to use. Legal values: CSDecl, DocIds, TypeForwards, TypeList. Default is CSDecl.", CommandOptionType.SingleValue);
            CommandOption<SyntaxWriterType> syntaxWriterType = app.Option<SyntaxWriterType>("-s|--syntax", "Specific the syntax writer type. Only used if the writer is CSDecl. Legal values: Text, Html, Xml. Default is Text.", CommandOptionType.SingleValue);
            CommandOption<DocIdKinds> docIdKinds = app.Option<DocIdKinds>("-d|--doc-id-kinds", "Only include API of the specified kinds. Legal values: A, Assembly, Namespace, N, T, Type, Field, F, P, Property, Method, M, Event, E, All. Default is All.", CommandOptionType.SingleValue);
            CommandOption exceptionMessage = app.Option("-t|--throw", "Method bodies should throw PlatformNotSupportedException.", CommandOptionType.SingleValue);
            CommandOption globalPrefix = app.Option("-g|--global", "Include global prefix for compilation.", CommandOptionType.NoValue);
            CommandOption excludeApiList = app.Option("--exclude-api-list", "Specify a api list in the DocId format of which APIs to exclude.", CommandOptionType.SingleValue);
            CommandOption excludeAttributesList = app.Option("--exclude-attributes-list", "Specify a list in the DocId format of which attributes should be excluded from being applied on apis.", CommandOptionType.SingleValue);
            CommandOption followTypeForwards = app.Option("--follow-type-forwards", "[CSDecl] Resolve type forwards and include its members.", CommandOptionType.NoValue);
            CommandOption apiOnly = app.Option("--api-only", "[CSDecl] Include only API's not CS code that compiles.", CommandOptionType.NoValue);
            CommandOption all = app.Option("--all", "Include all API's not just public APIs. Default is public only.", CommandOptionType.NoValue);
            CommandOption memberHeadings = app.Option("--member-headings", "[CSDecl] Include member headings for each type of member.", CommandOptionType.NoValue);
            CommandOption hightlightBaseMembers = app.Option("--hightlight-base-members", "[CSDecl] Highlight overridden base members.", CommandOptionType.NoValue);
            CommandOption hightlightInterfaceMembers = app.Option("--hightlight-interface-members", "[CSDecl] Highlight interface implementation members.", CommandOptionType.NoValue);
            CommandOption alwaysIncludeBase = app.Option("--always-include-base", "[CSDecl] Include base types, interfaces, and attributes, even when those types are filtered.", CommandOptionType.NoValue);
            CommandOption excludeMembers = app.Option("--exclude-members", "Exclude members when return value or parameter types are excluded.", CommandOptionType.NoValue);
            CommandOption langVersion = app.Option("--lang-version", "Language Version to target", CommandOptionType.SingleValue);

[1] I've only tried this on a C++/CLI pure and C# based .NET Core assemblies. You'll have to let me know how this goes for a C++/CLI mixed assembly. My expectation is that it ought to work. I know that Mono.Cecil can read (but not write) mixed-mode assemblies. I would think that Microsoft.Cci was capable of reading mixed-mode assemblies as well. edit I checked on a very simple test-assembly with PE32+, CorFlags 0x10 (i.e., mixed mode) that GenApi can indeed produce ref-assembly sources.

kpreisser added a commit to kpreisser/CefSharp that referenced this issue Jul 11, 2020
…serSubprocess.Core, CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen.

Contributes to cefsharp#2796
amaitland pushed a commit to amaitland/CefSharp that referenced this issue Jul 18, 2020
…not used in the corresponding .NET Framework projects.

Add CefSharp.Example.netcore and CefSharp.WinForms.Example.netcore.

Output documentation files.

Use "bin.netcore" and "obj.netcore" as [intermediate] output path for the .NET Core projects, to allow side-by-side builds with the .NET Framework projects.

Remove WindowsTargetPlatformVersion property as that is declared in CefSharp.props.

Switch project CefSharp.netcore.csproj to AnyCPU as it doesn't require different builds for each platform.

- Remove dependencies to "opengl32.lib" and "glu32.lib" which aren't available for ARM64 and don't seem to be needed.
- Clean up project files.

Add .NET Core 3.1 projects for CefSharp, CefSharp.Core, CefSharp.BrowserSubprocess.Core, CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen.

Contributes to cefsharp#2796
amaitland pushed a commit to amaitland/CefSharp that referenced this issue Jul 22, 2020
…not used in the corresponding .NET Framework projects.

Add CefSharp.Example.netcore and CefSharp.WinForms.Example.netcore.

Output documentation files.

Use "bin.netcore" and "obj.netcore" as [intermediate] output path for the .NET Core projects, to allow side-by-side builds with the .NET Framework projects.

Remove WindowsTargetPlatformVersion property as that is declared in CefSharp.props.

Switch project CefSharp.netcore.csproj to AnyCPU as it doesn't require different builds for each platform.

- Remove dependencies to "opengl32.lib" and "glu32.lib" which aren't available for ARM64 and don't seem to be needed.
- Clean up project files.

Add .NET Core 3.1 projects for CefSharp, CefSharp.Core, CefSharp.BrowserSubprocess.Core, CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen.

Contributes to cefsharp#2796
amaitland pushed a commit to amaitland/CefSharp that referenced this issue Jul 23, 2020
…not used in the corresponding .NET Framework projects.

Add CefSharp.Example.netcore and CefSharp.WinForms.Example.netcore.

Output documentation files.

Use "bin.netcore" and "obj.netcore" as [intermediate] output path for the .NET Core projects, to allow side-by-side builds with the .NET Framework projects.

Remove WindowsTargetPlatformVersion property as that is declared in CefSharp.props.

Switch project CefSharp.netcore.csproj to AnyCPU as it doesn't require different builds for each platform.

- Remove dependencies to "opengl32.lib" and "glu32.lib" which aren't available for ARM64 and don't seem to be needed.
- Clean up project files.

Add .NET Core 3.1 projects for CefSharp, CefSharp.Core, CefSharp.BrowserSubprocess.Core, CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen.

Contributes to cefsharp#2796
amaitland pushed a commit that referenced this issue Jul 23, 2020
* Add .NET Core 3.1 projects for CefSharp, CefSharp.Core, CefSharp.BrowserSubprocess.Core, CefSharp.WinForms, CefSharp.Wpf, CefSharp.OffScreen.

Contributes to #2796

* - Remove dependencies to "opengl32.lib" and "glu32.lib" which aren't available for ARM64 and don't seem to be needed.
- Clean up project files.

* Switch project CefSharp.netcore.csproj to AnyCPU as it doesn't require different builds for each platform.

* Remove WindowsTargetPlatformVersion property as that is declared in CefSharp.props.

* Use "bin.netcore" and "obj.netcore" as [intermediate] output path for the .NET Core projects, to allow side-by-side builds with the .NET Framework projects.

* Output documentation files.

* Add CefSharp.Example.netcore and CefSharp.WinForms.Example.netcore.

* Follow-up: Remove properties from the example projects that are also not used in the corresponding .NET Framework projects.
@amaitland amaitland added this to the 87.1.x milestone Jan 9, 2021
@amaitland amaitland self-assigned this Jan 9, 2021
@amaitland

This comment has been minimized.

@amaitland amaitland changed the title Feature Request - Add .Net Core 3.x Support Feature Request - Add .Net Core 3.1 Support Jan 9, 2021
@amaitland amaitland unpinned this issue Jan 15, 2021
@amaitland
Copy link
Member Author

The first official release to support .Net Core 3.1 is now available on Nuget.org


  • A minimum of .Net Core 3.1 is required (for .Net 3.0 which is no longer supported by Microsoft you'll need to use the older packages).
  • The MinimalExample has been updated for testing purposes.
  • These packages required Visual C++ 2019

They should work for both .Net Core and .Net 5.0. In a version of two I'll change the main CefSharp.WinForms/Wpf/OffScreen packages to be just meta packages and create a set of Net452 packages, so Nuget will choose the correct package based on framework.

See #3284 (comment) for further details.

@John0King
Copy link

@amaitland won't the multiple targeting just work ? why create a metapackage instead of direct use multiple-targeting ?

@amaitland
Copy link
Member Author

won't the multiple targeting just work ?

@John0King What do you mean by this exactly?

The new net core packages only contain a set of dlls compiled with .Net Core 3.1, whilst the original Nuget package contain dlls compiled using .Net 4.5.2.

@John0King
Copy link

@amaitland I'm not familiar with c++/cil , but in just c#, we can just multiple-targeting the targetframeworks
eg. <TargetFrameworks>net451;net5.0</TargetFrameworks>
and in old formate with .nupkg file can also do that.

it's on nuget package contains multiple set of dlls for different TFM , and will choose the correct dll base on the TFM of user's class library

@amaitland
Copy link
Member Author

I'm not familiar with c++/cil

@John0King See #2796 (comment) (which links to https://docs.microsoft.com/en-us/dotnet/core/porting/cpp-cli#ccli-net-core-limitations). There is no multi targeting for C++/CLI or support for the newer SDK project format.

it's on nuget package contains multiple set of dlls for different TFM , and will choose the correct dll base on the TFM of user's class library

Having a single package that contains all versions is a nice idea in theory, the reality is that C++/CLI project support currently makes this very complicated.

In theory the meta package will have the same result from an end user point of view (or at least that's my current understanding).

@rLindorfer
Copy link

After reading all the previous comments, I would like to summarize the facts regarding AnyCpu support with .NET Core 3.1.
Is it correct that there is no AnyCpu support for .NET Core 3.1?

Is there any example showing the steps needed when someone would like to upgrade from .NET 4.8 with AnyCPU support to the .NET Core 3.1 version?

@kpreisser
Copy link
Contributor

kpreisser commented Feb 3, 2021

After reading all the previous comments, I would like to summarize the facts regarding AnyCpu support with .NET Core 3.1.
Is it correct that there is no AnyCpu support for .NET Core 3.1?

There is kind of AnyCPU support in .NET Core, when you don't specify a RuntimeIdentifier (and Platform) when building a project. In that case, there will be a runtimes directory in your project's bin folder containing the runtime files for various architectures like win-x64, win-x86, linux-x64, etc., and then the runtime will dynamically load the ones for the current architecture/OS. This should also work with the CefSharp.NETCore packages, but @amaitland noted that this might not work in all cases:

I strongly suggest setting a RuntimeIdentifier. I've done my best to make it work when no RuntimeIdentifier is specified, there are probably use cases when you will need to specify one for it to work.

However, generally there is no "real" AnyCPU suppot in .NET Core/.NET 5 for application projects, because when building the application, the apphost (<application>.exe on Windows) will have a specific architecture (x64, x86, arm, or arm64), so the process will only run with that specific architecture. (Except when you run the application with dotnet <application>.dll, which will use the architecture of the dotnet host, but I think that isn't recommended for GUI applications.)

This is different from .NET Framework .exe files that are compiled as AnyCPU, which use a special feature of the OS to either run as x64 or as x86 (32-bit), depending on the OS architecture.

@trivalik

This comment has been minimized.

@amaitland

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests