sharpico:用純c#打造零依賴的.ico圖標生成器,支持.net9與aot編譯

sharpico:用純c#打造零依賴的.ico圖標生成器,支持.net9與aot編譯

網上搜到的很多 ico 製作工具都是針對 favicon 的,其他的要不太重,要不就是收費,於是我把目光重新放在了開源工具上

最后更新 2025/5/27 下午8:27
程序设计实验室
预计阅读 19 分钟
分类
.NET
专题
C# AOT
标签
.NET C# 開源 AOT ICO

前言

最近一直在完善我今年的两款桌面软件:视频剪辑工具 ClipifyAI 文章创作工具 StarBlogPublisher

雖然界面是基本完善了,但圖標還是默認的,顯得很不專業

於是我打算給這倆軟體換個好看的圖標

早在 vb6.0 年代,我用過一款開源的 ico 圖標製作工具,不過現在已經找不到了

網上搜到的很多 ico 製作工具都是針對 favicon 的

其他的要不太重,要不就是收費,於是我把目光重新放在了開源工具上

找到了一个基于 nodejs 实现并且零依赖的(大部分都是依赖于 Magick 这个 C++实现的图片库),然而很遗憾,这个工具在我电脑上并不能使用……

目標

這時我想起來之前曾經用 c# 開發了一個圖片格式轉換工具,使用了 .net8 的 aot 功能,可以像 go 語言那樣開發出跨平台的單可執行文件

所以我決定繼續使用 c# 開發一個圖標生成工具,這個工具可以實現:

  • 純 c# 實現,沒有外部依賴,跨平台,單可執行文件,aot
  • 將 png 圖片分解為多張不同尺寸的小圖片(邊長 16, 32, 48, 64, 128, 256, 512),然後合成一張 ico 圖標,實現不同尺寸屏幕的良好視覺體驗
  • 支持 inspect 功能,可以讀取並分析 ico 圖標
  • 方便的分發方式,支持 dotnet tool, scoop, brew 等工具一鍵安裝

目前已經完成了,並且也發布到了 nuget 和 scoop,接下來再研究下如何發布到 brew

項目主頁: https://github.com/star-plan/sharp-ico

實現

在 SharpIco 中,.ico 文件的生成完全不依赖 ImageMagick 或任何图像处理外部工具,而是通过纯 C# 代码手工拼接符合规范的 ICO 二进制结构。

这部分的核心类是 IcoGenerator,具体代码我就不贴了,在项目里有,挑几个要点介绍吧~

生成多尺寸圖像

使用 imagesharp 生成多尺寸圖像

var clone = original.Clone(ctx => ctx.Resize(size, size));
clone.SaveAsPng(ms);
  • 使用 imagesharp 的 resize 與 clone 功能,將原始高解析度 png 生成多個目標尺寸(如 16x16、32x32、256x256)
  • PNG 格式保存到内存流中,用于后续写入 .ico

💡 ico 文件支持嵌入 png 圖像(從 vista 開始),這樣可以保持更小體積與更佳透明度效果。

手動構建 ico 文件頭

按照 ico 文件格式手動構建 icondir 與 icondirentry

ico 文件的頭部由 3 部分組成:

  • ICONDIR(6 字节):固定结构
  • ICONDIRENTRY × N(每个 16 字节):描述每一张嵌入图像的尺寸、偏移
  • Image Data × N:实际图像二进制数据
writer.Write((ushort)0); // Reserved
writer.Write((ushort)1); // Type = icon
writer.Write((ushort)images.Count); // Image count
  • 先寫入 ico 文件頭 icondir
  • 然後循環寫入每一張圖片的描述信息(寬高、位深、數據偏移等)
writer.Write((byte)(img.Width == 256 ? 0 : img.Width)); // 256 用 0 表示
writer.Write((ushort)32); // bits per pixel
writer.Write(image.Length);
writer.Write(offset);

