如何在Javascript中连接C# ActiveX事件处理程序

9

我尝试使用几个代码片段将ActiveX对象与Javascript事件处理程序连接起来,但无法确定为什么事件处理程序没有被调用。

Github存储库包含此项目。

更新

通过将对SayHello()的javascript调用放置在“onLoad”事件中,我能够使ActiveX事件触发。现在我正在寻找C#调用以及如何将其连接到Javascript使用的ActiveX对象。

(这也可能依赖于IE的高级选项中启用本地脚本)。

消息继续

事件处理程序与此问题所描述的形式相同

    <script for="MyObject" event="OnUpdateString(stuff)">
        document.write("<p>" + stuff);
        document.writeln("</p>");
    </script>

利用MSDN文档,我创建了一个包含WebBrowser控件的WinForms应用程序,该控件充当ObjectForScripting(与问题无关)。这个容器调用ActiveX事件,但被Javascript忽略。我包括C# Form代码,以便完整地使用ActiveX交互,并允许将来使用ActiveX和/或WebBrowser控件的用户参考。

此文件旨在与新的Windows窗体项目一起使用,在该项目中向主窗口添加了WebBrowser控件。

C# Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ActiveXObjectSpace;

namespace TestActiveX
{
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    [System.Runtime.InteropServices.ComVisibleAttribute(true)]
    public partial class Form1 : Form
    {
        MyObject myObject = new MyObject();
        public Form1()
        {
            InitializeComponent();
            Text = "ActiveX Test";

            Load += new EventHandler(Form1_Load);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            webBrowser1.AllowWebBrowserDrop = false;
            webBrowser1.ObjectForScripting = this;
            webBrowser1.Url = new Uri(@"C:\path\to\TestPage.html");

            // Call ActiveX
            myObject.SayHello("C# Launch");
        }

        public string ControlObject()
        {
            return "<p>Control Object Called.</p>";
        }
    }
}

通过参考两个代码片段,我创建了一个ActiveX对象。需要注意的是,构建后需要注册该对象。这些代码片段分别来自此处此处C# ObjectX.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;


/// http://blogs.msdn.com/b/asiatech/archive/2011/12/05/how-to-develop-and-deploy-activex-control-in-c.aspx
/// https://dev59.com/W2XWa4cB1Zd3GeqPL1am
///
/// Register with %NET64%\regasm /codebase <full path of dll file>
/// Unregister with %NET64%\regasm /u <full path of dll file>
namespace ActiveXObjectSpace
{

