我遇到了 WPF 4 中添加的 XBAP 脚本互操作功能的问题。它涉及以下组合:
- 从 .NET 访问脚本对象的成员
- 在从 JavaScript 调用的回调中运行 .NET 代码
- 在部分信任中运行
这似乎是一个“选择任何两个”的场景......如果我尝试做所有这三件事,我会得到一个SecurityException
.
例如,组合 1 和 3 很容易。我可以将其放入托管网页的脚本中:
function ReturnSomething()
{
return { Foo: "Hello", Bar: 42 };
}
然后在我的 WPF 代码后面的按钮单击处理程序中,我可以这样做:
dynamic script = BrowserInteropHelper.HostScript;
if (script != null)
{
dynamic result = script.ReturnSomething();
string foo = result.Foo;
int bar = result.Bar;
// go on to do something useful with foo and bar...
}
即使在部分信任部署中,它也能正常工作。(我在 Visual Studio 2010 中使用 WPF 浏览器应用程序模板提供的默认 ClickOnce 安全设置,它可以像在 Internet 区域中运行一样调试 XBAP。)到目前为止,一切都很好。
我也可以将 2 和 3 结合起来。为了使我的 .NET 方法可以从 JavaScript 调用,遗憾的是我们不能只传递一个委托,我们必须这样做:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(int arg)
{
return "Value: " + arg;
}
}
然后我可以声明一个如下所示的 JavaScript 方法:
function CallMethod(obj)
{
var result = obj.MyMethod(42);
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
现在在一个 WPF 按钮单击处理程序中,我可以这样做:
script.CallMethod(new CallbackClass());
因此,我的 WPF 代码调用(通过BrowserInteropHelper.HostScript
)我的 JavaScriptCallMethod
函数,该函数又调用了我的 .NET 代码——具体来说,它调用了MyMethod
我的 CallbackClass 公开的方法。(或者我可以将回调方法标记为具有[DispId(0)]
属性的默认方法,这可以让我简化 JavaScript 代码 - 脚本可以将参数本身视为方法。任何一种方法都会产生相同的结果。)
MyMethod
成功调用回调。我可以在调试器中看到从 JavaScript (42) 传递的参数正确通过(已正确强制为 int)。当我的方法返回时,由于CallMethod
函数的其余部分,它返回的字符串最终会出现在我的 HTML UI 中。
太好了 - 所以我们可以做 2 和 3。
但是将这三者结合起来呢?我想修改我的回调类,以便它可以与脚本对象一起使用,就像我的第一个片段返回的那个ReturnSomething
函数一样。我们知道使用这样的对象是完全可能的,因为第一个示例成功了。所以你会认为我可以这样做:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(dynamic arg)
{
return "Foo: " + arg.Foo + ", Bar: " + arg.Bar;
}
}
然后修改我的 JavaScript 看起来像这样:
function CallMethod(obj)
{
var result = obj.MyMethod({ Foo: "Hello", Bar: 42 });
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
然后像以前一样从我的 WPF 按钮单击处理程序中调用该方法:
script.CallMethod(new CallbackClass());
这成功调用了 JavaScriptCallMethod
函数,该函数成功回调了MyMethod
C# 方法,但是当该方法尝试检索arg.Foo
属性时,我得到一个SecurityException
带有RequestFailed
. 这是调用堆栈:
at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.CodeAccessSecurityEngine.Check(PermissionSet permSet, StackCrawlMark& stackMark)
at System.Security.PermissionSet.Demand()
at System.Dynamic.ComBinder.TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject& result, Boolean delayInvocation)
at Microsoft.CSharp.RuntimeBinder.CSharpGetMemberBinder.FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
at System.Dynamic.DynamicMetaObject.BindGetMember(GetMemberBinder binder)
at System.Dynamic.GetMemberBinder.Bind(DynamicMetaObject target, DynamicMetaObject[] args)
at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at XBapDemo.CallbackClass.MyMethod(Object arg)
这就是异常报告的整个跟踪。在上面CallbackClass.MyMethod
,Visual Studio 显示了两个 [Native to Managed Transition] 和一个 [AppDomain Transition] - 这就是整个堆栈。(显然我们现在在另一个线程上。这个回调发生在线程面板描述为工作线程的地方 - 我可以看到主线程仍然位于我的 WPF 按钮单击处理程序中,等待对 JavaScript 的调用CallMethod
函数返回。)
显然,问题在于 DLR 最终将 JavaScript 对象包装在ComBinder
需要完全信任的对象中。但是在前面的例子中,我调用了一个 JavaScript 方法HostScript
,它返回给我一个对象,为我HostScript
将它包装在 aSystem.Windows.Interop.DynamicScriptObject
中。
该类DynamicScriptObject
特定于 WPFs XBAP 脚本互操作 - 它不是通常 DLR 类型的一部分,它在PresentationFramework.dll
. 据我所知,它所做的工作之一是使使用 C# 的dynamic
关键字访问 JavaScript 属性而不需要完全信任成为可能,即使这些属性是通过 COM 互操作(通常需要完全信任)在盖子。
据我所知,问题在于您只能获取从其他实例(例如)DynamicScriptObject
返回的对象的这些包装器。使用回调,这种包装似乎不会发生。在我的回调中,我得到了 C# 通常在普通的旧 COM 互操作场景中给我的那种动态包装器,此时,它要求我完全信任。DynamicScriptObject
HostScript
在完全信任的情况下运行它可以正常工作 - 这将是上面列表中的“1 和 2”组合。但我不想完全信任。(我想要 1、2和3。)在回调情况之外,我可以很好地访问 JavaScript 对象成员。大多数时候我都可以很好地访问 JavaScript 对象,但在回调中访问相同的对象是被禁止的,这似乎是不一致的。
有没有解决的办法?或者如果我想在回调中做任何有趣的事情,我是否注定要完全信任地运行我的代码?