ico 文件中的寬度/高度欄位如果為 0,表示 256。這是 ico 格式的一個特殊規定。

ico 文件格式在表示圖像尺寸時有一個限制:寬度和高度欄位各只有一個字節,值範圍是 0-255。當這些欄位為 0 時,按照規範表示 256 像素。對於大於 256 的尺寸(如 512×512 或 1024×1024),在文件頭中仍然會顯示為 0(即 256),但實際圖像數據可以包含更大尺寸的圖像。

拼接圖像

拼接所有 png 圖像數據

foreach (var image in images) {
    writer.Write(image);
}
  • 所有描述信息寫完後,緊跟著寫入圖像數據本體
  • 由於提前計算了偏移量,確保每張圖像數據可以被系統正確識別和讀取

擴展

支持自定義尺寸

public static void GenerateIcon(string sourcePng, string outputIco, int[] sizes)
  • 默認支持 16~512 的常見尺寸
  • 可通過傳參靈活指定尺寸組合(比如只打包 32/256)

inspect 功能

除了图标生成,SharpIco 还内置了一个图标内容分析工具 IcoInspector,可以帮助开发者深入理解 .ico 文件内部结构,并验证实际包含的图层图像尺寸与位深信息,解决市面上不少图标工具生成不规范 .ico 文件的问题。

這裡簡單居間一下實現思路~

讀取 ico 文件頭

手動讀取 icondir + icondirentry 結構

ico 文件的頭部結構由三個部分組成:

  • ICONDIR(6 字节):标记为图标、记录图像数量
  • ICONDIRENTRY × N(每个 16 字节):记录每张图片的元信息(宽、高、位深、数据偏移等)
  • 圖像數據塊:實際的 png 或 bmp 圖像
ushort reserved = reader.ReadUInt16(); // 必须为0
ushort type = reader.ReadUInt16();     // 1表示图标
ushort count = reader.ReadUInt16();    // 图像数量

隨後逐個讀取圖像條目,保存到內存中用於後續處理。

byte width = reader.ReadByte();
byte height = reader.ReadByte();
ushort bitCount = reader.ReadUInt16();
int sizeInBytes = reader.ReadInt32();
int imageOffset = reader.ReadInt32();

对了,前面介绍过,ICO 头部的空间有限,只能存 8 位,所以如果 width/height 字段为 0,根据规范表示 256,或者是超过 256

提取&解析圖像數據

提取圖像數據,校驗真實解析度

ICO 文件中记录的宽高未必真实,尤其是嵌入 PNG 格式的情况。因此,使用 ImageSharp 对每张图像进行真正解析。

fs.Seek(entry.ImageOffset, SeekOrigin.Begin);
fs.Read(imageData, 0, dataSize);
Image.Load(imageData) → 获取真实 Width 和 Height
  • 通过 GetImageDimensions() 方法判断是否为 PNG,并用 ImageSharp 加载
  • 如果格式錯誤或讀取失敗,回退為頭部聲明的尺寸

這一點可用於檢測某些“偽造 ico”的問題(比如尺寸與圖像內容不一致)

補全並輸出分析結果

所有條目都讀取並解析後,將內容輸出為結構化信息:

正在检查ICO文件: logo.ico
图标数量: 7
- 第1张图像: 16x16, 32bpp, 大小: 840字节, 偏移: 118
- 第2张图像: 32x32, 32bpp, 大小: 1939字节, 偏移: 958
- 第3张图像: 48x48, 32bpp, 大小: 3375字节, 偏移: 2897
- 第4张图像: 64x64, 32bpp, 大小: 4951字节, 偏移: 6272
- 第5张图像: 128x128, 32bpp, 大小: 13782字节, 偏移: 11223
- 第6张图像: 256x256, 32bpp, 大小: 37823字节, 偏移: 25005
- 第7张图像: 512x512, 32bpp, 大小: 114655字节, 偏移: 62828
  注意: 文件头中指定的尺寸为256x256,但实际图像尺寸为512x512