    /// <summary>
    /// Provides the ActiveX event listeners for Javascript.
    /// </summary>
    [Guid("4E250775-61A1-40B1-A57B-C7BBAA25F194"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IActiveXEvents
    {
        [DispId(1)]
        void OnUpdateString(string data);
    }

    /// <summary>
    /// Provides properties accessible from Javascript.
    /// </summary>
    [Guid("AAD0731A-E84A-48D7-B5F8-56FF1B7A61D3"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IActiveX
    {
        [DispId(10)]
        string CustomProperty { get; set; }
    }

    [ProgId("MyObject")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid("7A5D58C7-1C27-4DFF-8C8F-F5876FF94C64")]
    [ComSourceInterfaces(typeof(IActiveXEvents))]
    public class MyObject : IActiveX
    {

        public delegate void OnContextChangeHandler(string data);
        new public event OnContextChangeHandler OnUpdateString;

        // Dummy Method to use when firing the event
        private void MyActiveX_nMouseClick(string index)
        {

        }

        public MyObject()
        {
            // Bind event
            this.OnUpdateString = new OnContextChangeHandler(this.MyActiveX_nMouseClick);
        }

        [ComVisible(true)]
        public string CustomProperty { get; set; }


        [ComVisible(true)]
        public void SayHello(string who)
        {
            OnUpdateString("Calling Callback: " + who);
        }
    }
}

最后是由浏览器或容器加载的HTML页面。它成功加载了ActiveX对象,并包含一个OnUpdateString事件处理程序。它检查可以调用ActiveX提供的函数SayHello,并进行调用。

我希望在文档中看到Javascript和C#的调用,但没有这样的条目被写入。

TestPage.html

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>DemoCSharpActiveX webpage</title>
</head>
<body>
        <script type="text/javascript">
        window.objectLoadFailure = false;
        </script>

        <object id="MyObject" onerror="window.objectLoadFailure = true" classid="clsid:7A5D58C7-1C27-4DFF-8C8F-F5876FF94C64"></object>

        <script for="MyObject" event="OnUpdateString(stuff)">
            document.write("<p>" + stuff);
            document.writeln("</p>");
        </script>


        <script type="text/javascript">
            document.write("<p>Loaded ActiveX Object: " + !window.objectLoadFailure);
            document.writeln("</p>");
            if (typeof window.external.ControlObject !== "undefined") {
                document.write(window.external.ControlObject());
            }


            var obj = document.MyObject;
            if (typeof obj.SayHello !== "undefined") {
                document.writeln("<p>Can Call say hello</p>")
            }
            obj.SayHello("Javascript Load");

        </script>
</body>
</html>

这个页面显示以下内容:

输出

已加载ActiveX对象:true

调用控制对象。

可以调用say hello函数。


我在这里没有发现其他可疑的东西,类似的代码以前肯定对我有用过。也许,还应该在那个<script>标签中添加"type = text/javascript"。 - noseratio - open to work
@Noseratio 很不幸,这些建议都没有实现。 - he_the_great
在这种情况下,我建议您提供一个完整的项目(包括应用程序和DLL),以便重现此问题,并在此问题上设置赏金。我相信会有人接手解决这个问题。 - noseratio - open to work
@Noseratio,我喜欢这个仓库的想法,但是我被迫等待赏金。 - he_the_great
1个回答

3
更新,只要您从HTML中实例化了<object>MyObject.object != null),您的JavaScript事件处理程序的最终问题就是在调用MyObject.SayHello("Javascript Load")之前使用document.write杀死原始HTML文档,并将其替换为<p>Loaded ActiveX Object: ...</p>。 在此之后,所有原始JavaScript事件处理程序都已消失。

因此,以下内容正常工作,事件得到触发和处理(带有alert):

<!DOCTYPE html>
<html>
<head>
    <title>DemoCSharpActiveX webpage</title>
</head>
<body>
    <script type="text/javascript">
        window.objectLoadFailure = false;
    </script>

    <object id="MyObject" onerror="window.objectLoadFailure = true" classid="clsid:7A5D58C7-1C27-4DFF-8C8F-F5876FF94C64"></object>

    <script type="text/javascript" for="MyObject" event="OnUpdateString">
        alert("Hello from event handler");
    </script>

    <script type="text/javascript" for="window" event="onload">
        alert("Hello from window.onload!");
        alert(MyObject.object);
        MyObject.SayHello("Javascript Load");
    </script>
</body>
</html>

为了让你的原始逻辑起作用,你可以直接操作DOM而不是使用document.write。或者,在OnUpdateString被触发和处理之后再调用它。
现在我已经看到完整的源代码,我可以告诉你这里有很多问题。
  • You can hit a break point inside SayHello because you create MyObject from C# [MyObject myObject = new MyObject()] and call it from C# [myObject.SayHello("C# Launch")]. Remove that and you'll see it never gets invoked when you call it from JavaScript [obj.SayHello("Javascript Load")].

  • That leads to another issue: the <object> doesn't get create successfully, and even more so, none of your JavaScript scripts even run, because your test HTML file is served from the local file system (via file:// protocol). This is a security restriction. Try changing your script like below to see none of the alerts actually show up:

    <script type="text/javascript" for="window" event="onload">
        alert("Hello from window.onload!");
        alert(MyObject.object) // null! object wasn't created...
        document.write("<p>Loaded ActiveX Object: " + !window.objectLoadFailure);
        document.writeln("</p>");
        if (typeof window.external.ControlObject !== "undefined") {
            document.write(window.external.ControlObject());
        }
    
    
        var obj = document.MyObject;
        if (typeof obj.SayHello !== "undefined") {
            document.writeln("<p>Can Call say hello</p>")
        }
        obj.SayHello("Javascript Load");
    </script>
    
  • There're several ways of fixing it. The easiest one is probably to use "Mark of Web". The hardest one would be to provide a custom implementation of IInternetSecurityManager. I myself would use yet another method - Internet Feature Control - and disable FEATURE_LOCALMACHINE_LOCKDOWN, FEATURE_BLOCK_LMZ_SCRIPT, FEATURE_BLOCK_LMZ_OBJECT keys. You can use following code I adapted from my other related answer:

    // static constructor, runs first
    static Form1()
    {
        SetWebBrowserFeatures();
    }
    
    static void SetWebBrowserFeatures()
    {
        // don't change the registry if running in-proc inside Visual Studio
        if (LicenseManager.UsageMode != LicenseUsageMode.Runtime)
            return;
    
        var appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
    
        var featureControlRegKey = @"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\";
    
        Registry.SetValue(featureControlRegKey + "FEATURE_BROWSER_EMULATION",
            appName, GetBrowserEmulationMode(), RegistryValueKind.DWord);
    
        // enable the features which are "On" for the full Internet Explorer browser
    
        Registry.SetValue(featureControlRegKey + "FEATURE_ENABLE_CLIPCHILDREN_OPTIMIZATION",
            appName, 1, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_AJAX_CONNECTIONEVENTS",
            appName, 1, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_GPU_RENDERING",
            appName, 1, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_WEBOC_DOCUMENT_ZOOM",
            appName, 1, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_NINPUT_LEGACYMODE",
            appName, 0, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_LOCALMACHINE_LOCKDOWN",
            appName, 0, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_BLOCK_LMZ_SCRIPT",
            appName, 0, RegistryValueKind.DWord);
    
        Registry.SetValue(featureControlRegKey + "FEATURE_BLOCK_LMZ_OBJECT",
            appName, 0, RegistryValueKind.DWord);
    }
    
    static UInt32 GetBrowserEmulationMode()
    {
        int browserVersion = 0;
        using (var ieKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Internet Explorer",
            RegistryKeyPermissionCheck.ReadSubTree,
            System.Security.AccessControl.RegistryRights.QueryValues))
        {
            var version = ieKey.GetValue("svcVersion");
            if (null == version)
            {
                version = ieKey.GetValue("Version");
                if (null == version)
                    throw new ApplicationException("Microsoft Internet Explorer is required!");
            }
            int.TryParse(version.ToString().Split('.')[0], out browserVersion);
        }
    
        if (browserVersion < 7)
        {
            throw new ApplicationException("Unsupported version of Microsoft Internet Explorer!");
        }
    
        UInt32 mode = 11000; // Internet Explorer 11. Webpages containing standards-based !DOCTYPE directives are displayed in IE11 Standards mode. 
    
        switch (browserVersion)
        {
            case 7:
                mode = 7000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE7 Standards mode. 
                break;
            case 8:
                mode = 8000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE8 mode. 
                break;
            case 9:
                mode = 9000; // Internet Explorer 9. Webpages containing standards-based !DOCTYPE directives are displayed in IE9 mode.                    
                break;
            case 10:
                mode = 10000; // Internet Explorer 10.
                break;
        }
    
        return mode;
    }
    
  • Now, your scripts do run, but the <object> still doesn't get created (alert(MyObject.object) shows null). Finally, you'd need to implement IObjectSafety interface on your ActiveX object and site-lock it to only your very own HTML pages. Without proper IObjectSafety the object won't be getting created under default IE security settings. Without site-locking it might become a huge security threat, as any malicious script possibly could create and use your object outside the context of your application.


更新以回应评论:

我已经使用你提供的示例更新了项目,请注意,我进行了更改,添加了一个C#按钮和一个Javascript按钮来触发事件。JS按钮有效,但C#无法触发。我正在寻找一个“来自:C#按钮”的警报。

在您的代码中,myObject实例是从C#中创建和访问的,且仅从C#中访问:

MyObject myObject = new MyObject();

// ...

private void button1_Click(object sender, EventArgs e)
{
   // Call ActiveX
   myObject.SayHello("C# Button");
}

这个实例与您从HTML创建的<object id="MyObject" onerror="window.objectLoadFailure = true" classid="clsid:7A5D58C7-1C27-4DFF-8C8F-F5876FF94C64"></object>实例无关。它们是两个独立,不相关的对象。您的事件处理程序仅适用于后者的<object>实例。您甚至没有订阅任何new MyObject()实例的事件。
如果我正确理解您的目标,您需要这样做:
private void button1_Click(object sender, EventArgs e)
{
    // Call ActiveX
    //myObject.SayHello("C# Button");

    this.webBrowser1.Document.InvokeScript("eval",
        new[] { "MyObject.SayHello('C# Button')" });
}

现在,JavaScript事件处理程序将被调用,您将看到“C#按钮”警报。

@he_the_great,我发布的HTML片段在以你的项目为基础时完美地运行。我确实看到了来自alert("Hello from event handler")的警告。如果时间允许,我会上传完整的项目。 - noseratio - open to work
我已经使用您提供的示例更新了项目,请注意我进行了更改,以便添加了一个C#按钮和一个Javascript按钮来触发事件。JS按钮可以正常工作,但是C#按钮无法触发。我正在寻找一个“Hello from: C# button”警报。 - he_the_great
@he_the_great,我已经更新了我的答案以回应你的最后一条评论。 - noseratio - open to work
1
你已成功绕过C#与Javascript使用的ActiveX对象通信。但也许你是正确的,这就是它的实现方式(我无法访问客户端代码)。 - he_the_great
@he_the_great,C#和JavaScript之间有许多通信方式。请查看这个链接这个链接。在您的情况下,您也可以从C#中直接获取对MyObject的引用:var myObject = (MyObject)this.webBrowser1.Document.InvokeScript("eval", new[] { "MyObject.object" }),然后直接调用其方法。 - noseratio - open to work
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接