OrdinaryOrange
OrdinaryOrange

Reputation: 2712

CefSharp.offscreen in LinqPad

LinqPad is my goto REPL and there isn't much I throw at it that it cant handle.

However I cannot for the life of me get CefSharp (specifically OffScreen) to run.

I'm constantly met with either of the below errors

Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or one of its dependencies. The system cannot find the file specified.

BadImageFormatException: Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138'. An attempt was made to load a program with an incorrect format.

I have tried

And what seems like every combination of the above.

I don't understand the assembly resolution process that Visual Studio uses with the nuget package, but whatever it does I would like to at least simulate in Linqpad so I can avoid the VS ceremony when testing something simple.

I assume that manually referencing the correct .dll's and maybe setting a path somewhere should be sufficient, but I'm ideas=>EOF.

Can CefSharp be run outside of VS / MSBuild ?

Upvotes: 1

Views: 347

Answers (2)

OrdinaryOrange
OrdinaryOrange

Reputation: 2712

Found the answer with motivation from @Sasha's post and @amaitland's note about BadImageFormatException's being more than just incorrect architectures.

The below is all in reference to LP6 and CefSharp.Offscreen.NetCore. I have not pushed the efforts into LP5 but the process should be similar.

After some trial and error I narrowed down all of the necessary dependencies and worked out why CefSharp would not run in LinqPad.

Here are the steps to make it run -

  1. Add CefSharp.Offscreen.NetCore package as normal to query
  2. Enable Copy all NuGet assemblies into a single local folder (F4->Advanced)
  3. Add the OnInit() and queryPath code as below to the query
  4. Ensure the BrowserSubprocessPath is set before Initializing Cef

Here is the code.

async Task Main()
{
    var are = new AutoResetEvent(false);//my technique for waiting for the browser
    var sett = new CefSettings();
    sett.BrowserSubprocessPath = this.queryPath + @"\CefSharp.BrowserSubprocess.exe";   //CefSharp will complain it cant find it
    if (!Cef.IsInitialized) 
        Cef.Initialize(sett);
    var browser = new ChromiumWebBrowser("http://www.google.com");
    browser.LoadingStateChanged += (sender, args) => { if (!args.IsLoading) are.Set(); };
    are.WaitOne();
    await browser.WaitForInitialLoadAsync();
    var html = await browser.GetBrowser().MainFrame.GetSourceAsync();
    html.Dump("winner winner chicken dinner");
}

//this is the location of the queries shaddow folder
string queryPath = Path.GetDirectoryName(typeof(CefSettings).Assembly.Location);

void OnInit() // Executes when the query process first loads
{
    if (!Directory.Exists(queryPath + @"\locales")) //subdirectory of cef.redist
    {
        var nugetPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        var sources = new[] {
            /*paths here are hardcoded version dependant. Can get cefsharp.common.netcore version 
            from Util.CurrentQuery.NuGetReferences, but cef.redist not available via that method. */
            @"cef.redist.x64\95.7.14\CEF", //contans all the Cef dependencies needed
            @"cefsharp.common.netcore\95.7.141\runtimes\win-x64\lib\netcoreapp3.1", //mainly for ijwhost.dll
            @"cefsharp.common.netcore\95.7.141\runtimes\win-x64\native"}; //contains BrowserSubprocess & friends
        var dst = new DirectoryInfo(queryPath);
        foreach (var path in sources)
        {
            var src = new DirectoryInfo($@"{nugetPath}\.nuget\packages\{path}");
            CopyFilesRecursively(src, dst);
        }
    }
}

//curtesy of https://stackoverflow.com/a/58779/2738249 with slight mod
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
    foreach (DirectoryInfo dir in source.GetDirectories())
        CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
    foreach (FileInfo file in source.GetFiles())
    {
        var dst = Path.Combine(target.FullName, file.Name);
        if (!File.Exists(dst))
            file.CopyTo(dst);
    }
}

The why for those interested -

CefSharp needs every dependency to be in the same directory so they can be resolved at runtime, but Linqpad only copies a few key dll's from the NuGet package. None of the cef.redist files, ijwhost.dll or BrowserSubprocess.exe et al. come across. Dependencies are scattered between NuGet packages and trying to resolve them directly from the .nuget cache just does not work. So all these need to be brought in manually to the running query shadow path.

I did initially copy all files into the Assembly.GetExecutingAssembly().Location path, but this approach requires adding the assembly directory to the "path" environment variable. Internally Linqpad seems to have the shadow path set, so copying the dependencies to the shadow folder skips the need to set the environment variable.

Upvotes: 0

Sasha
Sasha

Reputation: 1017

It doesn't work because of the shadow-copying that LinqPad is using. Here is a hack to make your problem go away (spoiler alert: not really, read on):

For LinqPad v5

  1. Copy all CefSharp libraries to a separate folder (don't forget cef.redist).
  2. In LinqPad Preferences dialog (Advanced/Execution), set Do not shadow assembly references to True, restart LinqPad.
  3. Write your code in the LinqPad query.
  4. Reference CefSharp libraries from the folder you've set up on step 1.
  5. Run the query.

For previous LinqPad (earlier than v5)

  1. Write your code in the LinqPad query.
  2. Reference CefSharp libraries, so you get an exception from your question
  3. Find a LinqPad working directory (usually something like C:\Users\<user>\AppData\Local\Temp\LINQPad5\_yyigmhzg).
  4. Copy all CefSharp libraries to this folder (don't forget cef.redist).
  5. In LinqPad, click Ctrl + Shift + F5; this will reset the query state.
  6. Rerun the query.

Now all the referenced libraries should load. But you will likely face more problems after that.

I couldn't make CefSharp.MinimalExample work. LinqPad kept crashing for me with the cryptic message Query ended because an uncatchable exception was thrown and a crashdump.

Although I am not sure if you will make CefSharp work as intended under LinqPad, maybe this can get you a bit further.

Upvotes: 0

Related Questions