CefSharp基於.Net Framework 4.0 框架編譯
本次源碼使用的是Github上CefSharp官方的79版本源碼
准備
IDE
Visual Studio 2017 Enterprise
Environment
Windows10 SDK
VC2013 Redistributale Package x86\x64
組件清單
以下組件按照順序進行編譯最佳
基礎層
- CefSharp(C#)
- CefSharp.Core(C++)
- CefSharp.BrowserSubprocess.Core(C++)
- CefSharp.BrowserSubprocess(C#)
UI層
- CefSharp.WinForms(C#)
Example
-
CefSharp.Example
-
CefSharp.WinForms.Example
開始
建立一個名為CefSharp-DotNet4.0的空的解決方案(下文簡稱sln哈),咱們就開始吧!
CefSharp
首先把79版本的源碼中的CefSharp庫加入到sln中,形成如下的結構:
先不將框架切換為4.0嘗試編譯一下,出現報錯提示:
1>------ Rebuild All started: Project: CefSharp, Configuration: Debug x64 ------
1>CSC : error CS7027: Error signing output with public key from file '..\CefSharp.snk' -- File not found.
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========
檢查79版本的源碼發現,需要將CefSharp.snk文件放置到sln根目錄下,這里照做,然后編譯通過。
接着切換為4.0嘗試編譯,編譯出現大量錯誤,仔細檢查發現有如下幾種:
1、CefSharp.Web.JsonString.FromObject函數的參數DataContractJsonSerializerSettings並不存在
原因:4.0還不存在該種形式的調用
解決辦法:移除該方法的settings參數,移除DataContractJsonSerializerSettings構造函數的settings參數
2、CefSharp.Internals.ConcurrentMethodRunnerQueue.Enqueue方法中,調用了PropertyInfo.GetValue方法報錯
原因:該PropertyInfo.GetValue方法在4.5及以上可以不傳入第二個參數object[] index
解決辦法:GetValue函數傳入第二個參數為null即可
3、CefSharp.SchemeHandler.FolderSchemeHandlerFactory.ISchemeHandlerFactory.Create中WebUtility.UrlDecode報錯
原因:該方法是對一般字符串編碼為Url的實現,在4.5及以上中才有
解決辦法:實現一個相同的功能的方法替換之,因為后續還有些處理轉為4.0后的兼容問題的代碼,所以本人在CefSharp增加了一個ExHelper命名空間,用於存放后續的擴展處理代碼的Helper,這里首先增加一個WebUtilityHelper的處理類,該類有一個靜態方法UrlDecode,其實現本人直接拷貝的.NET 4.7.2的實現,代碼如下:
namespace CefSharp.ExHelper
{
/// <summary>
/// https://referencesource.microsoft.com/#System/net/System/Net/HttpListenerRequest.cs,80a5cbf6a66fa610
/// </summary>
public class WebUtilityHelper
{
private int _bufferSize;
// Accumulate characters in a special array
private int _numChars;
private char[] _charBuffer;
// Accumulate bytes for decoding into characters in a special array
private int _numBytes;
private byte[] _byteBuffer;
// Encoding to convert chars to bytes
private Encoding _encoding;
internal WebUtilityHelper(int bufferSize, Encoding encoding)
{
_bufferSize = bufferSize;
_encoding = encoding;
_charBuffer = new char[bufferSize];
// byte buffer created on demand
}
public static string UrlDecode(string encodedValue)
{
if (encodedValue == null)
return null;
return UrlDecodeInternal(encodedValue, Encoding.UTF8);
}
private static string UrlDecodeInternal(string value, Encoding encoding)
{
if (value == null)
{
return null;
}
int count = value.Length;
WebUtilityHelper helper = new WebUtilityHelper(count, encoding);
// go through the string's chars collapsing %XX and
// appending each char as char, with exception of %XX constructs
// that are appended as bytes
for (int pos = 0; pos < count; pos++)
{
char ch = value[pos];
if (ch == '+')
{
ch = ' ';
}
else if (ch == '%' && pos < count - 2)
{
int h1 = HexToInt(value[pos + 1]);
int h2 = HexToInt(value[pos + 2]);
if (h1 >= 0 && h2 >= 0)
{ // valid 2 hex chars
byte b = (byte)((h1 << 4) | h2);
pos += 2;
// don't add as char
helper.AddByte(b);
continue;
}
}
if ((ch & 0xFF80) == 0)
helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
else
helper.AddChar(ch);
}
return helper.GetString();
}
private static int HexToInt(char h)
{
return (h >= '0' && h <= '9') ? h - '0' :
(h >= 'a' && h <= 'f') ? h - 'a' + 10 :
(h >= 'A' && h <= 'F') ? h - 'A' + 10 :
-1;
}
private void FlushBytes()
{
if (_numBytes > 0)
{
_numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
_numBytes = 0;
}
}
internal void AddChar(char ch)
{
if (_numBytes > 0)
FlushBytes();
_charBuffer[_numChars++] = ch;
}
internal void AddByte(byte b)
{
if (_byteBuffer == null)
_byteBuffer = new byte[_bufferSize];
_byteBuffer[_numBytes++] = b;
}
internal String GetString()
{
if (_numBytes > 0)
FlushBytes();
if (_numChars > 0)
return new String(_charBuffer, 0, _numChars);
else
return String.Empty;
}
}
}
然后在報錯地方進行如下調用:
var filePath = ExHelper.WebUtilityHelper.UrlDecode(Path.GetFullPath(Path.Combine(rootFolder, asbolutePath)));
4、Type.GetTypeInfo報錯
原因:4.0中沒有將Type的信息(TypeInfo)從Type中抽離,所以4.0種的Type並沒有GetTypeInfo的方法
解決辦法:4.0訪問Type的BaseType、IsGenericType等屬性,直接從Type對象調用即可,即如下的形式:
Type type = XXX;
// 4.0版本
bool val = type.IsGenericType;
Type baseType = type.BaseType;
// 4.5以及以上版本
bool val = type.GetTypeInfo().IsGenericType;
Type baseType = type.GetTypeInfo().BaseType;
這里本人將代碼抽離到ExHelper.ReflecionHelper中方便統一調用
5、Task.Run、Task.FromResult以及Task.Delay報錯
原因:在4.0中都不支持上述的幾種方式進行調用
解決方案:通過Nuget加入Microsoft.Bcl、Microsoft.Bcl.Build以及Microsoft.Bcl.Async三個庫到本項目中,然后將上述的所有地方的調用都替換為Microsoft.Threading.Tasks.TaskEx,如下:
// 4.5之后
Task.Run
Task.FromResult
Task.Delay
// 4.0,加入了Bcl之后
TaskEx.Run
TaskEx.FromResult
TaskEx.Delay
這里講一下背景,微軟發布了Microsoft.Bcl.Async的最終版本,參看博客Microsoft.Bcl.Async is Now Stable。該包允許開發者在.NET 4、Silverlight 4和Windows Phone 7.5使用C# 5和VB中的異步特性。該包由三個庫組成:Microsoft.Bcl、Microsoft.Bcl.Async和Microsoft.Bcl.Build。由於使用了程序集統一的方式,解決方案中的所有工程都必須引用這三個庫。
C#發展至今,已經從最初的1.0到了5.0版本:
- 1.0版本 - 基本C#語法。
- 2.0版本 - 泛型的支持,CLR進行了升級,從根本上支持了運行時泛型。
- 3.0版本 - LINQ,添加了
from
/join
等類SQL關鍵字,添加了擴展函數,添加了編譯期動態類型var關鍵字。- 4.0版本 - dynamic關鍵字,CLR進行升級,加入DLR,開始對動態進行友好的支持。同時加入動態參數、參數默認值、泛型協變等特性。
- 5.0版本 - async/await關鍵字,將異步變得更為簡單。
async/await 將異步的編程模型統一為同步模型,簡化開發復雜度,提升生產效率。微軟正式發布了Microsoft.Bcl.Async的最終版本,這讓.NET4里頭也可以用上async/await,而不需要把項目更改為.net 4.5。
這里為了統一入口,本人把這幾個TaskEx的調用收口到ExHelper.TaskHelper中便於查找改動點。
目前為止,我們應該解決了CefSharp庫所有的問題,再次Rebuild該項目,Succeeded!
CefSharp.Core
CefSharp.Core是一個C++的庫,但是由於該C++庫里面調用了一些C#代碼,所以跟.Net Framework版本出現了相關性。這里我們同上一樣,把79版本的CefSharp.Core源碼加入到sln中,右鍵該項目,打開菜單最下面的properties:
這里我們修改3個點:
1、選擇Windows SDK Version。點擊Windows SDK Version右邊的下拉框,選擇我們安裝的Windos10 SDK,如果你和我的SDK版本安裝的是一樣的,應該就是10.0.17763.0,但是理論上Windows8以上的SDK都應該沒啥問題;
2、選擇Platform Toolset為我們安裝的IDE的版本,這里我的就是Visual Studio 2017;
3、手動填入.NET Target Framework Version 為:"v4.0"。
完成上述修改后,我們還需要進行如下的操作:
拷貝79版本源碼解決方案根目錄下的CefSharp.props文件到本sln根目錄下
這么做的原因是在CefSharp.Core的vcxproj文件中(VC++項目編譯文件),有一處Import(自行搜索):
<Import Project="..\CefSharp.props">
然后我們進行編譯Rebuild,不出意外應該還是有大量的錯誤,乍一看出現的錯誤似乎讓人摸不着頭腦,什么" 'AssmblyInfo' : is not a class or namesapce name"等C#問題,可是明顯在這些.NET 4.0上沒有問題。本人突然想起以前在學校學習C/C++的時候,老師告訴我們處理C/C++編譯處理一定要從最上面看,仔細看命令行編譯最開始的地方有兩處warning:
warning MSB3268: The primary reference "E:\Projects\CefSharp-DotNet4.0\CefSharp\bin\x64\Debug\CefSharp.dll" could not be resolved because it has an indirect dependency on the framework assembly "System.Runtime, Version=1.5.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v4.0". To resolve this problem, either remove the reference "E:\Projects\CefSharp-DotNet4.0\CefSharp\bin\x64\Debug\CefSharp.dll" or retarget your application to a framework version which contains "System.Runtime, Version=1.5.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
warning MSB3268: The primary reference "E:\Projects\CefSharp-DotNet4.0\CefSharp\bin\x64\Debug\CefSharp.dll" could not be resolved because it has an indirect dependency on the framework assembly "System.Threading.Tasks, Version=1.5.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v4.0". To resolve this problem, either remove the reference "E:\Projects\CefSharp-DotNet4.0\CefSharp\bin\x64\Debug\CefSharp.dll" or retarget your application to a framework version which contains "System.Threading.Tasks, Version=1.5.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
這兩個warning說我們的CefSharp因為Tasks相關動態庫的版本不對無法編譯,但是我們之前CefSharp已經完成了編譯,似乎沒有什么問題。實際上,我們CefSharp為了兼容使用了Bcl相關組件,上面我們提到:
由於使用了程序集統一的方式,解決方案中的所有工程都必須引用這三個庫。
實際上C++的工程代碼也不例外,所以我們添加Bcl庫代碼到工程中,由於nuget似乎無法為C++工程添加包,所以本人采用手工的方式添加:
1、在vcxproj文件的適當位置添加如下的節點引入Bcl包里面的組件:
<......>
<Reference Include="System.ServiceModel" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Runtime">
<HintPath>..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks"> <HintPath>..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AssemblyInfo.cpp" />
<......>
2、讓編譯的時候識別到該Nuget包,vcxproj文件末尾添加:
<......>
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
</Target>
</Project>
3、修改packge.config文件(沒有則新增)
<?xml version="1.0" encoding="utf-8"?>
<packages>
<!-- cef.sdk是需要的依賴包 -->
<package id="cef.sdk" version="79.1.36" targetFramework="native" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net40" />
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net40" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net40" />
</packages>
上述操作完成后需要進行Restore還原一下cef.sdk的NuGet包,然后再次進行編譯,發現warning已經消除,但是還是編譯失敗,還是有很多“報錯”,本人一開始找問題也找了很久以為全都是error,后來發現很多都是warning,最后發現2處關鍵點error(你們可以先自行搜索這兩個地方,雙擊就可以跳轉到對應的報錯處):
1、在CefSharp::Internals::Serialization中的SerializeV8SimpleObject有一處GetValue調用報錯:
error C2661: 'System::Reflection::PropertyInfo::GetValue': no overloaded function takes 1 arguments
2、在CefSharp::Internals::JavascriptCallbackProxy中的ExecuteWithTimeoutAsync有一處調用報錯:
error C2039: 'FromResult': is not a member of 'System::Threading::Tasks::Task'
這兩處很明顯是使用了C#的代碼,且該代碼是 .Net4.0不支持的,原因以及解決方法在上面的CefSharp中已經說了。這里我們修改按照C++語法改寫下代碼:
1、
auto propertyValue = properties[i]->GetValue(obj); // 4.5.2
auto propertyValue = properties[i]->GetValue(obj, nullptr); // 4.0
2、
Task::FromResult(invalidFrameResponse); // 4.5.2
ExHelper::TaskHelper::FromResult(invalidFrameResponse); // 4.0 命名空間就對應的CefSharp里面的Helper
完成操作后,Rebuild sln,Succeeded!
CefSharp.BrowserSubprocess.Core
同上操作,將4.5.2源碼加入到sln中,和上述CefSharp.Core相同方式:
1、修改properties;
2、增加Bcl包的依賴到vsxproj中。
完成操作后,直接進行Rebuild操作,因為該C++庫並不涉及到C#的代碼,所以只需要做上述增加Bcl庫的相關操作,編譯成功!
CefSharp.BrowserSubprocess
同上操作,將4.5.2源碼加入到sln中,然后:1、切換版本為.NET 4.0;2、增加Bcl相關依賴包。因為是C#項目我們終於不用手工給csproj添加節點了,可以使用nuget添加Bcl三個包。
添加完成后我們嘗試編譯該組件,不知道為什么,在我的機器上編譯過程會出現如下的錯誤:
找不到命令的錯誤提示
但是查看編譯結果還有輸出目錄能夠看到是編譯成功的,我也索性沒有繼續看下去了
CefSharp.WinForm
終於到我們的UI層了,如上方式添加源碼到項目里,然后:1、切換版本為.NET 4.0;2、增加Bcl相關依賴包。(如果你切換了框架后,右鍵該項目-Manage NuGet Packages出現報錯nuget is invalid,請嘗試關閉解決方案重新打開)。編譯該項目,不出意外,編譯成功~
至此,跟.NET Framework綁定的代碼已經全部編譯通過,本來到此步驟,我們的編譯工作已經完成了,但是官方提供了Example讓我們可以調用看看樣例,本人索性把Example和WinForm.Example兩個工程也一並.NET 4.0化了。
CefSharp.Example
該組件並非是必須組件,但是后續無論是Wpf還是WinForm的Example運行,都需要該組件,所以我們還是把它也.NET 4.0化。
還是上述方式,添加到項目,然后:1、切換版本為.NET 4.0;2、增加Bcl相關依賴包。最后嘗試進行編譯,出現編譯錯誤:
1、在CefSharp.Example.Handlers.DownloadHandler.OnBeforeDownloadFired函數中,定義的Eventhandler的泛型參數DownloadItem並不是EventArgs子類
原因:在4.5之后,EventHandler的泛型參數可以不是EventArgs的子類,而在.Net 4.0必須是繼承自EventArgs
解決辦法:因為DownloadItem較為公共,我們不方便將其繼承EventArgs,所以我們單獨寫一個自己的EventHandler,讓其泛型參數接收任意類型:
public delegate void DownloadEventHandler<in T>(object sender, T args);
// 改成我們的自己的下載事件
public event DownloadEventHandler<DownloadItem> OnBeforeDownloadFired;
public event DownloadEventHandler<DownloadItem> OnDownloadUpdatedFired;
再次編譯,還會有一些剩下的和Task相關的編譯報錯問題,上文已經解釋了原因和提供了解決方案,Do It!完成修改后,編譯成功!
CefSharp.WinForm.Example
我們依然如上的方式進行工程的添加,添加的過程會彈出提示框報如下的錯誤:
---------------------------
Microsoft Visual Studio
---------------------------
The imported project "E:\Projects\CefSharp-DotNet4.0\CefSharp.Native.props" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk. E:\Projects\CefSharp-DotNet4.0\CefSharp.WinForms.Example\CefSharp.WinForms.Example.csproj
---------------------------
確定
---------------------------
上述提示表明,我們缺少CefSharp.Native.props,在官方源碼中的解決方案根目錄下找到對應的文件拷貝到我們的目錄下。拷貝完成后,我們先不進行切換Framework和添加Bcl依賴包的操作,我們首先打開該項目的package.config文件,可以看到有如下的內容:
<package id="cef.redist.x64" version="79.1.36" targetFramework="net462" />
<package id="cef.redist.x86" version="79.1.36" targetFramework="net462" />
<package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net462" developmentDependency="true" />
很明顯和我們即將要切換的.NET4.0不符合,接下來我們再進行如下操作:
1、先Restore這些NuGet包,然后卸載掉,最后再切換為4.0;
2、只安裝Bcl相關組件包,不安裝上述卸載的cef.redist和Compiler
進行編譯,不出意外會出現如下的幾個編譯錯誤:
1、error CS0117: 'TaskContinuationOptions' does not contain a definition for 'HideScheduler'
原因:Net4.0中沒有這個定義
解決辦法:因為是Demo,我們使用的TaskContinuationOptions.None的枚舉暫時避過編譯
2、error CS0103: The name 'AppContext' does not exist in the current context
原因:Net4.0中沒有這個定義
解決辦法:這里的目的是獲取CefSharp.Example\Extensions里面的文件,我們使用System.AppDomain.CurrentDomain.BaseDirectory即可
剩下的幾個編譯問題還是Task的問題,不在贅述。
完成編譯以后,我們嘗試運行該WinForm.Example,提示:
未能加載文件或程序集“CefSharp.Core.dll”或它的某一個依賴項。找不到指定的模塊
檢查Bin目錄下的,發現已經有了該dll,那么就是缺少了CefSharp.Core.dll需要的組件。實際上,剛才我們移除了2個NuGet依賴包:
cef.redist.x64、cef.redist.x86,這里面是Cef的核心資源與類庫,就包含了CefSharp.Core所需要的所有資源。
重新安裝這兩個組件包,但需要注意的是對應版本一定要對應當前的版本(79.1.36)。安裝完成后,我們檢查packages里面的cef.redist組件包,可以看到CEF文件夾下面有我們需要的ceflib.dll等類庫和資源:
locales(dir)
swiftshader(dir)
cef.pak
cef_100_percent.pak
cef_200_percent.pak
cef_extensions.pak
chrome_elf.dll
d3dcompiler_47.dll
devtools_resources.pak
icudtl.dat
libcef.dll
libEGL.dll
libGLESv2.dll
natives_blob.bin
README.txt
snapshot_blob.bin
v8_context_snapshot.bin
我們再次運行,終於出現關於WinForm的Example!
制品梳理
- NuGet引用Microsoft.Bcl、Microsoft.Bcl.Build以及Microsoft.Bcl.Async
引入上述3個依賴庫組件是因為我們為了將CefSharp代碼使用.NET 4.0進行編譯,兼容async/await等Task相關的特性而引入的
Miscrosoft.Threading.Tasks.dll
Miscrosoft.Threading.Tasks.Extensions.dll
Miscrosoft.Threading.Tasks.Extensions.Desktop.dll
System.IO.dll
System.Runtime.dll
System.Threading.Tasks.dll
- NuGet引用cef.redist. x86/x64
該NuGet包中包含Cef原生需要的組件和資源包,包括核心的ceflib.dll,具體內容請查看packages/cef.redist. x86/x64/CEF中的所有。
- 基於DotNet 4.0編譯的CefSharp核心依賴庫
CefSharp(C#)
CefSharp.Core(C++)
CefSharp.BrowserSubprocess.Core(C++)
CefSharp.BrowserSubprocess(C#)
CefSharp.WinForms(C#)