Hi, I'm new with ClearScript and I could not figure out how to call C# object function after passing it to JavaScript callback.
Here is the code:
C#
public class RowData : RowInterface
{
public string raw(string name)
{
return "Something";
}
}
public void execute(string query, ScriptObject cb) {
var row = new RowData();
cb.Invoke(false, row);
}
In JavaScript
.execute("Some query", row => {
row.raw("abc");
});
I got error: raw is not a function
I can call the object function if I add it with the addHostObject
engine.AddHostObject("row", row);
However, I can't do it because it's a js callback
=============
In Swift, I can do it by implementing the JSExport interface
public protocol RowDataProtocolExport: JSExport {
// Get raw data
func raw(_ name: String) -> String?
}
So any object that confirms the protocol can be passed directly to JavaScript and we can call the raw function.
Is there any way that achieves the same thing in ClearScript?
Thank you!
Hi @huyphams,
We can't reproduce your issue with the code you provided. Here's the full test program we used:
C#
public class RowData {
public string raw(string name) {
return "Something";
}
}
public class Executor {
public void execute(string query, ScriptObject cb) {
var row = new RowData();
cb.Invoke(false, row);
}
}
public static class TestApp {
public static void Main() {
using (var engine = new V8ScriptEngine()) {
engine.AddHostType(typeof(Console));
engine.Script.exec = new Executor();
engine.Execute(@"
exec.execute('Some query', row => {
Console.WriteLine(row.raw('abc'));
});
");
}
}
}
This program correctly writes "Something" to the console. Note that you didn't post the RowInterface definition, so we left it out.
Is there something you're doing differently?
Thanks!
Oh thanks @ClearScriptLib I can confirm that your code works, here is my code. I think the issue was related to the way I pass the value:
var list = cb.Engine.Script.Array(new List<RowData> { new RowData(), new RowData() });
var data = cb.Engine.Script.Object(new { rows = list });
cb.Invoke(false, data);
Full code:
public class RowData
{
public string raw(string name)
{
return "Something";
}
}
public partial class MainWindow : Window
{
public void execute(string query, ScriptObject cb)
{
var list = cb.Engine.Script.Array(new List<RowData> { new RowData(), new RowData() });
var data = cb.Engine.Script.Object(new { rows = list });
cb.Invoke(false, data);
}
public MainWindow()
{
InitializeComponent();
using (var engine = new V8ScriptEngine())
{
engine.AddHostType(typeof(Console));
engine.AddHostObject("context", this);
engine.Execute(@"
var demo = function(context) {
context.execute('some query', data => {
data.rows.forEach(function(row) {
Console.WriteLine(row.raw('abc'));
});
});
}
");
engine.Evaluate("demo(context);");
}
}
}
Hi @huyphams,
Regarding these lines:
``` C#
var list = cb.Engine.Script.Array(new List
var data = cb.Engine.Script.Object(new { rows = list });
You're invoking `Array` and `Object` as functions rather than constructors, which is probably not your intention. Particularly problematic is the `Array` invocation, as passing in a managed list is unlikely to have the desired effect. The `Object` call has no effect at all; that is, `data` ends up being set to `new { rows = list }`.
Since `list` must be a JavaScript array, you can construct it like this:
``` C#
var arrayFunc = (ScriptObject)cb.Engine.Script.Array;
var list = arrayFunc.Invoke(true, new RowData(), new RowData());
On the other hand, if you can avoid Array.prototype.forEach, you can avoid script objects completely:
``` C#
// C#
var list = new List
var data = new { rows = list };
cb.Invoke(false, data);
And then:
``` JavaScript
// JavaScript
for (let row of data.rows) {
Console.WriteLine(row.raw('abc'));
}
Good luck!
Wow, it works! I can't thank you enough.
On the other hand, if you can avoid Array.prototype.forEach, you can avoid script objects completely
Thanks for the tip, however, it was a shared library on multiple platforms so I could not avoid it.
Most helpful comment
Hi @huyphams,
Regarding these lines:
``` C# { new RowData(), new RowData() });
var list = cb.Engine.Script.Array(new List
var data = cb.Engine.Script.Object(new { rows = list });
On the other hand, if you can avoid
Array.prototype.forEach, you can avoid script objects completely:``` C# { new RowData(), new RowData() };
// C#
var list = new List
var data = new { rows = list };
cb.Invoke(false, data);
Good luck!