這樣能夠快速確認:

  • 一个 .ico 文件中包含多少图层
  • 每層的真實尺寸與位深
  • 是否存在格式問題(尺寸對不上、大小異常等)

命令行界面設計

sharpico 不只是一個代碼庫,同時也提供了完整的命令行工具,方便在任意開發場景中快速調用,無論是手動使用還是集成進構建腳本都毫無壓力。

这一部分使用了 .NET 的现代 CLI 构建库 System.CommandLine,实现了两个主命令:

  • generate:将 PNG 图像转换为 ICO 图标
  • inspect:检查 ICO 文件的结构与图层信息

命令行使用方法:

sharpico generate -i logo.png -o icon.ico --sizes 16 32 48 256
sharpico inspect icon.ico

🛠️ 生成命令 generate

這個命令支持通過參數控制輸入、輸出路徑和生成圖標的尺寸:

sharpico generate --input logo.png --output app.ico --sizes 16 32 64 256

參數說明:

參數 簡寫 說明
--input -i 源 png 圖像路徑(必須)
--output -o 輸出 ico 文件路徑(必須)
--sizes -s 要生成的圖標尺寸列表,支持多個(默認為常見七種尺寸)

支持多个尺寸同时生成,内部调用 IcoGenerator.GenerateIcon() 实现多图层 .ico 文件创建。

舉個例子:

sharpico generate -i logo.png -o icon.ico -s 32 64 256

輸出將包含三種解析度圖層。

🔍 检查命令 inspect

可以对任意 .ico 文件进行结构检查与验证:

sharpico inspect icon.ico

這會輸出圖標中每一張圖層的:

  • 寬高(聲明與實際尺寸)
  • 位深(bpp)
  • 數據偏移與大小
  • 是否存在頭部與實際圖像尺寸不一致的潛在問題

示例輸出:

正在检查ICO文件: logo.ico
图标数量: 7
- 第1张图像: 16x16, 32bpp, 大小: 840字节, 偏移: 118
- 第2张图像: 32x32, 32bpp, 大小: 1939字节, 偏移: 958
- 第3张图像: 48x48, 32bpp, 大小: 3375字节, 偏移: 2897
- 第4张图像: 64x64, 32bpp, 大小: 4951字节, 偏移: 6272
- 第5张图像: 128x128, 32bpp, 大小: 13782字节, 偏移: 11223
- 第6张图像: 256x256, 32bpp, 大小: 37823字节, 偏移: 25005
- 第7张图像: 512x512, 32bpp, 大小: 114655字节, 偏移: 62828
  注意: 文件头中指定的尺寸为256x256,但实际图像尺寸为512x512

發布

SharpIco 不只是一个库,也不是一个只能源码使用的小工具,而是一个可以 通过 dotnet tool 全平台安装运行支持 AOT 编译优化性能和体积 的专业图标工具。

