#pragma once #include #include #include #include "strcode.h" #include "mpstr.h" #include #include #include #include "syncutil.h" // Generated by ChatGTP using namespace System; using namespace System::Threading; using namespace System::Reflection; std::wstring GetLastErrorString () { DWORD errCode = GetLastError (); if (errCode == 0) return L"No error"; wchar_t* msgBuffer = nullptr; // FORMAT_MESSAGE_ALLOCATE_BUFFER: 让系统分配缓冲区 // FORMAT_MESSAGE_FROM_SYSTEM: 从系统获取错误信息 // FORMAT_MESSAGE_IGNORE_INSERTS: 忽略 %1 %2 DWORD size = FormatMessageW ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errCode, 0, // 默认语言 (LPWSTR)&msgBuffer, 0, nullptr ); std::wstring msg; if (size && msgBuffer) { msg = msgBuffer; LocalFree (msgBuffer); // 释放缓冲区 } else { msg = L"Unknown error code: " + std::to_wstring (errCode); } return msg; } CriticalSection g_download_cs; public ref class DownloadHelper { public: static void DownloadFile ( String^ httpUrl, String^ savePath, Object^ onProgress, Object^ onComplete, Object^ onError) { DownloadHelper^ obj = gcnew DownloadHelper (); obj->m_url = httpUrl; obj->m_savePath = savePath; obj->cbProgress = onProgress; obj->cbComplete = onComplete; obj->cbError = onError; Thread^ th = gcnew Thread (gcnew ThreadStart (obj, &DownloadHelper::Worker)); th->IsBackground = true; th->Start (); } private: HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr; void CancelHttpHandle (HINTERNET hInternet) { if (hInternet) WinHttpCloseHandle (hInternet); hInternet = nullptr; } std::wstring FormatSpeed (long long speed) { if (speed < 0) return L"--/s"; const wchar_t* units [] = {L"B/s", L"KB/s", L"MB/s", L"GB/s", L"TB/s"}; double s = (double)speed; int idx = 0; while (s >= 1024.0 && idx < 4) { s /= 1024.0; if (s / 1024.0 < 1) break; idx++; } wchar_t buf [64]; swprintf (buf, 64, L"%.2f %s", s, units [idx]); return buf; } public: ~DownloadHelper () { if (hSession) CancelHttpHandle (hSession); if (hConnect) CancelHttpHandle (hConnect); if (hRequest) CancelHttpHandle (hRequest); } private: void Worker () { CreateScopedLock (g_download_cs); std::wstring url = MPStringToStdW (m_url); std::wstring outPath = MPStringToStdW (m_savePath); URL_COMPONENTS urlComp = {0}; urlComp.dwStructSize = sizeof (urlComp); wchar_t host [256]; wchar_t path [2048]; urlComp.lpszHostName = host; urlComp.dwHostNameLength = _countof (host); urlComp.lpszUrlPath = path; urlComp.dwUrlPathLength = _countof (path); if (!WinHttpCrackUrl (url.c_str (), 0, 0, &urlComp)) { ReportError (outPath, L"WinHttpCrackUrl failed: " + GetLastErrorString ()); return; } BOOL isHttps = (urlComp.nScheme == INTERNET_SCHEME_HTTPS); hSession = WinHttpOpen ( L"MyDownloader", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { ReportError (outPath, L"WinHttpOpen failed: " + GetLastErrorString ()); return; } DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; WinHttpSetOption ( hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof (protocols) ); hConnect = WinHttpConnect ( hSession, urlComp.lpszHostName, urlComp.nPort, 0); if (!hConnect) { CancelHttpHandle (hSession); hSession = nullptr; ReportError (outPath, L"WinHttpConnect failed: " + GetLastErrorString ()); return; } hRequest = WinHttpOpenRequest ( hConnect, L"GET", urlComp.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, isHttps ? WINHTTP_FLAG_SECURE : 0); DWORD redirect = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS; WinHttpSetOption ( hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirect, sizeof (redirect) ); if (!hRequest) { CancelHttpHandle (hConnect); hConnect = nullptr; CancelHttpHandle (hSession); hSession = nullptr; ReportError (outPath, L"WinHttpOpenRequest failed: " + GetLastErrorString ()); return; } LPCWSTR ua = L"Mozilla/5.0 (Windows NT 6.2; Win32; x86) AppInstallerUpdater/1.0"; WinHttpAddRequestHeaders ( hRequest, (std::wstring (L"User-Agent: ") + ua).c_str (), -1, WINHTTP_ADDREQ_FLAG_ADD ); if (!WinHttpSendRequest (hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { CancelHttpHandle (hRequest); CancelHttpHandle (hConnect); CancelHttpHandle (hSession); ReportError (outPath, L"WinHttpSendRequest failed: " + GetLastErrorString ()); return; } if (!WinHttpReceiveResponse (hRequest, NULL)) { CancelHttpHandle (hRequest); hRequest = nullptr; CancelHttpHandle (hConnect); hConnect = nullptr; CancelHttpHandle (hSession); hSession = nullptr; DWORD status = 0; DWORD size = sizeof (status); WinHttpQueryHeaders ( hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, NULL, &status, &size, NULL ); if (status >= 400) { ReportError (outPath, L"HTTP error: " + std::to_wstring (status)); return; } ReportError (outPath, L"WinHttpReceiveResponse failed: " + GetLastErrorString ()); return; } // ---- 获取 Content-Length ---- DWORD dwSize = sizeof (DWORD); DWORD fileSize = 0; WinHttpQueryHeaders ( hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &fileSize, &dwSize, NULL); HANDLE hOut = CreateFileW (outPath.c_str (), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hOut == INVALID_HANDLE_VALUE) { CancelHttpHandle (hRequest); hRequest = nullptr; CancelHttpHandle (hConnect); hConnect = nullptr; CancelHttpHandle (hSession); hSession = nullptr; ReportError (outPath, L"Cannot create output file: " + GetLastErrorString ()); return; } BYTE buffer [8192]; DWORD bytesRead = 0; DWORD bytesWritten = 0; long long received = 0; auto t0 = std::chrono::high_resolution_clock::now (); auto lastCheck = t0; unsigned long long lastBytes = 0; while (WinHttpReadData (hRequest, buffer, sizeof (buffer), &bytesRead) && bytesRead > 0) { WriteFile (hOut, buffer, bytesRead, &bytesWritten, NULL); received += bytesRead; auto now = std::chrono::high_resolution_clock::now (); double intervalSec = std::chrono::duration (now - lastCheck).count (); long long speed = -1; // -1 表示“保持上次速度” // 每 0.5 秒刷新一次速度(可调) if (intervalSec >= 0.5) { unsigned long long bytesInInterval = received - lastBytes; if (intervalSec > 0) speed = (long long)(bytesInInterval / intervalSec); // B/s lastCheck = now; lastBytes = received; } ReportProgress (received, fileSize, speed); } CloseHandle (hOut); CancelHttpHandle (hRequest); hRequest = nullptr; CancelHttpHandle (hConnect); hConnect = nullptr; CancelHttpHandle (hSession); hSession = nullptr; ReportComplete (outPath, received); } // ---------------- 回调组装 JSON ---------------- void ReportProgress (long long received, long long total, long long speed) { if (!cbProgress) return; rapidjson::StringBuffer buf; rapidjson::Writer w (buf); w.StartObject (); w.Key ("received"); w.Uint64 (received); w.Key ("total"); w.Uint64 (total); std::wstring speedText = FormatSpeed (speed); std::string speedUtf8 = WStringToString (speedText, CP_UTF8); w.Key ("speed"); w.String (speedUtf8.c_str ()); w.Key ("progress"); w.Double (received / (double)total * 100); w.EndObject (); CallJS (cbProgress, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8))); } void ReportComplete (const std::wstring& file, long long size) { if (!cbComplete) return; rapidjson::StringBuffer buf; rapidjson::Writer w (buf); w.StartObject (); w.Key ("file"); w.String (WStringToString (file, CP_UTF8).c_str ()); w.Key ("status"); w.String ("ok"); w.Key ("size"); w.Uint64 (size); w.EndObject (); CallJS (cbComplete, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8))); } void ReportError (const std::wstring& file, const std::wstring &reason) { if (!cbError) return; rapidjson::StringBuffer buf; rapidjson::Writer w (buf); w.StartObject (); w.Key ("file"); w.String (WStringToString (file, CP_UTF8).c_str ()); w.Key ("status"); w.String ("failed"); w.Key ("reason"); w.String (WStringToString (reason, CP_UTF8).c_str ()); w.EndObject (); CallJS (cbError, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8))); } // ---------------- 调用 JS 回调 ---------------- void CallJS (Object^ jsFunc, String^ arg) { if (!jsFunc) return; try { jsFunc->GetType ()->InvokeMember ( "call", BindingFlags::InvokeMethod, nullptr, jsFunc, gcnew array{ 1, arg } ); } catch (...) { // 失败可忽略 } } private: String^ m_url; String^ m_savePath; Object^ cbProgress; Object^ cbComplete; Object^ cbError; }; using namespace System::Runtime::InteropServices; [ComVisible (true)] public ref class _I_Download { public: void WorkAsync (String ^httpurl, String ^saveFilePath, Object ^onComplete, Object ^onError, Object ^onProgress) { auto download = gcnew DownloadHelper (); download->DownloadFile (httpurl, saveFilePath, onProgress, onComplete, onError); } };