:ASP.NET API與原生cpprestsdk實戰(zhàn))
1. 項目概述為什么C開發(fā)者需要關注WebService在當前的軟件開發(fā)格局里C常常被貼上“系統(tǒng)級”、“高性能”、“底層”的標簽仿佛它與Web、云端這些時髦詞匯天然絕緣。很多剛接觸C的朋友甚至一些有經(jīng)驗的開發(fā)者一聽到要用C去調用或者創(chuàng)建一個WebService第一反應往往是“這活兒不是應該用Java、C#或者Python來做嗎” 這種想法其實限制了我們手中這把“瑞士軍刀”的威力。我經(jīng)歷過不少項目核心算法或高性能計算模塊用C實現(xiàn)但為了集成到更廣泛的微服務架構中不得不額外包裝一層HTTP接口增加了復雜度和延遲。直接在C里搞定WebService能讓我們把性能優(yōu)勢從計算核心一直延伸到服務邊界。Visual Studio 2015VS2015對于C開發(fā)者來說是一個承上啟下的經(jīng)典版本。它不像更早的版本那樣對現(xiàn)代C標準支持有限也不像VS2017/2019/2022那樣引入了大量新的項目模板和構建系統(tǒng)如CMake集成導致一些傳統(tǒng)配置方式發(fā)生了變化。在VS2015中創(chuàng)建和調用WebService的路徑非常清晰相關工具鏈如ASP.NET Web Application模板、WCF服務庫成熟穩(wěn)定是學習和掌握這一技術棧的理想環(huán)境。這個教程的目的就是帶你繞過那些過時的“添加Web引用”的老路用VS2015提供的主流、有效的方法親手搭建一個從服務端到客戶端的完整WebService示例。你會發(fā)現(xiàn)用C玩轉WebService并沒有想象中那么復雜。2. 核心思路與方案選型在C世界里如何構建WebService當我們決定用C和VS2015來操作WebService時首先要明確一個關鍵點純粹的、標準庫的C本身并不直接提供創(chuàng)建HTTP服務器或解析SOAP/WSDL的內(nèi)置功能。因此我們的所有方案本質上都是“C 某個框架或庫”。在VS2015的生態(tài)下主要有兩條清晰的技術路徑我們需要根據(jù)項目需求做出選擇。2.1 路徑一托管C與ASP.NET Web API推薦用于快速構建服務端這是最貼近VS2015開發(fā)體驗、也是文檔和社區(qū)支持最完善的方案。其核心是利用“C/CLI”這門語言它是微軟推出的連接原生C和.NET框架的橋梁。通過它我們可以在C項目中直接使用強大的.NET類庫特別是ASP.NET Web API。為什么選擇這個方案開發(fā)效率高VS2015為ASP.NET項目提供了豐富的模板和腳手架可以快速生成符合RESTful風格的Web API控制器。你無需從零開始處理HTTP協(xié)議、路由、序列化JSON/XML等繁瑣細節(jié)。生態(tài)強大可以直接享用.NET Framework龐大的類庫用于數(shù)據(jù)庫訪問Entity Framework、身份認證ASP.NET Identity、依賴注入等功能完善。易于維護生成的代碼結構清晰符合MVC模式便于團隊協(xié)作和后期功能擴展。性能足夠對于大多數(shù)業(yè)務邏輯服務托管代碼的性能瓶頸往往不在語言本身而在I/O和算法。C/CLI允許你在性能關鍵處無縫調用原生C編譯的DLL兼顧開發(fā)效率與執(zhí)行效率。它的工作原理是你創(chuàng)建一個“ASP.NET Web 應用程序”項目選擇Web API模板然后在其中添加C/CLI類庫項目。Web API控制器C#編寫負責接收HTTP請求和返回響應而核心的業(yè)務邏輯或計算密集型任務則通過引用C/CLI類庫調用其中用原生C編寫的函數(shù)來完成。C/CLI項目在這里充當了“適配器”的角色。2.2 路徑二原生C與第三方庫如cpprestsdk如果你追求極致的原生性能或者你的應用部署環(huán)境無法安裝.NET Framework那么這條路更適合你。這里我們主要使用微軟官方推出的cpprestsdk也稱為Casablanca。為什么選擇這個方案純粹原生整個服務端和客戶端都由標準C編寫編譯后是獨立的可執(zhí)行文件或庫不依賴.NET運行時部署簡單??缙脚_cpprestsdk支持Windows、Linux、macOS等多個平臺便于構建跨平臺的Web服務?,F(xiàn)代C風格大量使用C11/14的特性如lambda表達式、future/promise異步模型代碼風格現(xiàn)代。同時支持客戶端與服務端同一個庫既可以用來創(chuàng)建HTTP服務器也可以用來編寫HTTP客戶端學習成本相對集中。它的挑戰(zhàn)在于你需要手動處理更多的底層細節(jié)比如路由分發(fā)、請求/響應的序列化與反序列化JSON/XML、連接池管理等。雖然cpprestsdk提供了很好的基礎但構建一個功能完善、健壯的服務端需要編寫的“樣板代碼”會比ASP.NET方案多。方案選擇建議如果你是初學者或項目需要快速原型驗證并部署在Windows服務器上強烈推薦路徑一ASP.NET Web API C/CLI。本教程也將以此為主要路線進行詳細演示因為它能讓你最快地看到成果理解WebService的核心概念。如果你在開發(fā)一個需要跨平臺部署、或對啟動時間、內(nèi)存占用有極端要求的獨立服務那么應該深入研究路徑二cpprestsdk。3. 環(huán)境準備與項目創(chuàng)建搭建你的第一個C WebService骨架理論說得再多不如動手敲一行代碼。我們首先按照路徑一來搭建環(huán)境。請確保你的VS2015安裝了“Visual C”和“ASP.NET和Web開發(fā)”相關的工作負載。3.1 創(chuàng)建解決方案與ASP.NET Web API項目打開VS2015點擊“文件”-“新建”-“項目”。在左側模板樹中展開“Visual C#”-“Web”選擇“ASP.NET Web 應用程序”。將項目命名為CppWebServiceDemo并選擇一個合適的解決方案位置。在彈出的“新建ASP.NET項目”對話框中選擇“Web API”模板。務必取消勾選“為云添加單元測試”和“添加應用程序見解遙測”以保持項目簡潔。身份驗證可以暫時選擇“不進行身份驗證”。點擊“確定”。創(chuàng)建完成后解決方案資源管理器里會出現(xiàn)一個C#項目。按F5運行一下如果瀏覽器打開并顯示一個API幫助頁面說明Web API項目創(chuàng)建成功。先關閉瀏覽器停止調試。3.2 創(chuàng)建并配置C/CLI類庫項目接下來我們要創(chuàng)建承載核心C邏輯的橋梁項目。在解決方案資源管理器中右鍵點擊解決方案 - “添加” - “新建項目”。在左側模板樹中展開“Visual C”-“CLR”選擇“類庫(.NET Framework)”。將項目命名為NativeCppLogic。注意這里選擇的是“CLR”下的類庫不是“Win32”項目。點擊“確定”創(chuàng)建。項目創(chuàng)建后你會看到自動生成了Class1.h和Class1.cpp文件。關鍵配置設置公共語言運行時支持。右鍵點擊NativeCppLogic項目選擇“屬性”。在“配置屬性”-“常規(guī)”下確?!肮舱Z言運行時支持”設置為“公共語言運行時支持(/clr)”。這是C/CLI項目的核心編譯選項。添加預編譯頭可選但推薦為了更好的編譯性能我們可以像標準C項目一樣使用預編譯頭。在“屬性頁”的“C/C”-“預編譯頭”中將“預編譯頭”設置為“使用(/Yu)”。然后在項目中添加一個stdafx.h和stdafx.cpp文件。在stdafx.h中可以包含一些常用的系統(tǒng)頭文件如iostream,string,vector等。在stdafx.cpp中只需包含#include “stdafx.h”。3.3 編寫原生C業(yè)務邏輯現(xiàn)在我們來編寫一個簡單的計算函數(shù)模擬核心業(yè)務邏輯。在NativeCppLogic項目中將Class1.h和Class1.cpp重命名為更有意義的名字例如Calculator.h和Calculator.cpp。右鍵文件-重命名。編輯Calculator.h內(nèi)容如下// Calculator.h #pragma once #include string // 這是一個原生的C類不包含任何.NET特性 class NativeCalculator { public: // 一個簡單的加法函數(shù) int Add(int a, int b); // 一個處理字符串的函數(shù)返回拼接結果 std::string Greet(const std::string name); // 一個稍微復雜點的函數(shù)計算斐波那契數(shù)列遞歸僅作示例效率不高 long long Fibonacci(int n); };編輯Calculator.cpp實現(xiàn)這些函數(shù)// Calculator.cpp #include “stdafx.h” // 如果使用了預編譯頭 #include “Calculator.h” int NativeCalculator::Add(int a, int b) { return a b; } std::string NativeCalculator::Greet(const std::string name) { return “Hello, “ name “! from Native C”; } long long NativeCalculator::Fibonacci(int n) { if (n 1) return n; return Fibonacci(n - 1) Fibonacci(n - 2); }3.4 創(chuàng)建C/CLI包裝器原生C類NativeCalculator不能被.NET直接識別。我們需要創(chuàng)建一個C/CLI包裝類它內(nèi)部持有一個NativeCalculator實例并暴露一個.NET能調用的公共接口。在NativeCppLogic項目中添加一個新的頭文件命名為ManagedWrapper.h。編輯ManagedWrapper.h// ManagedWrapper.h #pragma once #include “Calculator.h” // 使用C/CLI語法定義一個托管類 public ref class ManagedCalculator { private: // 持有原生C對象的指針 NativeCalculator* nativeCalc; public: // 構造函數(shù)和析構函數(shù) ManagedCalculator(); ~ManagedCalculator(); !ManagedCalculator(); // 析構函數(shù)Finalizer // 托管方法包裝原生功能 int Add(int a, int b); System::String^ Greet(System::String^ name); long long Fibonacci(int n); };注意ref class、System::String^托管字符串句柄和!ManagedCalculator()析構函數(shù)這些C/CLI特有的語法。添加一個新的源文件命名為ManagedWrapper.cpp。編輯ManagedWrapper.cpp// ManagedWrapper.cpp #include “stdafx.h” #include “ManagedWrapper.h” ManagedCalculator::ManagedCalculator() { nativeCalc new NativeCalculator(); } ManagedCalculator::~ManagedCalculator() { this-!ManagedCalculator(); } ManagedCalculator::!ManagedCalculator() { if (nativeCalc) { delete nativeCalc; nativeCalc nullptr; } } int ManagedCalculator::Add(int a, int b) { // 簡單地將調用轉發(fā)給原生對象 return nativeCalc-Add(a, b); } System::String^ ManagedCalculator::Greet(System::String^ name) { // 將托管字符串(System::String^)轉換為標準C字符串(std::string) msclr::interop::marshal_context context; std::string stdName context.marshal_asstd::string(name); // 調用原生函數(shù) std::string result nativeCalc-Greet(stdName); // 將結果轉換回托管字符串并返回 return gcnew System::String(result.c_str()); } long long ManagedCalculator::Fibonacci(int n) { return nativeCalc-Fibonacci(n); }這里使用了msclr::interop::marshal_context來進行System::String^和std::string之間的轉換這是C/CLI中處理字符串互操作的常見方式。3.5 在Web API中調用C/CLI庫現(xiàn)在橋梁已經(jīng)搭好我們回到C#的Web API項目來使用它。添加項目引用在解決方案資源管理器中右鍵點擊CppWebServiceDemoC#項目下的“引用”-“添加引用”。在彈出的窗口中選擇“項目”-“解決方案”然后勾選我們剛創(chuàng)建的NativeCppLogic項目。點擊“確定”。這樣C#項目就能“看到”我們寫的ManagedCalculator類了。創(chuàng)建Web API控制器在CppWebServiceDemo項目的Controllers文件夾上右鍵-“添加”-“控制器”。選擇“Web API 2 控制器 - 空”命名為CalculatorController.cs。編寫控制器代碼編輯CalculatorController.csusing System.Web.Http; using NativeCppLogic; // 引用我們的C/CLI項目 namespace CppWebServiceDemo.Controllers { public class CalculatorController : ApiController { // GET api/calculator/add?a5b3 [HttpGet] [Route(“api/calculator/add”)] public IHttpActionResult Add(int a, int b) { try { using (var calc new ManagedCalculator()) { int result calc.Add(a, b); return Ok(new { operation “add”, a, b, result }); } } catch (System.Exception ex) { return InternalServerError(ex); } } // GET api/calculator/greet?nameWorld [HttpGet] [Route(“api/calculator/greet”)] public IHttpActionResult Greet(string name) { try { using (var calc new ManagedCalculator()) { string result calc.Greet(name); return Ok(new { operation “greet”, name, message result }); } } catch (System.Exception ex) { return InternalServerError(ex); } } // GET api/calculator/fibonacci?n10 [HttpGet] [Route(“api/calculator/fibonacci”)] public IHttpActionResult Fibonacci(int n) { if (n 0 || n 40) // 限制輸入防止遞歸過深或溢出 return BadRequest(“Please input n between 0 and 40.”); try { using (var calc new ManagedCalculator()) { long result calc.Fibonacci(n); return Ok(new { operation “fibonacci”, n, result }); } } catch (System.Exception ex) { return InternalServerError(ex); } } } }注意using (var calc new ManagedCalculator())的用法這確保了ManagedCalculator對象在使用后會被正確釋放調用其Dispose方法進而調用C/CLI類的析構函數(shù)。3.6 運行與測試將CppWebServiceDemo項目設置為啟動項目右鍵項目-“設為啟動項目”。按F5運行。瀏覽器會打開通常顯示一個默認的API幫助頁面。在瀏覽器地址欄中手動輸入我們定義的API地址進行測試http://localhost:端口號/api/calculator/add?a5b3應該返回{“operation”:”add”,”a”:5,”b”:3,”result”:8}http://localhost:端口號/api/calculator/greet?nameDeveloper應該返回{“operation”:”greet”,”name”:”Developer”,”message”:”Hello, Developer! from Native C”}http://localhost:端口號/api/calculator/fibonacci?n10應該返回{“operation”:”fibonacci”,”n”:10,”result”:55}恭喜你已經(jīng)成功創(chuàng)建了一個使用C編寫核心邏輯、通過C/CLI封裝、并由ASP.NET Web API提供HTTP接口的完整WebService。這個過程清晰地展示了如何將高性能的C代碼無縫集成到現(xiàn)代的Web服務架構中。4. 核心環(huán)節(jié)進階使用cpprestsdk構建原生C WebService客戶端服務端跑通了我們再來看看客戶端。雖然在C#項目里調用這個服務很簡單直接用HttpClient但我們的目標是C。這里我們采用路徑二中提到的cpprestsdk來構建一個純原生的C控制臺客戶端它不依賴.NET更能體現(xiàn)C的跨平臺能力。4.1 為客戶端項目安裝cpprestsdk首先我們需要創(chuàng)建一個新的原生C項目來作為客戶端。在現(xiàn)有的解決方案中右鍵點擊解決方案 - “添加” - “新建項目”。選擇“Visual C”-“Win32”-“Win32 控制臺應用程序”。命名為NativeCppClient。在“Win32 應用程序向導”中點擊“下一步”在“應用程序類型”下選擇“控制臺應用程序”并在“附加選項”下勾選“空項目”。點擊“完成”。通過vcpkg安裝cpprestsdk推薦這是管理C庫最方便的方式之一。如果你還沒有安裝vcpkg請先從其GitHub倉庫克隆并安裝。打開命令提示符或PowerShell導航到vcpkg目錄執(zhí)行以下命令.\vcpkg install cpprestsdk:x86-windows # 如果你編譯的是32位程序 .\vcpkg install cpprestsdk:x64-windows # 如果你編譯的是64位程序安裝完成后記下vcpkg提示的“集成安裝”命令例如.\vcpkg integrate install執(zhí)行它以將vcpkg的庫目錄集成到VS2015中。配置客戶端項目屬性右鍵點擊NativeCppClient項目選擇“屬性”。C/C - 常規(guī) - 附加包含目錄添加cpprestsdk的頭文件路徑通常是你的vcpkg目錄\installed\x86-windows\include根據(jù)你的架構選擇x86或x64。鏈接器 - 常規(guī) - 附加庫目錄添加cpprestsdk的庫文件路徑例如你的vcpkg目錄\installed\x86-windows\lib。鏈接器 - 輸入 - 附加依賴項添加cpprest_2_10.lib版本號可能不同請查看lib目錄下的實際文件名。C/C - 代碼生成 - 運行庫確保與cpprestsdk編譯時使用的運行時一致。通常vcpkg默認使用/MT或/MTd靜態(tài)鏈接。為了簡單你可以將客戶端也設置為“多線程(/MT)”或“多線程調試(/MTd)”。如果不一致會導致鏈接錯誤。4.2 編寫原生C客戶端代碼在NativeCppClient項目中添加一個源文件例如main.cpp。編輯main.cpp編寫調用我們剛創(chuàng)建的WebService的代碼#include iostream #include cpprest/http_client.h #include cpprest/filestream.h #include cpprest/json.h using namespace web; using namespace web::http; using namespace web::http::client; using namespace concurrency::streams; int main() { // 1. 創(chuàng)建HTTP客戶端指定服務端地址根據(jù)你運行Web API的實際端口修改 http_client client(U(“http://localhost:你的端口號”)); // 2. 構建請求URI uri_builder builder(U(“/api/calculator/add”)); builder.append_query(U(“a”), “15”); builder.append_query(U(“b”), “25”); // 3. 發(fā)送GET請求并處理響應 client.request(methods::GET, builder.to_string()) .then([](http_response response) - pplx::taskjson::value { // 檢查狀態(tài)碼 if (response.status_code() status_codes::OK) { // 提取JSON響應體 return response.extract_json(); } // 如果請求失敗返回一個空的JSON任務 return pplx::task_from_result(json::value()); }) .then([](pplx::taskjson::value previousTask) { try { // 獲取JSON值 json::value const jsonValue previousTask.get(); if (!jsonValue.is_null()) { // 解析JSON auto result jsonValue.at(U(“result”)).as_integer(); std::wcout L“Result of addition: “ result std::endl; auto operation jsonValue.at(U(“operation”)).as_string(); std::wcout L“Operation: “ operation std::endl; } else { std::wcout L“Request failed or returned no JSON.” std::endl; } } catch (const http_exception e) { std::wcerr L“HTTP Exception: “ e.what() std::endl; } catch (const std::exception e) { std::cerr “Standard Exception: “ e.what() std::endl; } }) .wait(); // 等待異步操作完成對于控制臺程序是必要的 // 示例調用greet接口 std::wcout L“\n--- Calling Greet API ---“ std::endl; uri_builder builderGreet(U(“/api/calculator/greet”)); builderGreet.append_query(U(“name”), “CppRestSDK Client”); client.request(methods::GET, builderGreet.to_string()) .then([](http_response response) { return response.extract_string(); // 這次我們直接提取字符串 }) .then([](pplx::taskutility::string_t previousTask) { try { utility::string_t body previousTask.get(); std::wcout L“Response: “ body std::endl; } catch (const std::exception e) { std::cerr “Error: “ e.what() std::endl; } }) .wait(); std::wcout L“\nPress Enter to exit...“ std::endl; std::cin.get(); return 0; }這段代碼展示了cpprestsdk的核心用法基于任務的異步編程模型。client.request()返回一個task我們通過.then()來鏈式處理響應。注意端口號需要替換成你運行Web API時實際的端口。4.3 編譯與運行客戶端確保你的Web API服務CppWebServiceDemo項目正在運行按F5在調試模式下運行或者不調試運行CtrlF5。將NativeCppClient項目設為啟動項目右鍵項目-“設為啟動項目”然后按CtrlF5開始執(zhí)行不調試運行。控制臺窗口應該會輸出類似以下內(nèi)容Result of addition: 40 Operation: add --- Calling Greet API --- Response: {“operation”:”greet”,”name”:”CppRestSDK Client”,”message”:”Hello, CppRestSDK Client! from Native C”} Press Enter to exit...這表明你的原生C客戶端已經(jīng)成功通過HTTP協(xié)議調用了由C/CLI和ASP.NET承載的WebService并正確解析了返回的JSON數(shù)據(jù)。5. 常見問題、調試技巧與深度優(yōu)化將C融入WebService開發(fā)尤其是在VS2015這個相對經(jīng)典的環(huán)境里會遇到一些特有的坑。下面是我在實際項目中總結的一些關鍵問題和解決方案。5.1 編譯與鏈接問題問題1C/CLI項目編譯錯誤“error C3646: ‘…’: 未知重寫說明符”原因這通常是因為在C/CLI的ref class中試圖使用原生C的語法或類型來聲明一個托管方法或者頭文件包含順序有問題。解決確保你的C/CLI源文件.cpp包含了對應的#include “stdafx.h”如果使用了預編譯頭。檢查ManagedWrapper.h中所有公開方法的返回類型和參數(shù)類型都必須是托管類型或能被CLR識別的類型。例如使用System::String^而不是std::string作為公共接口的參數(shù)/返回類型。確保原生C類的頭文件如Calculator.h被C/CLI包裝器的頭文件ManagedWrapper.h包含而不是反過來。問題2鏈接錯誤“LNKxxxx: 無法解析的外部符號”涉及cpprestsdk原因項目配置不正確編譯器找不到cpprestsdk的庫文件或者運行時庫/MT, /MD等不匹配。解決仔細檢查項目屬性中的“附加包含目錄”和“附加庫目錄”確保路徑指向vcpkg安裝的正確架構x86或x64的include和lib文件夾。路徑中不要有中文或特殊字符。核對“附加依賴項”中的庫文件名必須和lib文件夾下的.lib文件名稱完全一致。統(tǒng)一運行時庫在項目屬性“C/C - 代碼生成 - 運行庫”中選擇與cpprestsdk編譯時相同的選項。最穩(wěn)妥的方法是用vcpkg安裝時指定相同的 triplet。例如如果你用.\vcpkg install cpprestsdk:x86-windows-static安裝的靜態(tài)庫那么你的客戶端項目就應該使用/MT或/MTd。5.2 運行時與調試問題問題3運行客戶端時提示“找不到cpprest_2_10.dll”或其他DLL原因如果你動態(tài)鏈接了cpprestsdk即使用.dll文件那么相應的DLL必須位于可執(zhí)行文件的同級目錄或系統(tǒng)PATH路徑下。解決將vcpkg安裝目錄下installed\x86-windows\bin文件夾內(nèi)的cpprest_2_10.dll等依賴DLL復制到你的客戶端項目生成的可執(zhí)行文件.exe所在的目錄通常是Debug或Release文件夾。或者在vcpkg安裝時使用靜態(tài)鏈接選項.\vcpkg install cpprestsdk:x86-windows-static然后在項目屬性中鏈接對應的靜態(tài)庫通常以.lib結尾但鏈接時會靜態(tài)編譯進去這樣生成的可執(zhí)行文件就不依賴外部DLL了。問題4在C#中調用C/CLI方法時拋出System.BadImageFormatException異常原因這是典型的“位”不匹配問題。你的C#項目通常是“任何CPU”或“x86”嘗試加載一個為不同平臺如x64編譯的C/CLI DLL。解決在VS2015的頂部工具欄找到“解決方案配置”和“解決方案平臺”。確保整個解決方案的平臺一致。將NativeCppLogicC/CLI項目的平臺設置為“x86”如果你的系統(tǒng)是32位或想強制32位運行或“x64”。然后將CppWebServiceDemoC#項目的平臺也設置為完全相同的平臺。對于ASP.NET項目在“項目屬性”-“生成”中取消“首選32位”的勾選并將目標平臺設置為對應的“x86”或“x64”。一個簡單的調試方法是在C#項目的“引用”中找到NativeCppLogic查看其“路徑”屬性確認引用的DLL是來自x86\Debug還是x64\Debug文件夾然后據(jù)此調整項目平臺。問題5內(nèi)存泄漏診斷場景在C/CLI包裝器中手動管理了原生C對象new/delete。技巧雖然我們示例中使用了析構函數(shù)和!Finalizer來確保釋放但在復雜場景下仍可能出錯??梢允褂肰isual Studio內(nèi)置的“診斷工具”窗口。在調試運行時點擊“調試”-“窗口”-“顯示診斷工具”。在“診斷工具”窗口中選擇“內(nèi)存使用量”選項卡。在測試前后手動點擊“拍攝快照”按鈕。比較快照查看“本機堆”的分配情況。如果發(fā)現(xiàn)NativeCalculator類型的對象在操作后沒有減少就可能存在泄漏。這時需要仔細檢查ManagedCalculator的析構和終結邏輯確保在所有執(zhí)行路徑上delete都被調用。5.3 性能與設計優(yōu)化建議避免頻繁創(chuàng)建/銷毀C/CLI包裝對象在Web API控制器中每次請求都new一個ManagedCalculator會產(chǎn)生開銷。對于無狀態(tài)的工具類可以考慮將其設計為單例或者使用依賴注入框架如ASP.NET Core內(nèi)置的DI將其注冊為單例或作用域服務。注意字符串轉換開銷msclr::interop::marshal_context的轉換是有成本的。如果某個方法被高頻調用且字符串數(shù)據(jù)較大需要考慮性能影響。對于簡單的字符串直接使用pin_ptr固定內(nèi)存可能是更高效的選擇但代碼會更復雜。異步調用原生代碼如果原生C函數(shù)執(zhí)行非常耗時例如我們示例中的Fibonacci當n較大時在Web API中同步調用它會阻塞線程池線程影響服務器并發(fā)能力。理想情況下應該將原生函數(shù)也改為異步模式或者在C/CLI層使用Task.Run將其包裝成.NET的Task然后在Web API中await它。使用更高效的序列化我們的示例返回的是JSON。對于C/CLI層如果數(shù)據(jù)復雜手動構建JSON字符串很麻煩??梢钥紤]使用像nlohmann/json這樣的原生C JSON庫來處理數(shù)據(jù)然后在C/CLI邊界只做簡單的類型傳遞或者使用.NET的System.Text.Json或Newtonsoft.Json在C#層進行序列化??紤]使用SWIG等工具對于大型的、已有成熟C代碼庫的項目手動編寫C/CLI包裝器會非常繁瑣且容易出錯??梢钥紤]使用SWIGSimplified Wrapper and Interface Generator這類工具它能自動生成將C代碼包裝成多種語言包括C#接口的代碼大大提升開發(fā)效率。不過SWIG生成C/CLI代碼可能需要一些額外的配置和技巧。通過這個從服務端到客戶端的完整流程你不僅學會了如何在VS2015中用C創(chuàng)建和調用WebService更重要的是掌握了連接原生C世界與現(xiàn)代Web開發(fā)框架的核心方法論。無論是為了復用遺留的C代碼還是為了在關鍵服務中榨取最后一滴性能這套技術組合拳都能為你提供堅實的支撐。