2014-01-15 16 views
7

Ich erstelle eine Anwendung in C#, die benutzerdefinierte Webseiten für die meisten der GUI hostet. Als Host möchte ich eine JavaScript-API bereitstellen, damit die eingebetteten Webseiten auf einige der von der Host-Anwendung bereitgestellten Dienste zugreifen können.Wie kann ich Javascript Callback-Funktion aus C# Host-Anwendung ausführen

Ich bin in der Lage, den einfachen Fall für das Arbeiten mit der WebBrowser.ObjectForScripting -Eigenschaft zu erhalten und eine Skriptklasse zu implementieren. Dies funktioniert hervorragend für synchrone Javascript-Aufrufe. Einige der vom Host bereitgestellten Vorgänge laufen jedoch lange und ich möchte die Möglichkeit bereitstellen, dass das JavaScript nach Abschluss des Vorgangs zurückgerufen wird. Und das ist, wo ich in Schwierigkeiten gerate.

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 
    } 
} 

Der 'Start' Funktion in Javascript diesen ganzen Prozess beginnt. Das Problem, das ich habe, ist, Was ist der Typ für den Rückruf? Und wie soll ich es von C# aus nennen?

Wenn ich den String-Typ für Callback verwende, kompiliert und läuft es, aber innerhalb der LongRunningProcess-Methode enthält Callback tatsächlich den vollen Inhalt der onComplete-Funktion (zB 'function onComplete (result) {alert (result)}')

Wenn ich den Objekttyp verwende, kommt er als COM-Objekt zurück. Mit der Microsoft.VisualBasic.Information.TypeName-Methode wird 'JScriptTypeInfo' zurückgegeben. Aber soweit ich das beurteilen kann, handelt es sich weder um einen echten Typ noch um eine wirkliche Erwähnung durch MSDN.

Wenn ich die IReflect Schnittstelle verwende, läuft es ohne Fehler, aber es gibt keine Mitglieder, Felder oder Eigenschaften auf dem Objekt, die ich finden kann.

Eine Umgehung wäre, den String-Namen der Callback-Funktion anstelle der Funktion selbst zu übergeben (z. B. window.external.LongRunningProcess ('data', 'onComplete');). Ich weiß, wie man die JavaScript-Funktion namentlich ausführt, aber ich würde lieber nicht diese Syntax in den Webseiten benötigen, es würde auch nicht mit Inline-Callback-Definitionen im Javascript arbeiten.

Irgendwelche Ideen?

Für was es wert ist, habe ich bereits dieses System mit dem Chromium Embedded-Framework arbeiten, aber ich arbeite daran, den Code auf das WebBrowser-Steuerelement zu portieren, um die heftige Größe der Neuverteilung Chrom zu vermeiden. Die HTML-Seiten, die entwickelt werden, werden jedoch eventuell unter Linux/Mac OSX laufen, wo Chromium wahrscheinlich noch verwendet wird.

+0

ich bin kein Programmierer in C# ... und vielleicht ist dies für Ihr Problem keine Lösung, aber meine Kollegen eine Bibliothek verwenden JS innen exeguite C# (ich erinnere mich nicht, ob C# oder .net) ist vielleicht nutzlos für Sie, aber Sie versuchen zu sehen: http://javascriptdotnet.codeplex.com/. Oder Sie können 'callback' wie string' window.external.LongRunningProcess ('data', 'onComplete'); 'übergeben und in C# können Sie eine Funktion mit diesem Namen erstellen und diese wie einen Callback ausführen, ohne die JS-Funktion zu verwenden C# (aber mit einer nativen C# -Funktion) ... Wenn Kommentar nicht hilft Ihnen tut mir leid für die Zeit Verlust – Frogmouth

Antwort

11

Sie können Spiegelung für diese Verwendung:

[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 }); 
    } 
} 

Sie auch dynamic Ansatz versuchen könnte. Es wäre eleganter, wenn es funktioniert, aber ich habe es nicht überprüft:

[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] Die dynamic Methode tatsächlich funktioniert gut, und wahrscheinlich ist der einfachste Weg, JavaScript von C# zu rufen zurück, wenn Sie ein JavaScript-Funktionsobjekt haben. Mit Reflection und dynamic können Sie auch eine anonyme JavaScript-Funktion aufrufen.Beispiel:

C#:

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

JavaScript:

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

Die Reflexionsmethode hat funktioniert. Da ich .NET2.0 anvisiere, kann ich Dynamic nicht verwenden, aber es ist gut zu wissen, dass das funktioniert. Woher kennst du die magische "[DispID = 0]" Zeichenkette? Gibt es dafür irgendwo Dokumentation? – MrSlippers

+0

Ich kenne es aus den Tagen von COM Ruhm, aber ich habe nur eine Suche konnte keine offizielle Quelle finden. Es gibt jedoch viele inoffizielle, [zum Beispiel] (http://social.msdn.microsoft.com/Forums/ie/en-US/e45d0706-f718-410f-9831-cd487e4cebff/invoking-javascript-method- von-native-code? forum = ieextensiondevelopment). Die 'DispId [...]' -Syntax von 'Type.InvokeMember' ist [hier] dokumentiert (http://msdn.microsoft.com/en-us/library/ekye10tx (v = vs.110) .aspx). – Noseratio

+1

Noch besser können Sie die Zeile "dynamic callbackFunc = callback;" durch den Parameter vom Typ "dynamisch", usw. "dynamischer Rückruf". Ich habe ein seltsames Problem gefunden, dass es nicht funktioniert, wenn der Callback keine Parameter hat. In diesem Beispiel würde "callbackFunc()" nicht funktionieren. Irgendeine Idee warum? – Martynas

0

Wie @Frogmouth bemerkt hier schon können Sie Callback-Funktion Namen der LongRunningProcedure passieren:

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

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

und wenn LongRunningProcedure abgeschlossen ist .InvokeScript wie folgt verwendet werden:

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}); 
    } 
+1

'eval' scheint hier redundant zu sein. Wenn die Callback-Funktion mit dem Namen übergeben wird (anstatt als Objekt, wie in der Frage des OP), würde das folgende ohne 'eval' tun:' webBrowser1.Document.InvokeScript (callbackFunctionName, neues Objekt [] {result}) '. "Eval" kann jedoch nützlich sein, wenn ein neuer JavaScript-Code in die Seite eingefügt werden muss ([Beispiel] (http://stackoverflow.com/a/19002650/1768303)). – Noseratio