JavaScript XMLHttpRequest 受限於單執行緒與瀏覽器並發限制,需由 C# DLL 提供多執行緒非同步 HTTP 下載,完成後回呼 JS 函式。
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
public class AsyncHttpClient
{
// 回呼委派定義 (供 JS/COM 呼叫)
public delegate void CallbackDelegate(string url, string content, int errorCode);
private CallbackDelegate _callback;
///
/// 設定完成回呼
///
public void SetCallback(CallbackDelegate callback)
{
_callback = callback;
}
///
/// 非同步 GET 請求 (多執行緒)
///
public void AsyncGet(string url, int timeoutMs = 30000)
{
Task.Run(async () =>
{
string content = null;
int errorCode = 0;
try
{
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromMilliseconds(timeoutMs);
content = await client.GetStringAsync(url);
}
}
catch (TaskCanceledException)
{
errorCode = -1; // Timeout
content = "timeout";
}
catch (Exception ex)
{
errorCode = -2; // Other error
content = ex.Message;
}
// 執行回呼 (確保在正確執行緒/上下文)
_callback?.Invoke(url, content, errorCode);
});
}
}
[ComVisible(true)]
[Guid("YOUR-GUID-HERE")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IAsyncHttpClient
{
void SetCallback([MarshalAs(UnmanagedType.IDispatch)] object callback);
void AsyncGet(string url, int timeoutMs);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("YOUR-CLASS-GUID")]
[ProgId("MyHttpClient.AsyncHttpClient")]
public class ComAsyncHttpClient : IAsyncHttpClient
{
private readonly AsyncHttpClient _inner = new AsyncHttpClient();
public void SetCallback(object callback)
{
// JS 函式物件轉為委派
_inner.SetCallback((url, content, errorCode) =>
{
try
{
// 動態呼叫 JS 函式
var callbackType = callback.GetType();
callbackType.InvokeMember("",
System.Reflection.BindingFlags.InvokeMethod,
null, callback, new object[] { url, content, errorCode });
}
catch { /* 忽略 JS 端錯誤 */ }
});
}
public void AsyncGet(string url, int timeoutMs) => _inner.AsyncGet(url, timeoutMs);
}
regasm MyHttpClient.dll /codebase /tlb
<script>
var http = new ActiveXObject("MyHttpClient.AsyncHttpClient");
http.SetCallback(function(url, content, errorCode) {
if (errorCode === 0) {
console.log("成功:", url, content.substring(0, 100));
document.getElementById("result").innerText = content;
} else if (errorCode === -1) {
alert("請求逾時: " + url);
} else {
alert("錯誤: " + content);
}
});
http.AsyncGet("https://api.example.com/data", 5000);
</script>
| 項目 | 說明 |
|---|---|
| ComVisible | 組件/類別/介面必須標記,才能被 COM/JS 存取 |
| IDispatch | 支援晚期繫結,JS 可動態呼叫 |
| 回呼封送 | JS 函式物件 → object → InvokeMember 動態呼叫 |
| 執行緒安全 | C# 回呼在 ThreadPool 執行緒,JS 為單執行緒,通常無衝突 |
| 錯誤代碼 | 約定:0=成功, -1=逾時, -2=其他錯誤 |
| 部署 | 需目標機器註冊 DLL,或用免註冊 COM (Manifest) |
此模式解決了舊版 Web 專案 (IE/HTA/HTC) 的並發下載限制,現代開發建議採用上述替代架構。