這次需要同時支持 aot 和傳統發布,所以對項目文件做了一些配置

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <!-- AOT编译设置移至条件属性组 -->
        <InvariantGlobalization>true</InvariantGlobalization>

        <!-- .NET Tool 配置 -->
        <PackAsTool>true</PackAsTool>
        <ToolCommandName>sharpico</ToolCommandName>
        <PackageOutputPath>./nupkg</PackageOutputPath>

        <!-- 包信息 -->
        <PackageId>SharpIco</PackageId>
        <Version>1.0.0</Version>
        <Authors>StarPlan</Authors>
        <Description>SharpIco是一个纯 C# AOT 实现的轻量级图标生成工具,用于生成和检查ICO图标文件。可将一张高分辨率 PNG 图片一键生成标准的 Windows .ico 图标文件,内含多种尺寸(16x16 到 512x512),还可以自定义尺寸。除了图标生成,SharpIco 还内置图标结构分析功能,助你轻松验证 .ico 文件中包含的图层与尺寸。</Description>
        <PackageTags>icon;ico;png;converter,DealiAxy,cli,tool,dotnet-tool,imagesharp</PackageTags>
        <PackageProjectUrl>https://github.com/star-plan/sharp-ico</PackageProjectUrl>
        <RepositoryUrl>https://github.com/star-plan/sharp-ico</RepositoryUrl>
        <PackageLicenseExpression>MIT</PackageLicenseExpression>
        <PackageReadmeFile>README.md</PackageReadmeFile>
    </PropertyGroup>

    <!-- AOT发布专用设置 -->
    <PropertyGroup Condition="'$(PublishAot)' == 'true'">
        <PublishAot>true</PublishAot>
        <TrimMode>full</TrimMode>
        <InvariantGlobalization>true</InvariantGlobalization>
        <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
        <IlcOptimizationPreference>Size</IlcOptimizationPreference>
        <IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
        <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
    </PropertyGroup>

    <ItemGroup>
        <None Include="README.md" Pack="true" PackagePath="\" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="SixLabors.ImageSharp" Version="3.1.8" />
        <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
    </ItemGroup>

</Project>

幾個要點:

定義為 cli 工具(dotnet tool)

<PackAsTool>true</PackAsTool>
<ToolCommandName>sharpico</ToolCommandName>

这使得 SharpIco 可以像任何其他 .NET CLI 工具 一样被全局安装:

dotnet tool install -g SharpIco --add-source ./nupkg

安裝後,只需運行:

sharpico generate -i logo.png -o icon.ico

支持 aot 編譯發布(.net 9 原生支持)

<PublishAot>true</PublishAot>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<TrimMode>full</TrimMode>

通過設置 publishaot=true,sharpico 支持編譯為原生可執行文件

無需 .net 運行時支持,啟動速度極快,適合構建工具鏈或集成環境使用

aot 構建命令示例:

dotnet publish -c Release -r win-x64 /p:PublishAot=true

生成的 sharpico.exe 是一个纯原生的 Windows 可执行文件,无需安装 .NET!

除 Windows 外,也可发布为 linux-x64, osx-arm64 等跨平台目标。

自動發布 nuget

sharpico 採用了完整的 github actions ci/cd 流水線,做到一次打 tag,全平台構建自動完成

只需推送一个符合语义化格式的标签(如 v1.0.0),系统将自动:

  1. 構建並發布 nuget 工具包
  2. 針對 windows/linux/macos 編譯原生 aot 可執行文件
  3. 自動上傳所有產物到 github release 頁面

我之前已經寫過關於如何發布 nuget 的文章了,詳見:

這部分問題不大

不過這次還有些不一樣,之前發布的是類庫和項目模板,這次是 dotnet tool

這類似於 npx 腳本、pip 工具之類的概念

可以使用 dotnet tool 命令安裝和調用

不過這種方式就不能使用 aot,只能使用 framework dependant 方式發布

nuget 發布比較簡單,也是使用 dotnet 命令

dotnet pack -c Release

不過關鍵在於我要用 github action 來自動化構建和發布,這個流程和之前差不多

name: 发布SharpIco
run-name: ${{ github.actor }} 正在发布SharpIco 🚀

on:
  push:
    tags:
      - "v*.*.*"  # 更明确的版本格式匹配

# 为整个工作流设置权限
permissions:
  contents: write
  id-token: write
  issues: write

