C# (托管代码) 和 JavaScript (非托管代码) 通过一个名为 ObjectForScripting 的桥梁对象进行双向调用。

核心原理
WebBrowser 控件内部使用的是 IE 的渲染引擎(在旧版 .NET 中)或 Edge 的旧版渲染引擎(在较新 .NET Framework 版本中),它提供了一种机制,允许你将 .NET 的对象暴露给网页中的 JavaScript,同时也可以在 C# 中执行网页里的 JavaScript 函数。
这个通信过程是异步的。
准备工作:设置 ObjectForScripting
这是实现 C# -> JavaScript 通信的关键一步。
-
创建一个可被 COM 识别的 C# 类 这个类必须被标记为
[ComVisible(true)],并且必须有一个[ComVisible(true)]的公共构造函数,这个类会包含一些公共方法,这些方法就是网页可以调用的。
(图片来源网络,侵删)using System; using System.Runtime.InteropServices; // 需要引入这个命名空间 [ComVisible(true)] public class ScriptingObject { // 公共方法,可以被 JavaScript 调用 public void ShowMessageFromJS(string message) { // 在 C# 端处理从 JS 传来的数据 MessageBox.Show($"从网页收到了消息: {message}"); } public int AddNumbers(int a, int b) { return a + b; } } -
将此类实例赋值给
WebBrowser控件的ObjectForScripting属性 通常在窗体的构造函数或Load事件中完成。public partial class MainForm : Form { private ScriptingObject scriptingObject; public MainForm() { InitializeComponent(); // 确保网页加载完成后再进行交互 this.webBrowser1.DocumentCompleted += WebBrowser1_DocumentCompleted; } private void MainForm_Load(object sender, EventArgs e) { // 创建桥梁对象 scriptingObject = new ScriptingObject(); // 将对象暴露给网页 this.webBrowser1.ObjectForScripting = scriptingObject; // 加载本地 HTML 文件(推荐这种方式进行测试) this.webBrowser1.Navigate("file:///" + Application.StartupPath + "/index.html"); } private void WebBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { // 网页加载完成后,可以在这里执行一些初始化操作 // 调用 JS 函数 this.webBrowser1.Document.InvokeScript("initializeCSharp"); } }
通信方式详解
C# 调用 JavaScript (C# -> JS)
使用 WebBrowser.Document.InvokeScript(string methodName, object[] args) 方法。
methodName: 要调用的 JavaScript 函数名。args: 传递给 JavaScript 函数的参数数组(可选)。
示例:
C# 代码:

// 在某个按钮的点击事件中
private void btnCallJS_Click(object sender, EventArgs e)
{
// 获取网页文档
HtmlDocument doc = this.webBrowser1.Document;
// 调用无参数的 JS 函数
doc.InvokeScript("showAlertFromCSharp");
// 调用带参数的 JS 函数
string message = "你好,来自 C# 的问候!";
doc.InvokeScript("updatePageContent", new object[] { message });
}
JavaScript 代码 (index.html):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">WebBrowser 通信测试</title>
<style>
body { font-family: sans-serif; }
#output { border: 1px solid #ccc; padding: 10px; margin-top: 10px; }
</style>
</head>
<body>
<h1>WebBrowser 通信测试页</h1>
<button onclick="callCSharpFunction()">调用 C# 方法</button>
<div id="output">等待通信...</div>
<script>
// C# 可以调用的函数
function showAlertFromCSharp() {
alert("Hello from C#!");
}
function updatePageContent(message) {
document.getElementById('output').innerText = message;
}
// C# 在 DocumentCompleted 事件中调用的函数
function initializeCSharp() {
console.log("C# 已初始化,可以开始通信了。");
}
// 供网页按钮调用的函数,该函数会调用 C#
function callCSharpFunction() {
// window.external 是 C# 暴露给 JS 的默认对象
if (window.external) {
// 调用 C# ScriptingObject 中定义的方法
window.external.ShowMessageFromJS("你好,我是网页!");
var result = window.external.AddNumbers(10, 25);
document.getElementById('output').innerText = "C# 计算结果: " + result;
} else {
alert("无法连接到 C# 宿主。");
}
}
</script>
</body>
</html>
JavaScript 调用 C# (JS -> C#)
这是通过 ObjectForScripting 实现的,在 JavaScript 中,window.external 对象就代表了你在 C# 中赋值给 ObjectForScripting 的那个实例。
JavaScript 代码:
// 在 JS 中,通过 window.external 访问 C# 对象
function callCSharpFunction() {
if (window.external) {
// 调用 C# 类的公共方法
window.external.ShowMessageFromJS("你好,我是网页!");
var result = window.external.AddNumbers(10, 25);
document.getElementById('output').innerText = "C# 计算结果: " + result;
} else {
alert("无法连接到 C# 宿主。");
}
}
C# 代码 (ScriptingObject.cs):
[ComVisible(true)]
public class ScriptingObject
{
// 公共方法,可以被 JavaScript 调用
public void ShowMessageFromJS(string message)
{
// 在 C# 端处理从 JS 传来的数据
// 注意:这里不能直接操作 UI,因为 JS 调用是异步的,
// 可能发生在非 UI 线程上。
// 正确做法是使用 Control.Invoke
this.webBrowser1.Invoke((MethodInvoker)delegate {
MessageBox.Show($"从网页收到了消息: {message}");
});
}
public int AddNumbers(int a, int b)
{
return a + b;
}
}
重要提示:线程安全
WebBrowser 控件及其相关的 HtmlDocument 不是线程安全的,当 InvokeScript 或 ObjectForScripting 中的方法被调用时,它们可能运行在后台线程,如果这些方法需要更新 UI(在窗体上显示消息),必须使用 Control.Invoke 或 Control.BeginInvoke 将 UI 更新操作调度到 UI 线程。
完整工作流程总结
-
项目设置:
- 在 Visual Studio 中创建一个 "Windows 窗体应用 (.NET Framework)" 项目。
- 从工具箱中拖拽一个
WebBrowser控件到窗体上。 - 拖拽一个
Button控件用于测试 C# 调用 JS。
-
创建 C# 桥梁类 (
ScriptingObject.cs):- 创建一个新类,标记为
[ComVisible(true)]。 - 在类中定义
public方法,这些方法将被 JS 调用。 - 在方法内部,如果需要更新 UI,使用
this.Invoke。
- 创建一个新类,标记为
-
窗体代码 (
MainForm.cs):- 在窗体加载事件 (
Form_Load) 中:- 实例化
ScriptingObject。 - 将实例赋值给
webBrowser1.ObjectForScripting。 - 使用
webBrowser1.Navigate()加载你的 HTML 文件。
- 实例化
- 为
WebBrowser.DocumentCompleted事件添加处理程序,用于在页面加载完成后执行初始化或调用 JS。 - 在某个事件(如按钮点击)中,使用
webBrowser1.Document.InvokeScript()来调用 JS 函数。
- 在窗体加载事件 (
-
创建 HTML 文件 (
index.html):- 将此文件添加到你的项目中,并设置其 "复制到输出目录" 属性为 "如果较新则复制"。
- 在 HTML 的
<script>标签中:- 定义一些函数,供 C# 调用。
- 使用
window.external来调用 C#ScriptingObject中定义的方法。
重要注意事项和最佳实践
-
安全风险:
- 绝对不要 从不受信任的来源加载网页,因为这意味着你将完全信任该网页执行的任何 C# 代码,这是一个巨大的安全漏洞。
ObjectForScripting相当于给了网页代码管理员权限。 - 始终加载本地或你完全信任的 HTML 文件。
- 绝对不要 从不受信任的来源加载网页,因为这意味着你将完全信任该网页执行的任何 C# 代码,这是一个巨大的安全漏洞。
-
异步性:
- 所有通信都是异步的,不要期望
InvokeScript或 JS 调用 C# 方法后会立即得到结果并继续执行,如果需要返回值,InvokeScript会返回它,但 JS 调用 C# 的返回值则无法直接获取(除非通过其他方式,如再次调用 JS 函数)。
- 所有通信都是异步的,不要期望
-
调试:
- 调试 C# 调用 JS: 在 C# 代码中设置断点,然后执行,JS 代码出错,
InvokeScript可能会抛出异常。 - 调试 JS 调用 C#: 在 C# 的
ScriptingObject方法中设置断点,在网页的 JavaScript 代码中,可以使用console.log()输出信息,但这些信息不会在 Visual Studio 的输出窗口中显示,最简单的方法是在 C# 方法中用MessageBox或Debug.WriteLine来确认是否被调用。
- 调试 C# 调用 JS: 在 C# 代码中设置断点,然后执行,JS 代码出错,
-
现代化替代方案:
WebBrowser控件基于旧的 IE/EdgeHTML 引擎,已过时,性能和兼容性都有问题。- 对于新的 .NET 项目(如 .NET 5/6/7/8),强烈推荐使用
WebView2控件。WebView2基于强大的 Chromium 引擎,性能更好,更安全,并且提供了更现代、更强大的通信机制(如CoreWebView2的PostWebMessageAsJson和WebMessageReceived事件),是未来的发展方向。
