我认为您在这里同时遇到了几个问题。
(1) 进程生命周期
据我了解您的问题及其上下文,您以某种方式手动执行JScript 脚本。执行此 EA 将在内部启动 SScripter.exe。您可以在 Debug 窗口中看到:
脚本完成后进程会被有效终止(因此也会终止您可能在UserControl 或Form 对象中注册的任何事件处理程序)。
(2) 将 JScript 对象实例作为 .NET delegate 传递
如果您能以某种方式延长脚本环境的生命周期,并且如果您可以将某些内容传递给您的事件,您将意识到 JScript 代码中的任何对象都将作为 System.__ComObject 传递给内部的 .NET 运行时艺电。因此,您不能只注册一个事件处理程序。
但是,当您从 .NET 评估对象时,您会发现它不是 IDispatch 接口:
MemberNames:
ToString,
GetLifetimeService,
InitializeLifetimeService,
CreateObjRef,
Equals,
GetHashCode,
GetType
TargetInvocationException@mscorlib: 'COM target does not implement IDispatch.'
我用下面的代码做了一个小测试:
function MyClass(name)
{
this.name = name;
}
MyClass.prototype.Invoke = function(value)
{
Session.Output("name " + value);
return true;
}
function main()
{
var myClass = new MyClass("Hotzenplotz");
myClass.Invoke("some Value");
var ctrl = new ActiveXObject("IMASE.TestUserControl2");
ctrl.Repository = Repository;
ctrl.JavaScriptObject = myClass;
}
[ProgId(Global.ADDIN_NAME + Global.DOT + "TestUserControl2")]
[Guid("87156dd9-e947-44bf-92a9-e9554a5b1844")]
[ComVisible(true)]
public partial class TestUserControl2 : ActivexControl
{
public static string TabName { get; } = Global.ADDIN_NAME;
private static readonly Lazy<string> _controlId = new Lazy<string>(() =>
{
var attribute = typeof(TestUserControl).GetCustomAttribute<ProgIdAttribute>();
return attribute.Value;
});
private Timer timer;
public static string ControlId = _controlId.Value;
public Repository Repository { get; set; }
public object JavaScriptObject { get; set; }
public TestUserControl2()
{
timer = new Timer();
timer.Elapsed += TimerEvent;
timer.Interval = 5000;
timer.Enabled = true;
timer.Start();
}
~TestUserControl2()
{
Logger.Default.TraceInformation("I'm gonna die ... " + this.GetHashCode());
}
private void OnDispose(object sender, EventArgs e)
{
timer.Dispose();
}
private void TimerEvent(object source, ElapsedEventArgs e)
{
Logger.Default.TraceInformation("I'm still alive ... " + this.GetHashCode());
if(null == JavaScriptObject) return;
try
{
var memberNames = JavaScriptObject.GetType().GetMembers(BindingFlags.Instance|BindingFlags.FlattenHierarchy|BindingFlags.Public).Select(p => p.Name);
Logger.Default.TraceInformation("memberNames: " + string.Join(", ", memberNames));
var result = JavaScriptObject.GetType().InvokeMember("Invoke", BindingFlags.InvokeMethod, null, JavaScriptObject, new object[] {"arbitraryString"});
Logger.Default.TraceInformation("result: " + result);
}
catch (Exception ex)
{
Logger.Default.TraceException(ex);
}
}
}
(3) 另一种方法
在您的插件中创建一个UserControl(使用WinForm 或Forms)并将ClearScript 用作脚本引擎。
将 EA 脚本中的 Session 和 Repository 传递给您的控件(或以其他方式执行,例如用于解决生命周期问题的菜单)并让您的表单代码从存储库(或任何其他来源)。然后根据需要对您的事件处理程序做出反应以执行您的JScript 代码。我创建了一个简单的示例,展示了如何从 EA JScript 调用控件并从表单代码中调用另一个 JScript,然后将记录到调试会话或常规脚本输出窗口:
function main()
{
var ctrl = new ActiveXObject("IMASE.TestUserControl2");
ctrl.Repository = Repository;
ctrl.Session = Session;
Session.Prompt("wait", promptOK);
}
main();
在您的表单代码中,您可以使用 Repository 和其他类似的对象调用您的 JScript:
public Repository Repository { get; set; }
public object Session { get; set; }
using (var engine = new JScriptEngine())
{
engine.AddHostObject("Repository", this.Repository);
engine.AddHostObject("Session", this.Session);
engine.Execute("Session.Output('Repository.ConnectionString: ' + Repository.ConnectionString);");
}
这是上述脚本交互的输出:
旁注:我个人认为不需要使用表单,因为我们可以在 AddIn 启动时动态注册 ActiveX 控件。有关执行此操作的代码,您可以查看以下要点:
https://gist.github.com/dfch/6a27bb1b9320c93456cee6d5b2b9d551
此外,如果您使用ClearScript 作为脚本宿主,您可以按照ClearScript FAQtorial 的问题#16 中的说明从您的脚本代码直接连接到您的(UI)事件。