jobs:
  # 第一步:发布NuGet包
  publish-nuget:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0  # 获取所有历史记录用于版本号计算
      
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 9.0.x
      
      - name: 缓存NuGet包
        uses: actions/cache@v3
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
          restore-keys: |
            ${{ runner.os }}-nuget-
      
      - name: 提取版本号
        id: get_version
        shell: bash
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      - name: 恢复依赖
        run: dotnet restore ./SharpIco/SharpIco.csproj
      
      - name: 运行测试
        run: dotnet test --no-restore

      - name: 构建项目
        run: dotnet build --no-restore -c Release --nologo ./SharpIco/SharpIco.csproj -p:Version=${{ steps.get_version.outputs.VERSION }}
      
      - name: 创建NuGet包
        run: dotnet pack -c Release ./SharpIco/SharpIco.csproj -p:PackageVersion=${{ steps.get_version.outputs.VERSION }} --no-build --output ./nupkg
        
      - name: 发布到NuGet Gallery
        run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_GALLERY_TOKEN }} --source https://api.nuget.org/v3/index.json --skip-duplicate

自動發布到 github release

這個是最折騰的,我反覆調試了十幾次才成功😂

不得不說 github action 的調試太不友好了

直接貼上我最後成功的配置吧

name: 发布SharpIco
run-name: ${{ github.actor }} 正在发布SharpIco 🚀

on:
  push:
    tags:
      - "v*.*.*"  # 更明确的版本格式匹配

# 为整个工作流设置权限
permissions:
  contents: write
  id-token: write
  issues: write

