using Microsoft.ML; using Microsoft.ML.Data; using Microsoft.ML.Transforms.Onnx; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using WinFormsApp3; //https://huggingface.co/protectai/gyr66-bert-base-chinese-finetuned-ner-onnx/tree/main //model.onnx //vocab.txt namespace WinFormsApp3 { class Program { [STAThread] // ← 加上这个,99% 立刻解决你的错误 static int Main(string[] args) { // 全局未处理异常兜底:写日志而不是直接闪退,放到最前面 AppDomain.CurrentDomain.UnhandledException += (s, e) => { try { File.WriteAllText("crash.log", $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {(e.IsTerminating ? "FATAL" : "NON-FATAL")}\n{e.ExceptionObject}"); } catch { } }; // 命令行下载:WinFormsApp3.exe --download <视频地址> [--out 保存路径] [--referer 页面地址] if (args.Length > 0 && string.Equals(args[0], "--download", StringComparison.OrdinalIgnoreCase)) { // 下载丢给线程池线程去跑,不要在 STA 主线程上等待异步,否则 COM 线程模式会冲突 return Task.Run(() => DownloadCliAsync(args)).GetAwaiter().GetResult(); } ApplicationConfiguration.Initialize(); try { Application.Run(new Form1()); } catch (Exception ex) { File.WriteAllText("crash.log", $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Application.Run\n{ex}"); throw; } return 0; } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AttachConsole(int dwProcessId); private const int AttachParentProcess = -1; /// /// 命令行下载入口:不创建窗体、不初始化 WebView2,直接把地址下完就退出,方便脚本化验证。 /// private static async Task DownloadCliAsync(string[] args) { // WinExe 默认没有控制台,Attach 到父进程的输出才能在终端里看到结果 AttachConsole(AttachParentProcess); string url = args.Length > 1 ? args[1] : string.Empty; if (string.IsNullOrWhiteSpace(url) || url.StartsWith("--", StringComparison.Ordinal)) { Console.Error.WriteLine("用法: WinFormsApp3.exe --download <视频地址> [--out 保存路径] [--referer 页面地址]"); return 2; } string? referer = GetOption(args, "--referer"); string? outPath = GetOption(args, "--out"); if (string.IsNullOrWhiteSpace(outPath)) { outPath = Path.Combine(Path.GetTempPath(), new VideoDownloader(referer).SuggestedFileName(url, null)); } using CancellationTokenSource cts = new(TimeSpan.FromMinutes(15)); Progress progress = new(message => Console.WriteLine(message.Message)); try { VideoDownloader downloader = new(referer); DownloadResult result = await downloader.DownloadAsync(url, outPath!, progress, cts.Token); Console.WriteLine($"OK file={result.FilePath} bytes={result.Bytes} converted={result.Converted}"); return 0; } catch (Exception ex) { Console.Error.WriteLine($"FAIL {ex.GetType().Name}: {ex.Message}"); return 1; } } /// /// 取 --out / --referer 这类"选项 值"形式的参数。 /// private static string? GetOption(string[] args, string name) { for (int i = 0; i < args.Length - 1; i++) { if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) { return args[i + 1]; } } return null; } } }