深入 .NET Core 基礎 - 1:deps.json, runtimeconfig.json 以及 dll
深入 .NET Core 基礎:deps.json, runtimeconfig.json 以及 dll
原文地址:https://natemcmaster.com/blog/2017/12/21/netcore-primitives/
1. .NET Core 應用程式基礎
我學習過使用 gcc,C++ 和 vim 程式設計。當我開始使用 C# 和 .NET 的時候,點選 Visual Studio 中的 執行
按鈕就是魔法,也帶者失望。失望 - 不是因為我希望編寫 Makefile - 而是因為我不知道 執行
都做了什麼。所以,我開始探索。在本博文中,我將展示在 .NET Core 中使用的多數基礎工具,並手工建立 .NET Core 應用程式而不借助於 Visual Studio。如果你是 .NET Core 的新手,並且希望揭開內幕,本文就是為您而來。如果您已經是一個 .NET Core 的開發者,並且好奇 *.deps.json 或者 *.runtimeconfig.json 檔案是做什麼的,我也會涵蓋這些內容。
我將會終止 Visual Studio 的魔法,而一直使用命令列工具。為了你能夠進行,您需要
.NET Core 2.1 SDK ( 實際上,.NET Core 3.1 SDK 已經發布,我想你更應該下載這個最新版)。下面的這些步驟是在 macOS 上完成的,但是它們也同樣在 Linux 和 Windows 上一樣工作,如果您將路徑更改為 c:\Program Files\dotnet\
和 dotnet.exe
的話。
2. C# 編譯器
C# 編譯器將 *.cs 檔案編譯為 *.dll 檔案,也被稱為程式集檔案。程式集檔案具有便攜可執行檔案格式,.NET Core 可以在 Windows、macOS 和 Linux 上執行它。.NET Core app
C# 編譯器可以直接呼叫來生成程式集檔案。C# 編譯器可以在 .NET Core SDK 中發現,並像下面這樣被呼叫。
dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll -help
讓我們為它提供一個輸入內容。首先,建立名為 Program.cs
的檔案,並編寫如下 C# 程式碼:
/* Program.cs */
class Program
{
static void Main(string[] args)=> System.Console.WriteLine("Hello World!");
}
然後,在命令列,執行如下命令:
> dotnet \
/usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \
-out:Program.dll \
Program.cs
在 .NET Core 3.1 中,NuGetFallbackFoler
已經從 sdk
資料夾中移除了。這些程式集已經轉移到 C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1
資料夾中。
如果在 Windows 下,注意空格的處理:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -out:Program.dll Program.cs
引數的含義如下:
-
dotnet - C# 編譯器本身也是一個 .NET Core 應用程式,所以,我們需要通過 dotnet 命令來啟動它
-
/usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll - C# 編譯器的路徑。在 Windows 上,路徑就是: C:\Program Files\dotnet\
-
-reference 引數指向了 System.Runtime.dll 和 System.Console.dll,這些類似於 C++ 中的標頭檔案,它們為編譯器提供關於
System.Object
和System.Console
的資訊。 -
-out:Program.dll,輸出檔名。
.dll
的副檔名是 .NET Core 的約定,並不是必需的。如果沒有指定,編譯器將生成名為Program.exe
的檔案。在 Windows 系統上,這會導致一點誤解,因為你並不能通過雙擊 Program.exe 來啟動它,所以,在 .NET Core 中,我們總是使用 .dll 副檔名。
Reference 引用允許我們使用程式碼中涉及的在其它 .NET Core 程式碼中定義的成千上萬的型別,例如 List
、Integer
以及 HttpClient
型別等等。但是,你不得不告訴編譯器到哪裡去找到它們。如果你刪除掉 -reference:***
部分,編譯器將會失敗,並返回如下錯誤:
Program.cs(1,11): error CS0518: Predefined type 'System.Object' is not defined or imported
Program.cs(3,26): error CS0518: Predefined type 'System.String' is not defined or imported
Program.cs(3,16): error CS0518: Predefined type 'System.Void' is not defined or imported
示例中使用的路徑是 /usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app
。這些來自與 Microsoft.NETCore.App
這個 NugGet 包,後面我們會討論它。
3. runtimeconfig.json
對於 .NET Core 應用程式來說,runtime.config.json
檔案是必需的。術語 runtime
、shared framework
、或者 platform
經常互換,但是,在談論 .NET Core 的時候,它們是一回事。該 JSON 配置檔案用於執行時。
如果您擁有了上一步所得到的程式集,您可以試著在命令列執行它,通過 dotnet
工具。沒有這個 runtime.config.json
,該嘗試將會失敗:
dotnet Program.dll
A fatal error was encountered. The library 'libhostpolicy.dylib' required to execute the application was not found in '/Users/nmcmaster/code/'.
在 Windows 環境的 .NET Core 3.1 環境下,我得到是:
dotnet Program.dll
A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'C:\temp\dotnet\'.
Failed to run as a self-contained app. If this should be a framework-dependent app, add the C:\temp\dotnet\Program.runtimeconfig.json file specifying the appropriate framework.
該段說明的意思是,.NET Core 不能找到用於執行 Program.dll 檔案所必需的某些檔案。為了解決這個問題,建立名為 Program.runtimeconfig.json
的檔案,並使用如下內容:
{
"runtimeOptions": {
"framework": {
"name": "Microsoft.NETCore.App",
"version": "2.0.0"
}
}
}
注意,在 .NET Core .3.1 下,檔案內容如下:
{
"runtimeOptions": {
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.0"
}
}
}
這些設定指示 dotnet
使用 Microsoft.NETCore.App 3.1.0
共享框架。該框架也是最常使用的框架,但是,還有其它的框架,例如 Microsoft.AspNetCore.App
。不像 .NET Framework 是裝個機器範圍生效,可以有多個 .NET Core 共享框架安裝在同一臺機器上。dotnet
將讀取該 JSON 檔案,並在 /usr/local/share/dotnet/shared/$FrameworkName/$Version/
中查詢需要的檔案並執行應用程式。
說明:如果有更高版本的
Microsoft.NeTCore.App
補丁安裝,例如shared/Microsoft.NETCore.App/2.0.4/
,dotnet
將自動使用更高版本。
現在,執行 dotnet Program.dll
。
>dotnet Program.dll
Hello world!
4. Package 包
包提供了在不同專案之間、專案組之間以及組織之間共享程式碼的方式,.NET 程式集被打包到 *.nupkg
檔案中,這僅僅是一個 ZIP 壓縮格式檔案,並含有一個 XML 檔案 (.nuspec) ,包含有關於該包的元資料。
最流行的一個 .NET 包稱為 JSON.NET,也被稱為 Newtonsoft.Json。它提供瞭解析和序列化 JSON 的 API。我們可以從 NuGet.org 得到它並提取到磁碟上。
# Bash
mkdir -p ./packages/Newtonsoft.Json/10.0.3/
curl -L https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 | tar -xf - -C ./packages/Newtonsoft.Json/10.0.3/
Windows 環境下
# Windows (powershell)
mkdir ./packages/Newtonsoft.Json/10.0.3/
Invoke-WebRequest https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 -OutFile Newtonsoft.Json.10.0.3.zip
Expand-Archive Newtonsoft.Json.10.0.3.zip -D ./packages/Newtonsoft.Json/10.0.3/
為了演示它的使用,我們將更新前一步的示例程式碼,以 JSON 物件格式輸出資訊。
class Program
{
static void Main(string[] args)
=> System.Console.WriteLine(
Newtonsoft.Json.JsonConvert.SerializeObject(new { greeting = "Hello World!" }));
}
這需要新增更多的編譯引數到編譯器的引數列表中,以便使用 Newtonsoft.Json 的 API。
> dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Collections.dll \
-reference:./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll \
-out:Program.dll \
Program.cs
使用 .NET Core 3.1 的命令如下:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -out:Program.dll Program.cs
注意:顯然我們需要
reference:Newtonsoft.Json.dll
,但是,為什麼需要System.Collections.dll
?這是因為我們還使用了匿名型別,new { greeting }
。在背後,C# 編譯器在匿名型別上生成了一個.Equals()
方法,該方法呼叫了System.Collections.Generic.EqualityComparer
,它定義在System.Collections.dll
中。
編譯應當成功,雖然帶有一些警告資訊。
Program.cs(4,35): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
在 .NET Core 3.1 下,實際上,我得到的輸出資訊如下:
Microsoft (R) Visual C# Compiler version 3.4.0-beta4-19562-05 (ff930dec)
Copyright (C) Microsoft Corporation. All rights reserved.
warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
Program.cs(5,11): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
這意味著在 Newtonsoft.Json
的作者建立 Newtonsoft.Json.dll
的時候,他基於的 System.Runtime.dll
的版本是 4.0.20.0
。但是,現在提供的 System.Runtime.dll
更新一些,版本是 4.2.0.0
。如果在版本 4.0.20.0
到 4.2.0.0
之間有變化的化,會導致你執行的應用程式出現問題,所以,編譯器發出警告。幸運的是,這些變更是後向相容的,所以 Newtonsoft.Json
將工作正常。我們可以通過新增引數 -nowarn:/CS1701
來抑制這些警告。
> dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Console.dll \
-reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.0.0/ref/netcoreapp2.0/System.Collections.dll \
-reference:./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll \
-nowarn:CS1701 \
-out:Program.dll \
Program.cs
對於 Windows 環境下的 .NET Core 3.1,命令如下:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -nowarn:CS1701 -out:Program.dll Program.cs注意
CS1701
中的字母是大寫。
5. 動態連結
在上一步,我們編譯了一個引用 Newtonsoft.dll
、System.Runtime.dll
和其它程式集的簡單應用程式。在新增 Newtonsoft.dll
之前,我們的應用程式工作良好。但是,在更新版本之後,該程式的執行將會失敗。
> dotnet Program.dll
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.
.NET 在執行時動態連結程式集。編譯器在程式集 Program.dll
中添加了到 Newtonsoft.Json.dll
的引用,但並不會將程式碼複製進來。.NET Core 執行時期待能夠在應用程式執行的時候能夠載入名為 Newtonsoft.Json.dll
的程式集。對於 System.Runtime.dll
和 System.Console.dll
,以及其它引用的 System.* 檔案也是同樣的。
.NET Core 可以通過配置在一系列位置尋找 Newtonsoft.Json.dll
,但是,為了簡單起見,我們可以將它複製到 Program.dll
的同一個資料夾中。
> cp ./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll ./
> dotnet Program.dll
{"greeting":"Hello World!"}
為什麼我們不需要將 System.Runtime.dll
和其它檔案複製過來呢?這些檔案通過 Microsoft.NETCore.App 共享框架動態連結過來,如前面所述。
6. deps.json
deps.json
檔案是依賴說明檔案。它可以用來配置來自包的動態連結到程式集。如前所述,.NET Core 可以配置為從多個位置來動態載入程式集。這些位置包括:
-
應用程式所在的目錄,與應用程式入口相同的資料夾,不需要配置。
-
包的快取資料夾 (NuGet 恢復快取或者 NuGet 回落資料夾)
-
優化之後的包快取,或者執行時包儲存。
-
服務目錄 (servicing index),很少使用,用於 Windows Update 方式
-
共享框架 (通過 runtimeconfig.json 配置)
綜上所述,deps.json
定義可以動態連結的依賴列表,通常,該檔案由機器生成,對於實際的應用程式,可能變得很大並且很複雜。但是,它是純文字形式的,所以我們可以使用編輯器來處理它。
新增名為 Program.deps.json
的檔案到專案中,內容如下:
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v2.0"
},
"targets": {
".NETCoreApp,Version=v2.0": {
"Newtonsoft.Json/10.0.3": {
"runtime": {
"lib/netstandard1.3/Newtonsoft.Json.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.3.21018"
}
}
}
}
},
"libraries": {
"Newtonsoft.Json/10.0.3": {
"type": "package",
"serviceable": false,
"sha512": ""
}
}
}
為了展示這是如何工作的,將現在的與 Program.dll
同一資料夾的 Newtonsoft.Json.dll
刪除。然後,執行 dotnet Program.dll
。
> rm Newtonsoft.Json.dll
> dotnet Program.dll
Error:
An assembly specified in the application dependencies manifest (Program.deps.json) was not found:
package: 'Newtonsoft.Json', version: '10.0.3'
path: 'lib/netstandard1.3/Newtonsoft.Json.dll'
雖然提供了 Program.deps.json
檔案,.NET Core 還需要一點關於到哪裡定位匹配 deps.json
檔案中程式集的資訊。該配置可以通過如下三種方式之一實現:
-
*.runtimeconfig.dev.json
。這是配置的典型的最佳方式。新增檔案Program.runtimeconfig.dev.json
檔案,其中設定了包資料夾的位置。它類似於Program.runtimeconfig.json
檔案,但它是可選的。典型地其中包含了檔案完全路徑,所以不適於在不同機器上釋出。{
"runtimeOptions": {
"additionalProbingPaths": [
"/Users/nmcmaster/code/packages/"
]
}
} -
命令列。你可以使用
exec
命令來手工指定dotnet
命令中程式集的位置。使用--additionalprobingppath
引數,可以指定多個值。> dotnet exec --additionalprobingpath ./packages/ Program.dll
-
*.runtimeconfig.json
。可以新增一個執行時設定來指定新的探測位置。它可以使用相對路徑。{
"runtimeOptions": {
"framework": {
"name": "Microsoft.NETCore.App",
"version": "2.0.0"
},
"additionalProbingPaths": [
"./packages/"
]
}
}
注意:在我的 .NET Core 3.1 環境下,這個
additionalProbingPaths
沒有工作。這裡是 StackOverflow 上的一個問題:https://stackoverflow.com/questions/56844233/additional-probing-paths-for-net-core-3-migration
7. 總結
對於大多數的開發工作,並不需要這些基礎。類似 NuGet、MSBuild 和 Visual Studio 自動處理獲取應用,C# 檔案,呼叫編譯器,連結到偵錯程式,以及其它任務。但是,我認為知道背後是如何工作的還是非常用用。當然了,你還可以更加深入。在 *.dll 檔案中實際有什麼?什麼是 *.pdb 檔案?什麼是 crossgen 和 libcoreclr?我會把這些留在其它部分中。
8. 參考資訊
-
Specs on runtimeconfig.json and deps.json: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/runtime-configuration-file.md
-
Assembly resolution and dynamic linking: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/corehost.md