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
2
3
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的实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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;
}
}

}

然后在报错地方进行如下调用:

1
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对象调用即可,即如下的形式:

1
2
3
4
5
6
7
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,如下:

1
2
3
4
5
6
7
8
// 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. 1.0版本 - 基本C#语法。
  2. 2.0版本 - 泛型的支持,CLR进行了升级,从根本上支持了运行时泛型。
  3. 3.0版本 - LINQ,添加了from / join等类SQL关键字,添加了扩展函数,添加了编译期动态类型var关键字。
  4. 4.0版本 - dynamic关键字,CLR进行升级,加入DLR,开始对动态进行友好的支持。同时加入动态参数、参数默认值、泛型协变等特性。
  5. 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(自行搜索):

1
<Import Project="..\CefSharp.props">

然后我们进行编译Rebuild,不出意外应该还是有大量的错误,乍一看出现的错误似乎让人摸不着头脑,什么" ‘AssmblyInfo’ : is not a class or namesapce name"等C#问题,可是明显在这些.NET 4.0上没有问题。本人突然想起以前在学校学习C/C的时候,老师告诉我们处理C/C编译处理一定要从最上面看,仔细看命令行编译最开始的地方有两处warning:

1
2
3
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包里面的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<......>
<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文件末尾添加:

1
2
3
4
5
6
7
8
9
10
11
 <......>
<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文件(没有则新增)

1
2
3
4
5
6
7
8
<?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调用报错:

1
error C2661: 'System::Reflection::PropertyInfo::GetValue': no overloaded function takes 1 arguments

**2、**在CefSharp::Internals::JavascriptCallbackProxy中的ExecuteWithTimeoutAsync有一处调用报错:

1
error C2039: 'FromResult': is not a member of 'System::Threading::Tasks::Task'

这两处很明显是使用了C#的代码,且该代码是 .Net4.0不支持的,原因以及解决方法在上面的CefSharp中已经说了。这里我们修改按照C++语法改写下代码:

1
2
3
4
5
6
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三个包。

添加完成后我们尝试编译该组件,不知道为什么,在我的机器上编译过程会出现如下的错误:

1
找不到命令的错误提示

但是查看编译结果还有输出目录能够看到是编译成功的,我也索性没有继续看下去了

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,让其泛型参数接收任意类型:

1
2
3
4
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

我们依然如上的方式进行工程的添加,添加的过程会弹出提示框报如下的错误:

1
2
3
4
5
6
7
---------------------------
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文件,可以看到有如下的内容:

1
2
3
<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等类库和资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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相关的特性而引入的

1
2
3
4
5
6
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核心依赖库
1
2
3
4
5
CefSharp(C#)
CefSharp.Core(C++)
CefSharp.BrowserSubprocess.Core(C++)
CefSharp.BrowserSubprocess(C#)
CefSharp.WinForms(C#)