Visual Studio 是由微軟開發的一個整合式開發環境(IDE),支援多種程式語言如 C++、C#、VB.NET、Python、JavaScript 等。適用於開發桌面應用程式、網站、雲端服務及行動應用程式。
Visual Studio 支援 Windows 作業系統,另有 Visual Studio for Mac,專為 macOS 設計。
[*.cs] indent_style = space indent_size = 43. 檔案儲存後,格式化會自動遵循這些規則。
您可以透過命令列工具檢查電腦上安裝的 .NET SDK 版本:
dotnet --list-sdks
若出現類似下列結果,表示已安裝的 SDK 版本:
8.0.100 [C:\Program Files\dotnet\sdk] 9.0.100-preview.3.24172.9 [C:\Program Files\dotnet\sdk]
如果未出現您想使用的版本,表示該版本尚未安裝。
dotnet 命令若未看到此選項,您也可以使用 Windows 的開始選單搜尋「Developer Command Prompt for VS」開啟。
Ctrl + `dotnet 命令檢查或操作 SDKusing System;
class Program {
static void Main() {
string name = "世界";
Console.WriteLine($"哈囉, {name}!");
}
}
(x, y) => x + yvar q = list.Where(x => x > 10);await Task.Delay(1000);
CS8618 意思是:在建構函式結束時,非 Nullable 的屬性沒有被初始化,因此 C# 編譯器警告它可能會是 null。
public class Product {
public string Name { get; set; } // 警告 CS8618
}
public class Product {
public string Name { get; set; }
public Product(string name) {
Name = name;
}
}
public class Product {
public string Name { get; set; } = string.Empty;
}
public class Product {
public string? Name { get; set; }
}
→ 適用於 Name 合理允許為 null 的情況。
required 修飾元(C# 11+ 支援)public class Product {
public required string Name { get; set; }
}
// 呼叫端必須初始化
var p = new Product { Name = "手機" }; // OK
null:用 建構函式 或 required。null:改成 string?。string.Empty)。float 與 System::String 相加,例如:
System::String^ tmpStr = "Value: " + someFloat;
當 someFloat 很大或很小時,可能會自動以科學記號(e.g. 1.23E+05)顯示。
System::String::Format 或 ToString 搭配格式字串,指定小數位數並避免科學記號。
using namespace System;
float value = 123456.789f;
// 方法1:String::Format
String^ tmpStr1 = String::Format("Value: {0:F2}", value); // F2 表示小數點後 2 位
Console::WriteLine(tmpStr1);
// 方法2:ToString 搭配格式
String^ tmpStr2 = "Value: " + value.ToString("F2");
Console::WriteLine(tmpStr2);
// 方法3:更多位數
String^ tmpStr3 = "Value: " + value.ToString("F6");
Console::WriteLine(tmpStr3);
F2:固定小數點格式,小數點後 2 位F6:固定小數點格式,小數點後 6 位F{位數}
在 C++/CLI 中,gcnew System::String(stdStr.c_str()) 會將 std::string(通常是 ANSI / UTF-8 編碼)直接轉成 .NET System::String,而 System::String 預期是 UTF-16。
如果 stdStr 內含非 ASCII 字元(例如中文),就會出現亂碼。
marshal_as(建議)需引用命名空間:
#include <msclr/marshal_cppstd.h>
using namespace msclr::interop;
std::string stdStr = "中文測試";
System::String^ netStr = marshal_as<System::String^>(stdStr);
✅ 此方法能自動依 UTF-8 / ANSI 正確轉成 .NET Unicode。
---#include <codecvt>
#include <locale>
std::string utf8Str = u8"中文測試";
std::wstring wideStr = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>{}.from_bytes(utf8Str);
System::String^ netStr = gcnew System::String(wideStr.c_str());
---
std::wstring若 C++ 端原本使用寬字元字串,直接使用即可:
std::wstring wstr = L"中文測試";
System::String^ netStr = gcnew System::String(wstr.c_str());
---
std::string 來源是 UTF-8(常見於現代系統)→ 用 marshal_as 或 codecvt。gcnew String(stdStr.c_str()),除非字串是純英文。
在 .NET(C++/CLI、C#、VB.NET 等語言)中,List<T> 是一個泛型集合類別,
可儲存任意型別的資料(T),並提供動態調整大小的功能。
它屬於 System::Collections::Generic 命名空間。
// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;
int main()
{
// 宣告一個 List 儲存 int
List<int>^ numbers = gcnew List<int>();
// 初始化時直接加入元素
List<String^>^ names = gcnew List<String^>({ "Alice", "Bob", "Charlie" });
}
List<int>^ nums = gcnew List<int>();
// 新增元素
nums->Add(10);
nums->Add(20);
nums->Add(30);
// 插入指定位置
nums->Insert(1, 15); // 在索引 1 插入 15
// 移除指定值
nums->Remove(20);
// 移除指定索引
nums->RemoveAt(0);
// 取得元素
int value = nums[1]; // 取得索引 1 的元素
// 檢查是否包含
if (nums->Contains(30))
Console::WriteLine("找到 30");
// 清空所有元素
nums->Clear();
// for 迴圈
for (int i = 0; i < nums->Count; i++)
{
Console::WriteLine("第 {0} 個元素: {1}", i, nums[i]);
}
// for each 迴圈
for each (int n in nums)
{
Console::WriteLine(n);
}
| 屬性 / 方法 | 說明 |
|---|---|
Count | 目前元素數量 |
Capacity | 內部容量(可自動成長) |
Add(item) | 加入一個元素 |
AddRange(collection) | 加入另一個集合的所有元素 |
Insert(index, item) | 在指定位置插入元素 |
Remove(item) | 移除指定值(找到第一個匹配項) |
RemoveAt(index) | 移除指定索引的元素 |
Clear() | 移除所有元素 |
Contains(item) | 檢查是否包含指定值 |
IndexOf(item) | 回傳元素索引(若不存在則為 -1) |
Sort() | 排序元素(適用於可比較型別) |
Reverse() | 反轉元素順序 |
10 15 30
在 .NET(C++/CLI、C#、VB.NET 等語言)中,Dictionary<TKey, TValue> 是一種泛型集合,
用於儲存「鍵值對 (key-value pair)」。每個 key 必須唯一,而 value 則可重複。
它屬於 System::Collections::Generic 命名空間。
// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;
int main()
{
// 建立一個 Dictionary,Key 為 int,Value 為 String
Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
// 直接初始化
users->Add(1, "Alice");
users->Add(2, "Bob");
users->Add(3, "Charlie");
return 0;
}
Dictionary<String^, int>^ ages = gcnew Dictionary<String^, int>();
// 新增元素
ages->Add("Tom", 25);
ages->Add("Jerry", 30);
// 修改值(透過索引器)
ages["Tom"] = 26;
// 新增或修改(若 key 不存在則自動新增)
ages["Spike"] = 40;
// 刪除元素
ages->Remove("Jerry");
// 存取元素
if (ages->ContainsKey("Tom"))
{
Console::WriteLine("Tom 的年齡是 {0}", ages["Tom"]);
}
// 嘗試取得值(避免例外)
int age;
if (ages->TryGetValue("Spike", age))
{
Console::WriteLine("Spike 的年齡是 {0}", age);
}
Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
users->Add(1, "Alice");
users->Add(2, "Bob");
users->Add(3, "Charlie");
// 使用 KeyValuePair 迴圈
for each (KeyValuePair<int, String^> entry in users)
{
Console::WriteLine("Key = {0}, Value = {1}", entry.Key, entry.Value);
}
// 只取 Keys
for each (int key in users->Keys)
{
Console::WriteLine("Key: {0}", key);
}
// 只取 Values
for each (String^ name in users->Values)
{
Console::WriteLine("Name: {0}", name);
}
| 屬性 / 方法 | 說明 |
|---|---|
Add(key, value) | 新增一組鍵值對 |
Remove(key) | 移除指定的鍵及其對應的值 |
Clear() | 清空所有項目 |
ContainsKey(key) | 檢查是否存在指定的鍵 |
ContainsValue(value) | 檢查是否存在指定的值 |
TryGetValue(key, out value) | 安全地取得值,不會拋出例外 |
Count | 目前字典中的元素數量 |
Keys | 回傳所有鍵的集合 |
Values | 回傳所有值的集合 |
Key = 1, Value = Alice Key = 2, Value = Bob Key = 3, Value = Charlie Tom 的年齡是 26 Spike 的年齡是 40
在 .NET C++ 中,可以使用 System::IO 命名空間提供的功能來操作檔案與目錄。
取得目錄中最新的檔案,可以透過讀取所有檔案資訊並比較最後修改日期實現。
以下是一個完整的範例,展示如何取得指定目錄中最新的檔案:
#include "stdafx.h"
#include <iostream>
#include <cliext/vector>
#include <System.IO>
using namespace System;
using namespace System::IO;
using namespace cliext;
int main()
{
try
{
// 指定目錄路徑
String^ directoryPath = "C:\\Your\\Directory\\Path";
// 檢查目錄是否存在
if (!Directory::Exists(directoryPath))
{
Console::WriteLine("目錄不存在: {0}", directoryPath);
return -1;
}
// 取得目錄中的所有檔案
array^ files = Directory::GetFiles(directoryPath);
// 如果目錄中沒有檔案
if (files->Length == 0)
{
Console::WriteLine("目錄中沒有檔案。");
return 0;
}
// 找到最新的檔案
String^ newestFile = nullptr;
DateTime newestTime = DateTime::MinValue;
for each (String^ file in files)
{
// 取得檔案的最後修改時間
DateTime lastWriteTime = File::GetLastWriteTime(file);
// 比較時間並更新最新檔案資訊
if (lastWriteTime > newestTime)
{
newestTime = lastWriteTime;
newestFile = file;
}
}
// 輸出最新檔案資訊
Console::WriteLine("最新檔案: {0}", newestFile);
Console::WriteLine("最後修改時間: {0}", newestTime);
}
catch (Exception^ ex)
{
Console::WriteLine("發生錯誤: {0}", ex->Message);
}
return 0;
}
Directory::GetFiles 方法獲取指定目錄中的所有檔案路徑。File::GetLastWriteTime 方法取得每個檔案的最後修改時間。try-catch 區塊捕獲潛在的異常,例如目錄不存在或無法存取。System.Reflection 是 .NET 框架中的一個命名空間,提供了檢查和操作元數據的工具,使開發者可以在運行時動態檢查類型、方法、屬性等,並動態創建和操縱對象。
Assembly:表示已加載的程序集,提供加載、探索和反射程序集的方法。Type:表示一種類型,包括類別、結構、介面等,提供獲取類型信息的功能。MethodInfo:表示方法資訊,允許開發者檢查方法的屬性並動態調用它們。PropertyInfo:表示屬性資訊,提供對屬性元數據的存取。FieldInfo:表示字段資訊,可用於訪問類型的字段。以下是一個使用 System.Reflection 的範例程式,展示如何動態檢查類型和方法,並調用方法。
// 定義一個簡單的範例類別
public class SampleClass {
public string SayHello(string name) {
return $"Hello, {name}!";
}
}
// 使用 Reflection 來動態調用方法
using System;
using System.Reflection;
class Program {
static void Main() {
// 創建 SampleClass 類別的實例
Type sampleType = typeof(SampleClass);
object sampleInstance = Activator.CreateInstance(sampleType);
// 獲取 SayHello 方法資訊
MethodInfo methodInfo = sampleType.GetMethod("SayHello");
// 動態調用 SayHello 方法
object result = methodInfo.Invoke(sampleInstance, new object[] { "World" });
Console.WriteLine(result); // 輸出: Hello, World!
}
}
在上述範例中,我們使用 Activator.CreateInstance 來創建類別的實例,並使用 MethodInfo.Invoke 來調用方法
SayHello。
System::Management::ManagementClass 是 .NET Framework 中提供用於操作 Windows Management Instrumentation (WMI) 的類別之一。它允許開發者讀取、操作和管理系統資訊,例如硬體、作業系統、網路設定等。
System::Management 是位於 System.Management.dll 中的命名空間,使用此功能需先加入參考。
// C++/CLI 寫法
using namespace System;
using namespace System::Management;
int main() {
try {
ManagementClass^ mc = gcnew ManagementClass("Win32_OperatingSystem");
for each (ManagementObject^ mo in mc->GetInstances()) {
Console::WriteLine("OS 名稱: {0}", mo["Caption"]);
}
}
catch (Exception^ ex) {
Console::WriteLine("錯誤: {0}", ex->Message);
}
return 0;
}
System.Management.dll 的參考。ManagementException。ManagementObjectManagementObjectSearcherManagementBaseObjectnet stop winmgmt winmgmt /resetrepository net start winmgmt
Get-WmiObject Win32_NetworkAdapterConfiguration方式二:WMI 測試工具 (wbemtest)
wbemtestroot\cimv2 並連線SELECT * FROM Win32_NetworkAdapterConfigurationDISM /Online /Cleanup-Image /RestoreHealth 修復系統映像在 .NET C++ (或更常見的 C++/CLI) 專案中,讀取和解析檔案中的 HTML 表格,通常需要藉助一個外部的 HTML 解析函式庫,因為標準的 .NET 框架和 C++ 本身沒有內建強大的 HTML DOM 解析功能。一個常見且高效的選擇是使用 C# 或其他 .NET 語言的函式庫,然後在 C++/CLI 中引用。
對於 C++/CLI 專案,最實用且推薦的方法是使用 Html Agility Pack (HAP),這是一個非常流行的 .NET HTML 解析器。雖然 HAP 是用 C# 編寫的,但它可以無縫地在任何 .NET 語言(包括 C++/CLI)中作為參考使用。
HtmlAgilityPack。以下是一個 C++/CLI 的程式碼片段,演示如何讀取一個本地 HTML 檔案並解析其中的第一個表格 (<table>) 的內容:
#using <System.dll>
#using <System.Xml.dll>
#using <HtmlAgilityPack.dll> // 確保已加入參考
using namespace System;
using namespace System::IO;
using namespace HtmlAgilityPack;
void ParseHtmlTable(String^ filePath)
{
// 檢查檔案是否存在
if (!File::Exists(filePath))
{
Console::WriteLine("錯誤:檔案不存在。");
return;
}
// 1. 載入 HTML 文件
HtmlDocument^ doc = gcnew HtmlDocument();
try
{
// 從檔案載入 HTML
doc->Load(filePath);
}
catch (Exception^ ex)
{
Console::WriteLine("載入檔案時發生錯誤:" + ex->Message);
return;
}
// 2. 選擇第一個 <table> 節點
HtmlNode^ table = doc->DocumentNode->SelectSingleNode("//table");
if (table != nullptr)
{
Console::WriteLine("找到 HTML 表格,開始解析...");
// 3. 選擇所有的 <tr> (表格列) 節點
HtmlNodeCollection^ rows = table->SelectNodes(".//tr");
if (rows != nullptr)
{
for each (HtmlNode^ row in rows)
{
// 4. 選擇 <td> 或 <th> (儲存格) 節點
// 使用 | 運算子選擇 <td> 或 <th>
HtmlNodeCollection^ cells = row->SelectNodes("td | th");
if (cells != nullptr)
{
String^ rowData = "";
for each (HtmlNode^ cell in cells)
{
// 獲取儲存格的內部文字並去除首尾空白
rowData += cell->InnerText->Trim() + "\t";
}
Console::WriteLine(rowData);
}
}
}
else
{
Console::WriteLine("表格中未找到 <tr> 標籤。");
}
}
else
{
Console::WriteLine("檔案中未找到 <table> 標籤。");
}
}
int main(array<String^>^ args)
{
// 請將 "your_html_file.html" 替換為您的實際檔案路徑
String^ htmlFilePath = "C:\\path\\to\\your_html_file.html";
ParseHtmlTable(htmlFilePath);
return 0;
}
doc->Load(filePath):將本地 HTML 檔案讀取到 HAP 的 DOM 結構中。SelectSingleNode("//table"):使用 XPath 表達式選取文件中的第一個 <table> 元素。SelectNodes(".//tr"):選取當前節點(<table>)下的所有 <tr> 元素。SelectNodes("td | th"):選取 <tr> 節點下的所有 <td> 或 <th> 元素。cell->InnerText:獲取儲存格內容的純文字,會自動去除 HTML 標籤。gcnew 和 ^: 這是 C++/CLI 用來建立和管理 .NET 物件(受 CLR 垃圾回收機制管理)的語法。HttpListener 類別允許您在 C++/CLI 應用程式中建立一個簡單的、自託管(Self-Hosted)的 HTTP 伺服器。
在 Visual Studio 中建立一個 C++/CLI 專案(例如 CLR Console Application),並確保已引用 System.dll。
以下是使用 HttpListener 建立一個簡單 API 伺服器並處理 GET 請求的 C++/CLI 程式碼。
#using <System.dll>
using namespace System;
using namespace System::Net;
using namespace System::Threading::Tasks;
using namespace System::Text;
// 處理傳入 HTTP 請求的函式
void HandleRequest(HttpListenerContext^ context)
{
HttpListenerRequest^ request = context->Request;
HttpListenerResponse^ response = context->Response;
// 預設回應設定
response->ContentType = "application/json; charset=utf-8";
String^ responseString = "";
response->StatusCode = 200; // 預設為 OK
// 檢查請求的路徑和方法
String^ url = request->RawUrl->ToLower();
if (url == "/api/status" && request->HttpMethod == "GET")
{
// 處理 GET /api/status 請求
responseString = "{\"status\": \"Server Running\", \"language\": \"C++/CLI\"}";
}
else
{
// 處理其他未定義的請求
response->StatusCode = 404; // Not Found
responseString = "{\"error\": \"404 Not Found\", \"path\": \"" + url + "\"}";
}
// 將字串回應轉換為位元組,並寫入輸出串流
array<Byte>^ buffer = Encoding::UTF8->GetBytes(responseString);
response->ContentLength64 = buffer->Length;
try
{
response->OutputStream->Write(buffer, 0, buffer->Length);
response->OutputStream->Close();
}
catch (Exception^)
{
// 忽略寫入錯誤,通常是客戶端斷開連線
}
}
// 啟動 HttpListener 的函式
void StartListener(String^ prefix)
{
HttpListener^ listener = gcnew HttpListener();
try
{
listener->Prefixes->Add(prefix);
listener->Start();
Console::WriteLine("C++/CLI API Server 啟動,正在監聽: {0}", prefix);
}
catch (Exception^ ex)
{
Console::WriteLine("啟動 HttpListener 發生錯誤。請確認權限或位址是否已被佔用。");
Console::WriteLine("錯誤訊息: {0}", ex->Message);
return;
}
// 異步迴圈,持續接收請求
while (listener->IsListening)
{
try
{
// 同步等待下一個請求
HttpListenerContext^ context = listener->GetContext();
// 使用 Task 異步處理請求,避免阻塞監聽迴圈
Task::Factory->StartNew(gcnew Action<HttpListenerContext^>(&HandleRequest), context);
}
catch (Exception^)
{
// 監聽器停止時會拋出例外
break;
}
}
if (listener->IsListening)
{
listener->Close();
}
}
int main(array<String^>^ args)
{
// 設定監聽的 URL 前綴
// 注意:在 Windows 中,可能需要管理員權限或使用 netsh 註冊 URL 命名空間。
String^ listeningPrefix = "http://localhost:8080/";
StartListener(listeningPrefix);
Console::WriteLine("按下任意鍵結束伺服器...");
Console::ReadKey(true);
return 0;
}
在 Windows 系統中,如果程式碼沒有以管理員權限運行,HttpListener 監聽特定埠時可能會拋出存取拒絕的例外。您可以透過以下命令註冊 URL 命名空間,以允許非管理員帳戶運行伺服器:
netsh http add urlacl url=http://+:8080/ user=Everyone
其中 http://+:8080/ 應替換為您程式碼中實際使用的前綴。
在 ASP.NET Core 中,建立 HTTP API 伺服器的標準專案類型是 Web API。最新的 .NET 版本(.NET 6 及更高版本)推薦使用 Minimal APIs 來快速且輕量地建立 API 端點。
Minimal APIs 使用最少的檔案和程式碼行,將啟動邏輯 (Startup) 和路由定義 (Routing) 合併到一個檔案 Program.cs 中。
要建立一個新的 Minimal API 專案,您可以使用 .NET CLI (命令列介面):
dotnet new web -n MinimalApiServer
cd MinimalApiServer
以下是 Minimal API 伺服器核心檔案 Program.cs 的完整內容。它定義了伺服器的啟動、中介軟體和兩個 API 端點 (一個 GET,一個 POST)。
// 1. 建立 WebApplication 實例 (取代了傳統的 Startup 類別)
var builder = WebApplication.CreateBuilder(args);
// 2. 註冊服務 (Service Configuration)
// 這裡可以加入資料庫連線、驗證服務等
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // 啟用 Swagger/OpenAPI 支援
var app = builder.Build();
// 3. 配置 HTTP 請求管線 (Middleware Configuration)
// 開發環境下,啟用 Swagger UI 以供測試
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// 啟用 HTTPS 重新導向 (生產環境推薦)
// app.UseHttpsRedirection();
// 4. 定義 API 端點 (Endpoint Definition)
// 實體類別用於 POST 請求
public record Product(int Id, string Name, decimal Price);
// GET 請求端點:/api/hello
app.MapGet("/api/hello", () =>
{
return Results.Ok(new { message = "Hello from ASP.NET Core Minimal API!", timestamp = DateTime.Now });
});
// GET 請求端點:/api/products/{id}
app.MapGet("/api/products/{id}", (int id) =>
{
// 假設從資料庫查詢產品
if (id == 1)
{
return Results.Ok(new Product(1, "筆記型電腦", 1200.00m));
}
return Results.NotFound(new { error = $"Product ID {id} not found" });
});
// POST 請求端點:/api/products
app.MapPost("/api/products", (Product product) =>
{
// ASP.NET Core 會自動將 JSON 請求主體解序列化為 Product 實體
Console.WriteLine($"收到新產品: {product.Name} (ID: {product.Id})");
// 實際應用中,這裡會執行資料庫新增操作
// 回傳 201 Created 狀態碼
return Results.Created($"/api/products/{product.Id}", product);
});
// 5. 啟動應用程式,開始監聽 HTTP 請求
app.Run();
dotnet run。http://localhost:5000 或 https://localhost:7000 運行。
/api/hello (GET)。/swagger 路徑可以查看 Swagger UI,用於測試所有定義的 API 端點。app.UseSwagger()、app.UseHttpsRedirection() 等,這些組件按順序構成請求處理管線。每個請求都會流經這些中介軟體。app.MapPost 範例中,當用戶端發送 JSON 數據時,ASP.NET Core 會自動將其轉換(解序列化)為 C# 的 Product 記錄 (Record)。大多數現代 Chromebook 支援透過 Crostini 專案來運行 Linux 應用程式。
設定 > 進階 > 開發人員 > 啟用 Linux (Beta)。sudo apt update
sudo apt install -y dotnet-sdk-7.0
無需在本地安裝任何軟體,即可在 Chromebook 上使用雲端平台運行 .NET 程式。
Chromebook 支援使用容器運行應用程式,您可以透過 Docker 啟動 .NET 環境。
docker pull mcr.microsoft.com/dotnet/runtime:7.0
從 .NET 6 開始,應用程式可以跨多平台運行,包括 Linux。將您的應用程式編譯為跨平台格式並部署至 Chromebook。
dotnet publish -c Release -r linux-x64 --self-contained
using System;
using System.Windows.Forms;
public class MyForm : Form {
public MyForm() {
Button btn = new Button();
btn.Text = "點我";
btn.Click += (s, e) => MessageBox.Show("你點了按鈕!");
Controls.Add(btn);
}
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
// MainWindow.xaml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WPF 範例" Height="200" Width="300">
<StackPanel>
<Button Content="點我" Click="Button_Click"/>
</StackPanel>
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("你點了按鈕!");
}
// MainPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="MyApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30">
<Button Text="點我" Clicked="OnButtonClicked"/>
</VerticalStackLayout>
</ContentPage>
// MainPage.xaml.cs
private void OnButtonClicked(object sender, EventArgs e) {
await DisplayAlert("通知", "你點了按鈕!", "OK");
}
| 技術 | 用途 | 平台 |
|---|---|---|
| WinForms | 快速開發桌面應用 | Windows |
| WPF | 複雜桌面應用、MVVM 模式 | Windows |
| MAUI | 跨平台應用 | Windows、macOS、Android、iOS |
| Blazor | Web 前端開發 | 跨平台(Web) |
以下範例展示了如何使用 Controls->GetEnumerator() 方法來逐一遍歷 .NET 表單中的所有子控制項並批量修改其屬性。
using System;
using System.Drawing;
using System.Windows.Forms;
public class FormExample : Form
{
public FormExample()
{
// 初始化一些控制項
Button button1 = new Button { Text = "Button 1", Location = new Point(10, 10) };
TextBox textBox1 = new TextBox { Location = new Point(10, 50) };
Controls.Add(button1);
Controls.Add(textBox1);
// 使用 GetEnumerator() 遍歷並修改控制項屬性
ModifyControls();
}
private void ModifyControls()
{
var enumerator = Controls.GetEnumerator();
while (enumerator.MoveNext())
{
Control control = (Control)enumerator.Current;
// 設定範例:將所有控制項的背景色設為淺藍
control.BackColor = Color.LightBlue;
// 若控制項為 TextBox,將其設為不可編輯
if (control is TextBox)
{
control.Enabled = false;
}
}
}
// 啟動應用程式
public static void Main()
{
Application.Run(new FormExample());
}
}
**GetEnumerator()**: 利用 `Controls.GetEnumerator()` 方法來取得控制項的列舉器,這樣可以遍歷所有子控制項。
**條件修改**: 在 `while` 迴圈中,對每個 `Control` 物件進行屬性修改,例如將背景色設為淺藍,並根據控制項類型進行特定修改,如將 `TextBox` 設為不可編輯。
**用途**: 這種方法在需要批量修改屬性時非常有效,比如調整 UI 風格或在特定情況下禁用多個控制項。
執行後,所有控制項的背景色會變成淺藍色,且所有 `TextBox` 控制項將被設為不可編輯。
在 C++/CLI 中,MessageBox::Show 是一個靜態方法,用於彈出對話框。最完整的呼叫方式包含:訊息內容、標題、按鈕類型、圖示以及預設按鈕。
using namespace System::Windows::Forms;
// 最簡單的顯示
MessageBox::Show("操作已完成");
// 完整參數版本
DialogResult result = MessageBox::Show(
"您確定要刪除此檔案嗎?", // 訊息內容 (Text)
"確認刪除", // 視窗標題 (Caption)
MessageBoxButtons::YesNo, // 按鈕組合
MessageBoxIcon::Warning, // 提示圖示
MessageBoxDefaultButton::Button2 // 預設聚焦在第二個按鈕 (No)
);
當按鈕包含多個選項(如 Yes/No 或 OK/Cancel)時,必須檢查 DialogResult 來決定後續邏輯。
if (result == DialogResult::Yes) {
// 執行刪除邏輯
} else {
// 取消操作
}
| 類別 | 常用選項 | 說明 |
|---|---|---|
| MessageBoxButtons | OK, OKCancel, YesNo, YesNoCancel | 定義對話框下方出現哪些按鈕。 |
| MessageBoxIcon | Information, Warning, Error, Question | 定義左側顯示的圖示與系統音效。 |
| DialogResult | OK, Cancel, Yes, No | Show 方法的回傳值,代表使用者按了哪個鍵。 |
由於 MessageBox::Show 接收的是 .NET 的 System::String^,如果你有原生 C++ 的資料,需要進行轉換。
#include <string>
#include <msclr/marshal_cppstd.h>
// 將 std::string 顯示在 MessageBox
std::string cpp_str = "核心錯誤";
MessageBox::Show(msclr::interop::marshal_as<System::String^>(cpp_str));
// 將 float 格式化後顯示
float dist = 1.23456f;
MessageBox::Show("距離為: " + dist.ToString("F2")); // 顯示 "距離為: 1.23"
MessageBoxButtons::OK。using namespace System; 和 using namespace System::Windows::Forms;,可能會與其他 MessageBox 定義衝突,建議使用全名 System::Windows::Forms::MessageBox::Show(...)。System::Windows::Forms::MessageBox 的設計初衷是為了顯示「通知」或「警告」,並透過標準按鈕獲取使用者決定。它並不具備動態插入控制項的功能。若需輸入文字,標準做法是繼承 System::Windows::Forms::Form 建立自定義對話框。
這是一個可重複使用的簡單類別,包含了標籤、文字方塊與確定按鈕。
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
public ref class CustomInputBox : public Form {
public:
TextBox^ txtInput;
Button^ btnOK;
Label^ lblPrompt;
CustomInputBox(String^ title, String^ prompt) {
// 設定表單基本屬性
this->Text = title;
this->Size = System::Drawing::Size(300, 150);
this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
this->StartPosition = FormStartPosition::CenterParent;
this->MaximizeBox = false;
this->MinimizeBox = false;
// 提示文字
lblPrompt = gcnew Label();
lblPrompt->Text = prompt;
lblPrompt->Location = Point(15, 15);
lblPrompt->Size = System::Drawing::Size(250, 20);
// 輸入框
txtInput = gcnew TextBox();
txtInput->Location = Point(15, 40);
txtInput->Size = System::Drawing::Size(250, 20);
// 確定按鈕
btnOK = gcnew Button();
btnOK->Text = "確定";
btnOK->DialogResult = System::Windows::Forms::DialogResult::OK;
btnOK->Location = Point(190, 75);
// 加入控制項
this->Controls->Add(lblPrompt);
this->Controls->Add(txtInput);
this->Controls->Add(btnOK);
// 設定預設按鈕(按下 Enter 等同點擊確定)
this->AcceptButton = btnOK;
}
};
使用 ShowDialog() 方法以強制回應方式開啟視窗,並檢查回傳值是否為 OK。
void OnButtonClick() {
CustomInputBox^ ibox = gcnew CustomInputBox("系統輸入", "請輸入檔案名稱:");
if (ibox->ShowDialog() == System::Windows::Forms::DialogResult::OK) {
String^ userInput = ibox->txtInput->Text;
if (!String::IsNullOrWhiteSpace(userInput)) {
// 處理使用者輸入的字串
MessageBox::Show("你輸入了: " + userInput);
}
}
}
| 方案 | 實作複雜度 | 優點 | 缺點 |
|---|---|---|---|
| 自定義 Form | 中 | 完全控制佈局、可加入驗證邏輯(如限數字)。 | 需撰寫較多介面程式碼。 |
| VB Interaction | 低 | 一行程式碼完成。 | 需引用 Microsoft.VisualBasic,樣式固定不可調。 |
| Win32 API | 高 | 無需 .NET 環境。 | 在 C++/CLI 中開發極其瑣碎且難以維護。 |
delete ibox; 或確保其在 scope 結束後正確釋放資源。ShowDialog() 而非 Show(),這樣可以確保使用者必須關閉輸入視窗後才能操作主視窗,且能直接獲取 DialogResult。
PictureBox 顯示紅色打叉,代表在 OnPaint 執行期間發生例外,
但 .NET 預設將此例外吞掉,不會顯示在一般 Log 或中斷點中,導致難以除錯。
| 原因 | 說明 |
|---|---|
| Bitmap 已被 Dispose | PictureBox 仍持有該 Bitmap 的參考,Paint 時存取已釋放的資源 |
| 來源 Stream 已關閉 | 用 Stream 建立 Bitmap 後關閉 Stream,Bitmap 資料失效 |
| 跨執行緒存取 | 背景執行緒直接修改或指派 Bitmap,與 UI 執行緒衝突 |
| GDI+ 資源耗盡 | 大量 Bitmap 未 Dispose,導致 GDI+ 記憶體不足 |
| 例外訊息 | 根本原因 | 解法方向 |
|---|---|---|
| ArgumentException: Parameter is not valid | Bitmap 已被 Dispose 或 Stream 已關閉 | 深複製 Bitmap,不依賴 Stream |
| InvalidOperationException: object is in use elsewhere | 跨執行緒同時存取同一個 Bitmap | 加 lock,或 Invoke 回 UI 執行緒 |
| OutOfMemoryException | GDI+ 資源耗盡或圖片格式不支援 | 及時 Dispose 舊 Bitmap,檢查圖片來源 |
| ExternalException: A generic error occurred in GDI+ | Bitmap 對應的檔案或 Stream 已不存在 | 確保 Bitmap 生命週期內來源仍有效 |
由於紅叉問題不一定常發生,建議依序進行:
OnPaint,
用 try/catch 捕捉例外並輸出至 Debug.WriteLine,
捕捉後執行 this.Image = null 避免持續紅叉。
Environment.StackTrace,找出誰在什麼時間點釋放了資源。
Image 設為 null 再 Dispose。
所有換圖操作一律透過此模式,確保執行緒安全與 Dispose 順序正確:
// C++/CLI
void SafeUpdateImage(Bitmap^ newBmp) {
if (pictureBox1->InvokeRequired) {
pictureBox1->Invoke(gcnew Action<Bitmap^>(
this, &YourClass::SafeUpdateImage), newBmp);
return;
}
Bitmap^ old = safe_cast<Bitmap^>(pictureBox1->Image);
pictureBox1->Image = newBmp; // 先換上新圖
if (old != nullptr) old->Dispose(); // 再 Dispose 舊圖
}
// 錯誤:stream 關閉後 bitmap 失效
using (var stream = new FileStream(path, FileMode.Open)) {
pictureBox1.Image = new Bitmap(stream);
}
// 正確:深複製,不依賴 stream
Bitmap loaded;
using (var stream = new FileStream(path, FileMode.Open))
using (var tmp = new Bitmap(stream)) {
loaded = new Bitmap(tmp);
}
pictureBox1.Image = loaded;
| 檢查項目 | 確認方式 |
|---|---|
| Bitmap 是否在別處被 Dispose | 統一透過 SafeUpdateImage 換圖,記錄 StackTrace |
| 來源 Stream 是否已關閉 | 改用深複製 new Bitmap(tmp) |
| 是否跨執行緒存取 | 換圖時檢查 InvokeRequired,一律 Invoke 回 UI 執行緒 |
| 紅叉是立即出現還是過一陣子 | 立即出現多為來源問題;過一陣子多為 Dispose 時機問題 |
當堆疊追蹤中出現以下錯誤訊息時,開發者可能需要檢查 Message 物件的內容:
at ....MainForm.Dispose(Boolean A_0)
at System.Windows.Forms.Form.WmClose(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
...
此時需要透過覆寫或檢測 WndProc 方法中的 Message m 來檢查其詳細資訊。
以下提供幾種方法來檢查或記錄 Message 物件的內容。
如果可以存取表單或控制項的原始碼,建議覆寫 WndProc 方法,直接記錄或檢查 Message m 的內容。
protected override void WndProc(ref Message m)
{
try
{
// 記錄 Message 的內容
Console.WriteLine($"Message Details: hWnd={m.HWnd}, Msg={m.Msg}, WParam={m.WParam}, LParam={m.LParam}");
base.WndProc(ref m);
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex}");
throw; // 保持原有例外行為
}
}
這段程式碼會在每次接收到訊息時記錄相關資訊,並允許開發者進一步分析。
如果錯誤發生在 Dispose 方法中,可以在方法內加入例外處理來檢查相關資訊。
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
// 釋放資源
}
base.Dispose(disposing);
}
catch (Exception ex)
{
Console.WriteLine($"Exception in Dispose: {ex}");
throw;
}
}
這樣可以確保在釋放資源時不會忽略重要的錯誤資訊。
如果無法確定錯誤發生的位置,可以透過全域例外處理來記錄堆疊追蹤和相關資訊。
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Exception ex = (Exception)args.ExceptionObject;
Console.WriteLine($"Unhandled Exception: {ex}");
Environment.Exit(1);
};
可以使用 Visual Studio 將斷點設置在 WndProc 或 Dispose 方法中,並檢查 Message 物件的內容。
HWnd:接收訊息的視窗句柄。Msg:訊息的 ID。WParam:附加訊息資訊(字參數)。LParam:附加訊息資訊(長參數)。WndProc 或修改程式碼需小心,避免影響現有行為。Message 的座標或 ID 不固定,可考慮加入條件判斷來篩選需要的訊息。在 .NET C++/CLI 中,WmClose(Message& m) 是 System.Windows.Forms.Form
的內部保護方法,無法直接覆寫。不過,可以透過覆寫 WndProc 方法來攔截並處理 WM_CLOSE 訊息,以達到類似覆寫 WmClose
的效果。
#include <Windows.h>
#include <System.Windows.Forms.h>
using namespace System;
using namespace System::Windows::Forms;
public ref class CustomForm : public Form
{
protected:
// 模擬 WmClose 行為的覆寫
void WmClose(Message% m)
{
// 在這裡加入自定義的 WM_CLOSE 行為
if (MessageBox::Show("確定要關閉視窗嗎?", "確認", MessageBoxButtons::YesNo) == DialogResult::Yes)
{
// 繼續調用基底行為以進行正常關閉
this->Form::WndProc(m);
}
else
{
// 阻止關閉視窗
return;
}
}
// 覆寫 WndProc,攔截 WM_CLOSE 訊息並調用 WmClose
virtual void WndProc(Message% m) override
{
const int WM_CLOSE = 0x0010;
if (m.Msg == WM_CLOSE)
{
WmClose(m); // 調用自定義的 WmClose 方法
}
else
{
// 處理其他訊息
Form::WndProc(m);
}
}
};
[STAThread]
int main(array<String^>^ args)
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
CustomForm^ form = gcnew CustomForm();
form->Text = "覆寫 WmClose 行為範例";
Application::Run(form);
return 0;
}
模擬覆寫 WmClose 方法,在這裡自定義視窗關閉的行為。透過確認對話框詢問使用者是否要關閉視窗。
覆寫 WndProc 方法來攔截 WM_CLOSE 訊息,並將其委派給自定義的 WmClose 方法。
在沒有攔截 WM_CLOSE 的情況下,調用基底類別的 WndProc 方法處理其他訊息。
WmClose 方法僅是模擬覆寫,實際上是透過攔截訊息進行處理。Form::WndProc 以確保基底行為正常運作。InvokeRequired 是 System::Windows::Forms::Control 的屬性,用來判斷目前執行緒是否為控制項所屬的 UI 執行緒。當從背景執行緒存取 UI 控件時,應使用 Invoke 將操作封送回 UI 執行緒。
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
ref class ImageHelper {
public:
static void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox == nullptr)
return;
if (pPictureBox->InvokeRequired) {
// 使用 MethodInvoker 呼叫同一函式,但於 UI 執行緒執行
pPictureBox->Invoke(
gcnew MethodInvoker(gcnew Action
InvokeRequired 只能用於繼承 Control 的物件(如 PictureBox)。true,代表目前執行緒不是 UI 執行緒,必須用 Invoke。Tuple 封裝多參數並傳入靜態代理函式處理。try-catch 捕捉例外避免程式崩潰。
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox->InvokeRequired) {
pPictureBox->Invoke(gcnew MethodInvoker(
gcnew EventHandler(nullptr, &SetImageInvoker)
), gcnew array
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
void SafeAssignImage(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox->InvokeRequired) {
pPictureBox->Invoke(gcnew MethodInvoker(gcnew delegate {
try {
if (b != nullptr) {
if (pPictureBox->Image != nullptr)
delete pPictureBox->Image;
pPictureBox->Image = b;
}
} catch (System::Exception^ ex) {
Console::WriteLine("設定圖片失敗: " + ex->Message);
}
}));
} else {
try {
if (b != nullptr) {
if (pPictureBox->Image != nullptr)
delete pPictureBox->Image;
pPictureBox->Image = b;
}
} catch (System::Exception^ ex) {
Console::WriteLine("設定圖片失敗: " + ex->Message);
}
}
}
gcnew MethodInvoker(...) 必須是合法的委派,不支援 C++11 lambda(如 [=]())語法。BeginInvoke 搭配 Object^ 陣列並解析。Blazor 是由微軟推出的前端框架,允許開發者使用 C# 和 Razor 語法來建立互動式 Web 應用程式,無需使用 JavaScript,即可在瀏覽器中執行 C# 程式碼。
Blazor 無縫整合 ASP.NET Core,可與現有的 .NET 應用程式結合,並支援依賴注入、路由、元件架構等功能。
對於以 .NET 為主要技術的開發者而言,Blazor 提供了與 Node.js 統一 JavaScript 語言相對應的解決方案,使前後端都能使用 C#,建立一致性更高的開發體驗。
.NET MAUI(Multi-platform App UI)是微軟推出的跨平台應用框架,可使用 C# 和 XAML 撰寫一次程式碼,部署到 Windows、Android、iOS 甚至 macOS。
可使用 .NET MAUI 的 Blazor Hybrid 模式,在原生應用中嵌入 Web UI,讓開發者用 Razor 組件開發跨平台使用者介面,仍可存取原生 API。
.NET MAUI 是目前最完整的 .NET 跨平台解決方案,可結合 Blazor 達成 Web、桌面與行動平台的統一應用開發,提供給 .NET 開發者一個語言與技術一致的全端體驗。
.NET MAUI 可透過 API 呼叫與 ASP.NET Core 建立的後端服務整合,形成完整的「前端 App + 後端 API」架構。
ASP.NET Core 解決「後端服務」與「Web 應用」問題,.NET MAUI 則專注在「用戶端跨平台 App」。兩者並不互斥,反而常搭配使用,構成完整解決方案。
以下是一個使用 .NET MAUI 建立的跨平台應用程式範例,這個應用在畫面上顯示一個按鈕,點擊後會更新文字計數。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30">
<Label x:Name="counterLabel"
Text="你尚未點擊按鈕"
FontSize="24"
HorizontalOptions="Center" />
<Button Text="點我"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
namespace MauiApp;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
counterLabel.Text = $"你已經點擊了 {count} 次";
}
}
這個簡單的範例展示了 .NET MAUI 的跨平台能力,開發者只需撰寫一次 XAML 與 C# 程式碼,即可同時在多個平台運作。
MainPage.xaml:定義 UI 介面(使用 XAML)MainPage.xaml.cs:後端程式碼(C#)處理事件MauiProgram.cs:應用程式啟動與服務註冊點Platforms 資料夾:各平台的原生設定(Android、iOS、Windows、MacCatalyst)使用 Visual Studio 建立 .NET MAUI 專案只需幾個步驟,且能一套程式碼同時部署到多個平台,是現代 C# 全端開發者的理想選擇。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30"
VerticalOptions="Center">
<Label
Text="歡迎使用 .NET MAUI!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
<Button
x:Name="counterBtn"
Text="點我!"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
using System;
namespace MyMauiApp;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
counterBtn.Text = $"你已經點了 {count} 次";
}
}
Spacing(間距)與 Padding(內距)。x:Name 以供 C# 後端存取,Clicked 屬性指定點擊事件處理方法。InitializeComponent():初始化 XAML 中定義的 UI 結構。OnCounterClicked:點擊事件的處理方法,每點一次按鈕就更新按鈕文字。counterBtn.Text:透過 XAML 中指定的名稱來控制該按鈕的屬性。執行後,畫面中央會顯示一個歡迎訊息與一個按鈕。每次點擊按鈕,按鈕上的文字就會更新為「你已經點了 X 次」。
using Microsoft.Extensions.Logging;
namespace MyMauiApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp() // 指定應用程式的進入點
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
MauiApp 實例。App.xaml.cs 中的 App 類別),這會是應用程式的根組件。builder.Services.AddSingleton<MainPage>();這樣可讓 MainPage 使用依賴注入方式載入,例如在 App 類中使用
serviceProvider.GetService<MainPage>()。
builder.Services.AddTransient<MyViewModel>();
builder.Services.AddSingleton<IMyService, MyService>();
MauiProgram.cs 就像 ASP.NET Core 的 Startup.cs 或 Program.cs,是應用啟動與服務註冊的主要設定中心。開發大型 MAUI 應用時,擴充此檔案來加入 DI、日誌、組件設定等是非常常見的做法。
在 Ubuntu 系統上部署 .NET C++(C++/CLI 或以 .NET 為基底的 C++ 應用)需要根據專案類型分別處理。若使用純 C++/CLI(依賴 .NET Framework),將無法直接於 Linux 執行,需改為使用 .NET 6/7/8 的跨平台技術(如 C++/CLR → C# 或使用 C++/Native 搭配 .NET 互通)。
根據應用性質,部署可分為三種情境:
在 Ubuntu 中安裝 .NET SDK 與必要工具:
sudo apt update
sudo apt install -y dotnet-sdk-8.0
sudo apt install -y build-essential
(可依版本替換 dotnet-sdk-8.0 為 dotnet-sdk-7.0 或其他版本)
若使用 .NET C# 為主程式,並呼叫 C++ 函式庫:
# 建立主應用
dotnet new console -n MyApp
cd MyApp
# 建立 native 函式庫
mkdir native
cd native
nano add.cpp
add.cpp 範例:
extern "C" int add(int a, int b) {
return a + b;
}
編譯為共享函式庫:
g++ -fPIC -shared -o libadd.so add.cpp
於 Program.cs 加入:
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("libadd.so", EntryPoint="add")]
public static extern int Add(int a, int b);
static void Main()
{
Console.WriteLine("3 + 5 = " + Add(3, 5));
}
}
回到專案根目錄執行:
dotnet run
若輸出結果為 3 + 5 = 8,表示部署成功。
可將程式打包為可獨立執行的部署檔:
dotnet publish -c Release -r linux-x64 --self-contained true
生成的可執行檔位於 bin/Release/net8.0/linux-x64/publish/。
可使用 Docker 將應用容器化,以便在任何 Ubuntu 系統運行:
# Dockerfile 範例
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY ./publish .
COPY ./native/libadd.so /usr/lib/
ENTRYPOINT ["./MyApp"]
建構與執行容器:
docker build -t myapp .
docker run --rm myapp
.so 函式庫,Windows 使用 .dll。LD_LIBRARY_PATH。部署 .NET C++ 程式至 Ubuntu 的可行方式通常是採「C# + C++ 原生函式庫」架構。若原始程式依賴 C++/CLI,需重新設計以支援跨平台 .NET。最推薦的方式是使用 .NET 8 + 原生 C++ 模組搭配 Docker,達到穩定、可攜且一致的部署流程。
email: [email protected]