jobs:
  # 第二步:编译各平台可执行文件
  build-executables:
    needs: publish-nuget  # 确保在NuGet包发布后运行
    strategy:
      fail-fast: false
      matrix:
        kind: ['windows', 'linux', 'macOS']
        include:
          - kind: windows
            os: windows-latest
            target: win-x64
            extension: '.zip'
          - kind: linux
            os: ubuntu-latest
            target: linux-x64
            extension: '.tar.gz'
          - kind: macOS
            os: macos-latest
            target: osx-x64
            extension: '.tar.gz'

    runs-on: ${{ matrix.os }}
    
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0  # 获取所有历史记录用于版本号计算
      
      - name: 提取版本号
        id: get_version
        shell: bash
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 9.0.x
      
      - name: 缓存NuGet包
        uses: actions/cache@v3
        with:
          path: ~/.nuget/packages
          key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
          restore-keys: |
            ${{ runner.os }}-nuget-
      
      - name: 安装Linux依赖
        if: matrix.kind == 'linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y clang zlib1g-dev libkrb5-dev

      - name: 设置Windows环境
        if: matrix.kind == 'windows'
        shell: pwsh
        run: |
          Write-Host "设置Windows编译环境..."
          # 确保有最新的开发者工具
          choco install visualstudio2022buildtools -y --no-progress
      
      - name: 恢复依赖
        run: dotnet restore ./SharpIco/SharpIco.csproj
      
      - name: AOT编译
        run: |
          echo "正在为 ${{ matrix.kind }} 平台进行AOT编译..."
          dotnet publish ./SharpIco/SharpIco.csproj -c Release -r ${{ matrix.target }} --self-contained true -p:PublishAot=true -p:Version=${{ steps.get_version.outputs.VERSION }} -o ./publish/${{ matrix.kind }}
      
      - name: 打包Windows可执行文件
        if: matrix.kind == 'windows'
        run: |
          cd ./publish/${{ matrix.kind }}
          7z a -tzip ../../SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }} *
      
      - name: 打包Linux/macOS可执行文件
        if: matrix.kind != 'windows'
        run: |
          cd ./publish/${{ matrix.kind }}
          tar -czvf ../../SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }} *
      
      # 上传构建产物作为工作流构件(artifacts)
      - name: 上传构建产物
        uses: actions/upload-artifact@v4
        with:
          name: SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}
          path: ./SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }}
          retention-days: 1

  # 第三步:统一上传所有平台可执行文件到GitHub Release
  upload-to-release:
    needs: build-executables
    runs-on: ubuntu-latest
    
    steps:
      - name: 提取版本号
        id: get_version
        shell: bash
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      # 下载所有平台构建产物
      - name: 下载Windows构建产物
        uses: actions/download-artifact@v4
        with:
          name: SharpIco-windows-${{ steps.get_version.outputs.VERSION }}
          path: ./artifacts
      
      - name: 下载Linux构建产物
        uses: actions/download-artifact@v4
        with:
          name: SharpIco-linux-${{ steps.get_version.outputs.VERSION }}
          path: ./artifacts
      
      - name: 下载macOS构建产物
        uses: actions/download-artifact@v4
        with:
          name: SharpIco-macOS-${{ steps.get_version.outputs.VERSION }}
          path: ./artifacts
      
      # 列出下载的文件以确认
      - name: 列出下载的文件
        run: ls -la ./artifacts
      
      # 统一上传到GitHub Release
      - name: 上传所有文件到GitHub Release
        uses: softprops/action-gh-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          files: ./artifacts/*
          tag_name: ${{ github.ref }}
          fail_on_unmatched_files: false
          draft: false
          name: SharpIco 版本 ${{ steps.get_version.outputs.VERSION }}
          generate_release_notes: true 

工作流總覽

整個工作流由三大 job 組成:

階段 描述
publish-nuget 编译项目并发布 .nupkg 包到 NuGet.org
build-executables 為三大平台編譯 aot 原生可執行文件
upload-to-release 將所有產物上傳到當前 tag 對應的 github release

發布 nuget 部分比較容易,這裡就不重複了

編譯三平台 aot 可執行文件(build-executables)

採用 github matrix 構建策略,分別針對:

  • win-x64(Windows 可执行文件,ZIP 打包)
  • linux-x64(Linux ELF,可执行,tar.gz 打包)
  • osx-x64(macOS 可执行,tar.gz 打包)
dotnet publish -c Release -r ${{ matrix.target }} --self-contained true -p:PublishAot=true

每个平台生成对应压缩包,打包后使用 upload-artifact 上传中转。

上傳至 github release(upload-to-release)

這一階段會自動下載之前構建的產物,並打包進當前版本的 release 頁面:

uses: softprops/action-gh-release@v1

发布页面自动生成更新说明(generate_release_notes: true),方便用户查看版本变更。

最終效果如下圖所示:

🔖 v1.0.0
├── SharpIco-windows-v1.0.0.zip
├── SharpIco-linux-v1.0.0.tar.gz
└── SharpIco-macOS-v1.0.0.tar.gz

这里最大的坑就是原本使用 actions/upload-artifact@v3 一直报错,后面查了 issue 发现是已经 deprecate 了

升級到 v4 問題解決

另外就是發布 github release 時老是衝突,一直說已存在

设置了 append 也不行……

後面我改成先上傳中轉,後面一次性發布,才終於成功了😂

小結

SharpIco 是我基于 .NET 9 和 AOT 编译能力打造的一款纯 C# 图标工具,目标是替代繁琐依赖、简化图标生成与验证流程

ImageSharp 图像处理,到 BinaryWriter 手工拼装 ICO 格式;从 System.CommandLine 打造命令行体验,到 GitHub Actions 全流程自动化发布;SharpIco 不仅是一个实用工具,也是一场小而美的工程探索。

它代表了我對工具理想的追求:輕量、純粹、易集成、跨平台、即開即用

  • 無需安裝 python、node 或 imagemagick
  • 支持 aot 編譯,生成原生可執行文件
  • 同時具備圖標生成與結構分析功能
  • 一次發布,自動產出全平台構建結果

歡迎 star/fork/issue/pr,歡迎將它集成進你的構建系統中。

📦 github 項目地址: 👉 https://github.com/star-plan/sharp-ico

Keep Exploring

延伸阅读

更多文章
同分类 / 同专题 2026/2/7

aot使用經驗總結

從項目創建伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 aot 發布測試。

继续阅读
同分类 / 同专题 2023/8/29

.NET 8.0 AOT DebugView

debugview 是一個應用程式,支持你監視本地系統上或可通過 tcp/ip 訪問的網絡上任何計算機上的調試輸出。

继续阅读