Rubyで書いてJavaScriptで使う 2(managed codeを使いやすくする)
前回の続き。
http://d.hatena.ne.jp/arikui/20090428/1240866008
前は、
(なんちゃら).Invoke("呼ぶメソッドの名前", [引数])
という風でしたがやっぱり使いづらい。Ruby のオブジェクトをそのまま JavaScript にしたような感じで使いたい。ということで、多少の変更を加えます。
変更点
メソッドをLambdaで登録
http://d.hatena.ne.jp/arikui/20090305/1236259323
Lambda で登録すれば柔軟にオブジェクトが作れるしブラウザ側からメンバが見えます。
引数の渡し方を改善
ブラウザ - Silverlight 間の可変長引数の渡しが狂ってるなら、その辺は JavaScript に任せます。
new Function("return arguments.callee.Invoke(arguments)");
みたいなのを介して呼び出せば、Silverlight 側には常に ScriptObject が引数として渡されるようになります。実際に Managed Code を呼び出してるのは、この Function オブジェクトに登録されてる Invoke メソッドです。
Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult Public Function ToScriptObject() As Browser.ScriptObject Dim obj As Browser.ScriptObject = window.CreateInstance("Object") Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result) For i = 0 To names.Count - 1 Dim name As String = names(i) Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args) Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)") method.SetProperty("Invoke", fun) obj.SetProperty(name, method) Next Return obj End Function
デモ
http://arikui.github.com/run_ruby/TestPage.html
見えるぞ、私にもメソッドが見える。
コード
Public Class ScriptableRuby Private Shared window As Browser.HtmlWindow = Browser.HtmlPage.Window Private Shared runtime As Microsoft.Scripting.Hosting.ScriptRuntime = IronRuby.Ruby.CreateRuntime Private Shared engine As Microsoft.Scripting.Hosting.ScriptEngine = runtime.GetEngine("IronRuby") Private Shared scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope Private Shared operations As Microsoft.Scripting.Hosting.ObjectOperations = engine.CreateOperations(scope) Public Shared Function Create(ByVal source As String) As RubyObject Dim result = engine.Execute(source, scope) Return New RubyObject(ScriptableRuby.operations, result) End Function Public Shared Function Arrayify(ByVal args As Browser.ScriptObject, Optional ByVal arity As Integer = -1) As Object() Dim length As Integer = args.GetProperty("length") arity = IIf(arity < 0, length, arity) Dim _args(arity - 1) As Object For i As Integer = 0 To arity - 1 _args(i) = args.GetProperty(i) Next Return _args End Function <Browser.ScriptableType()> _ Public Class RubyObject Private result As Object Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult Public Delegate Function F(Of TResult)() As TResult Public Sub New(ByVal operations As Microsoft.Scripting.Hosting.ObjectOperations, ByVal result As Object) Me.result = result End Sub Public Function ToScriptObject() As Browser.ScriptObject Dim obj As Browser.ScriptObject = window.CreateInstance("Object") Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result) For i = 0 To names.Count - 1 Dim name As String = names(i) Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args) Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)") method.SetProperty("Invoke", fun) obj.SetProperty(name, method) Next Return obj End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, Optional ByVal args As Browser.ScriptObject = Nothing) As Object Dim member As IronRuby.Builtins.RubyMethod = operations.GetMember(result, name) If args Is Nothing Then Return engine.Operations.Invoke(member) Else Return engine.Operations.Invoke(member, ScriptableRuby.Arrayify(args, member.Info.GetArity())) End If End Function <Browser.ScriptableMember()> _ Public Function Invoke(ByVal name As Object, ByVal ParamArray args As Object()) As Object Return engine.Operations.Invoke(operations.GetMember(result, name), args) End Function End Class End Class