1

Intro

I'm programming a project where C# and JavaScript code need to work together, so I decided to use CefSharp as it guarantees the latest JS features (in contrast with WebView).

The Goal

Now I need to create an async C# method which would call an async JS function and then wait for it to finish. The problem is that JavaScript doesn't have anything like the await keyword, so everything is defined in terms of events.

This is what the Javascript code looks like:

var name1 = "A";
var name2 = "B";

library.renameObject("A","B");
library.onrename = function(from, to){
  if(from === "A"){
    //let C# know
  }
}

And the C# code should ideally be something like this:

class FooObject
{
  string Name;
  async Task Rename(string newName)
  {
    await mainFrame.EvaluateScriptAsync(@"
      //The JS code
    ");
    Name = newName;
  }
}

The Problem

At first I thought using a task/promise would do the job, but after some testing I found out that it's impossible to pass objects between JS and C#.

This means that when I tried to pass TaskCompletionSource to an IJavascriptCallback, I got

System.NotSupportedException: Complex types cannot be serialized to Cef lists

Then I tried to return a Promise from the JavaScript code, but in C# I always got null.


A Bad Solution

I'm aware that using reflection, proxies and Dictionary<int, WeakRef<object>> I could provide an interface, so that any C# object could be accessed from JavaScript in a way that would be indistinguishable from using an actual JS object. The approach would be:

  1. save (a weakref to) the C# object in the dictionary under a unique key
  2. using Reflection create methods that take the unique key as an argument and allow reading fields and calling methods of the object
  3. expose these methods to JavaScript using RegisterJsObject
  4. create a Proxy that would mimic an ordinary JS object and call the exposed C# methods in the background.

The bad news with this solution is that in JavaScript there are no destructors/finalizers, so I have no control over the V8 GC. This means the C# objects would stay there forever (memory leak) or get collected by the .Net GC too early (null pointer exception).

m93a
  • 8,866
  • 9
  • 40
  • 58
  • I've only used CEF3 not CEFSharp, but can you just store the data in JS then send an event to C# letting it know that the data is available to fetch? Or send the data as a string to parse and object-ify? – Dave S Jun 09 '17 at 17:07
  • Sending an event to C# is a good idea. However if the JavaScript code failed somehow, I'd have a method waiting forever. – m93a Jun 09 '17 at 17:22
  • On the C# side you split your code into 2 parts - part 1 starts an action then returns immediately without waiting. On completion you get an event. In C# you set flag(s) that the action is in progress, maybe with the time it started and a sequence ID. If the part 2 never gets called after x seconds you know it probably failed. – Dave S Jun 09 '17 at 17:45
  • @DaveS The problem is that it's a non-static method. And storing the instances in a dictionary is ugly at least. At most it's breaking my good code by exposing internal-stuff methods as public. – m93a Jun 09 '17 at 18:14
  • *Don't* use `async void`, that's only for event handlers. An `async void` method *can't* be awaited, it's essentially a fire-and-forget method – Panagiotis Kanavos Jun 27 '17 at 07:22
  • @PanagiotisKanavos Sorry, that was a typo :) Of course I need to await it later. – m93a Jun 27 '17 at 07:50
  • "Your best bet would be to bind an object and have the JS function callback to the bound object when done" (from https://github.com/cefsharp/CefSharp/issues/2188) – kosmosan Sep 04 '18 at 06:22

0 Answers0