How can I execute Javascript callback function fro

2019-01-14 18:56发布

问题:

I'm creating an application in C# that hosts custom web pages for most of the GUI. As the host, I'd like to provide a javascript API so that the embedded web pages can access some of the services provided by the host application.

I've been able to get the simple case for this working using the WebBrowser.ObjectForScripting property and implementing a scripting class. This works great for synchronous javascript calls. However, some of the operations that the host provides are long running and I'd like to provide the ability for the javascript to be called back when the operation completes. And this is where I'm running into trouble.

Javascript:

function onComplete( result )
{
    alert( result );
}

function start()
{
    window.external.LongRunningProcess( 'data', onComplete );
}

C#:

[ComVisible(true)]
public class ScriptObject
{
    public void LongRunningProcess( string data, <???> callback )
    {
        // do work, call the callback
    }
}

The 'start' function in javascript kicks this whole process off. The problem I'm having is, What is the type for the callback? And how should I call it from C#?

If I use the string type for callback, it compiles and runs, but from within the LongRunningProcess method callback actually contains the full contents of the onComplete function ( i.e. 'function onComplete( result ) { alert( result ) }' )

If I use the object type, it comes back as a COM object. Using the Microsoft.VisualBasic.Information.TypeName method, it returns 'JScriptTypeInfo'. But as far as I can tell, that's not a real type, nor is there any real mention of it through all of MSDN.

If I use the IReflect interface, it runs without error, but there are no members, fields, or properties on the object that I can find.

A work around would be to pass the string name of the callback function instead of the function itself ( i.e. window.external.LongRunningProcess( 'data', 'onComplete' ); ). I do know how to execute the javascript function by name, but I'd rather not have that syntax be required in the web pages, it also would not work with inline callback definitions in the javascript.

Any Ideas?

For what it's worth, I've already got this system working with the Chromium Embedded framework, but I'm working to port the code over to the WebBrowser control to avoid the hefty size of redistributing Chromium. However, the HTML pages being developed will eventually be run on Linux/Mac OSX where Chromium will probably still be used.

回答1:

You can use Reflection for that:

[ComVisible(true)]
public class ScriptObject
{
    public void LongRunningProcess(string data, object callback)
    {
        string result = String.Empty;

        // do work, call the callback

        callback.GetType().InvokeMember(
            name: "[DispID=0]",
            invokeAttr: BindingFlags.Instance | BindingFlags.InvokeMethod,
            binder: null,
            target: callback,
            args: new Object[] { result });
    }
}

You could also try dynamic approach. It'd be more elegant if it works, but I haven't verified it:

[ComVisible(true)]
public class ScriptObject
{
    public void LongRunningProcess(string data, object callback)
    {
        string result = String.Empty;

        // do work, call the callback

        dynamic callbackFunc = callback;
        callbackFunc(result);
    }
}

[UPDATE] The dynamic method indeed works great, and probably is the easiest way of calling back JavaScript from C#, when you have a JavaScript function object. Both Reflection and dynamic allow to call an anonymous JavaScript function, as well. Example:

C#:

public void CallbackTest(object callback)
{
    dynamic callbackFunc = callback;
    callbackFunc("Hello!");
}

JavaScript:

window.external.CallbackTest(function(msg) { alert(msg) })


回答2:

As @Frogmouth noted here already you can pass callback function name to the LongRunningProcedure:

function onComplete( result )
{
    alert( result );
}

function start()
{
    window.external.LongRunningProcess( 'data', 'onComplete' );
}

and when LongRunningProcedure completes use .InvokeScript as the following:

    public void LongRunningProcess(string data, string callbackFunctionName)
    {
        // do work, call the callback

        string codeStrig = string.Format("{0}('{1}')", callbackFunctionName, "{{ Your result value here}}");
        webBrowser1.Document.InvokeScript("eval", new [] { codeStrig});  
    }