libCEF总结01下载、编译、入门

 

1 下载    1

1.1 下载    1

1.2 合并    1

2 cmake    4

2.1 编译简介    4

2.2 下载cmake    4

2.3 运行cmake    5

3 编译进阶    7

3.1 创建项目    7

3.2 添加源文件    8

3.2.1 下载vcHelper    8

3.2.2 使用vcHelper    8

3.3 配置VC++项目    9

3.3.1 不使用预编译头文件    9

3.3.2 增加宏定义    10

3.3.3 设置头文件查找目录    11

3.4 其它    12

3.4.1 使用VC++2008编译    12

3.4.2 修改#include语句    13

4 入门示例    16

4.1 简介    16

4.2 创建项目    16

4.3 编码    17

4.3.1 修改stdafx.h    17

4.3.2 增加cefSimple.h    20

4.3.3 修改mfcCEF.h    21

4.3.4 修改mfcCEF.cpp    21

4.3.5 修改mfcCEFDlg.h    23

4.3.6 修改mfcCEFDlg.cpp    23

4.4 运行    24

4.5 程序运行逻辑    25

 

 

1 下载

1.1 下载

下载网址为:http://www.magpcss.net/cef_downloads/,显示如下图所示:

1.1

下载后的文件名如下所示:

cef_binary_3.2785.1466.g80e473e_windows32.tar.tar

cef_binary_3.2785.1466.g80e473e_windows64.tar.tar

1.2 合并

解压上一节的两个压缩包,并使用 Beyond Compare 进行二进制比较,如下图所示:

1.2

可见:只有 DebugReleaseResources 三个文件夹中的某些文件存在差异。所以,可将32位的DebugReleaseResources重命名为Debug32Release32Resources3264位的DebugReleaseResources重命名为Debug64Release64Resources64。然后,将两个文件夹合并。

笔者将两个文件夹合并到了W:\libCEF\v3.2785.1466\unzip,其目录结构如下图所示:

1.3

 

 

2 cmake

2.1 编译简介

这里的编译主要指的是 libcef_dll_wrapper 的编译。它是libcef.dll的包装工程,客户端程序通过它可以间接的访问libcef.dll

libCEF较早的版本(如3.2171.1979),包含有VC++项目文件(*.sln),可直接打开进行编译。目前的版本不再包含这些*.sln文件,使用VC++编译前可使用cmake程序生成*.sln文件。

2.2 下载cmake

cmake的下载网址为:https://cmake.org/download/。显示如下图所示:

2.1

说明如下:

cmake-3.6.1.zip                cmake的源代码

cmake-3.6.1-win32-x86.msi        安装包(32 )

cmake-3.6.1-win32-x86.zip        直接运行(32 )

cmake-3.6.1-win64-x64.msi        安装包(64 )

cmake-3.6.1-win64-x64.zip        直接运行(64 )

这里,下载 cmake-3.6.1-win32-x86.zip 即可。

2.3 运行cmake

解压cmake-3.6.1-win32-x86.zip,运行cmake-gui.exe,将显示如下界面:

2.2

"Where is the source code:"右边的文本框内请输入图1.3CMakeLists.txt所在的目录,即W:\libCEF\v3.2785.1466\unzip

"Where to build the binaries:"右边的文本框内请输入一个目录,cmake将在这个目录下生成VC++项目文件(*.sln;*.vcproj;*.vcxproj)。这个目录可以和"Where is the source code:"的设置相同。

单击上图的"Configure"按钮,将弹出上图右下角的界面。在这个界面里选择VC++编译器,如:使用vc2010编译64位程序,请选择"Visual Studio 10 2010 Win64"。这里选择Visual Studio 10 2010,然后单击"Finish"按钮。

单击上图的"Generate"按钮,cmake将在W:/libCEF/v3.2785.1466/make/vc2010里生成vc2010的项目文件cef.slnALL_BUILD.vcxproj……如下图所示:

2.3

使用Visual Studio 2010打开上图的cef.sln,可以发现有五个项目。如下图所示:

2.4

libcef_dll_wrapper        libcef.dll的包装,是编译的重点

cefclient                一个调用libcef的示例程序,较复杂

cefsimple                一个调用libcef的示例程序,较简单

ZERO_CHECK            再次运行cmake程序,检查哪些文件被修改了

ALL_BUILD            编译它,则ZERO_CHECKlibcef_dll_wrapper

                        cefclientcefsimple将依次被编译

 

3 编译进阶

本章将说明如何在不使用cmake的情况下编译libcef_dll_wrapper

3.1 创建项目

创建VC++静态库项目libcef_dll_wrapper。以VC++2010为例,创建项目时首先选择"Win32 Project",如下图所示:

3.1

接下来的设置如下图所示:即创建一个静态库,不使用预编译头文件。

3.2

3.2 添加源文件

将图1.3文件夹libcef_dll及其子文件夹下的所有源文件(*.cc)添加到上一节创建的VC++项目libcef_dll_wrapper里。

3.2.1 下载vcHelper

手动添加所有的源文件比较繁琐,可借助工具vcHelper来完成此项工作。其下载方法为:

1、进入百度网盘 http://pan.baidu.com/s/1gd7XDkf

2、进入目录 public\Tools\vcHelper

3、下载vcHelper的最新版本。

3.2.2 使用vcHelper

运行vcHelper,进入"源文件树"页面,如下图所示:

3.3

接下来的操作步骤为:

1、拖放文件夹libcef_dll至"生成文件树"内的文本框内,文件夹libcef_dll的全路径名(上图中的W:\libCEF\v3.2785.1466\unzip\libcef_dll)即被自动添加到该文本框内。当然,也可手工输入或粘贴这个全路径名;

2、单击"生成文件树"按钮。根据上图可知:vcHelper找到了263个文件;

3、拖放VC++项目libcef_dll_wrapper所在文件夹(W:\libCEF\v3.2785.1466\libcef_dll_wrapper)至"替换文件树"内的文本框内。vcHelper将在该文件夹内查找所有的VC++工程文件(*.vcproj*.vcxproj),并将其全路径名添加到相应的文本框内;

4、单击"替换文件树"按钮,文件树(263个文件)将被添加到VC++工程文件里。

打开项目libcef_dll_wrapper,可以看到源文件树。如下图所示:

3.4

3.3 配置VC++项目

3.3.1 不使用预编译头文件

具体的配置如下图所示:

3.5

3.3.2 增加宏定义

增加宏定义:USING_CEF_SHARED=1NOMINMAX,如下图所示:

3.6

USING_CEF_SHARED 有两个作用:

1、调用libcef.dll里的函数时,会使用__declspec(dllimport),详见include\internal\cef_export.h文件;

2、编译时防止有效代码被屏蔽,如文件libcef_dll\cpptoc\views\browser_view_delegate_cpptoc.h里有如下代码:

#ifndef USING_CEF_SHARED

#pragma message("Warning: "__FILE__" may be accessed wrapper-side only")

#else // USING_CEF_SHARED

class CefBrowserViewDelegateCppToC

: public CefCppToC<CefBrowserViewDelegateCppToC, CefBrowserViewDelegate,

cef_browser_view_delegate_t> {

public:

CefBrowserViewDelegateCppToC();

};

#endif

不定义宏USING_CEF_SHARED,则有效代码(蓝色部分)将被屏蔽掉。

NOMINMAX的作用:取消宏minmax的定义,防止它们与std::minstd::max冲突。以下内容节选自windef.h

#ifndef NOMINMAX

 

#ifndef max

#define max(a,b) (((a) > (b)) ? (a) : (b))

#endif

 

#ifndef min

#define min(a,b) (((a) < (b)) ? (a) : (b))

#endif

 

#endif /* NOMINMAX */

亦即:定义宏NOMINMAX之后,宏minmax将不会被定义。

3.3.3 设置头文件查找目录

编译libcef_dll_wrapper时,需要用到W:\libCEF\v3.2785.1466\unzip\include目录下的头文件。为此,需要设置W:\libCEF\v3.2785.1466\unzip为头文件查找目录。具体设置如下图所示:

3.7

上图中的../../../unzipW:\libCEF\v3.2785.1466\unzip相对于文件W:\libCEF\v3.2785.1466\libcef_dll_wrapper\make-libT\vc2010\libcef_dll_wrapperT.vcxproj的相对路径。可用vcHelper获得这个相对路径,如下图所示:

3.8

输入"基准目录/文件"和"绝对路径",单击按钮">>"即可获得相对路径。

3.4 其它

3.4.1 使用VC++2008编译

libcef_dll_wrapper默认情况下至少需要VC++2010才能编译通过。使用VC++2008编译时,会提示无法找到stdint.h,为此特做如下修改:

include目录下新建文件stdint.h,其内容如下:

#pragma once

#if _MSC_VER >= 1600 //VC++2010 ~ VC++2015

#include <stdint.h>

#else

typedef signed char int8_t;

typedef short int16_t;

typedef int int32_t;

typedef unsigned char uint8_t;

typedef unsigned short uint16_t;

typedef unsigned int uint32_t;

typedef signed char int_least8_t;

typedef short int_least16_t;

typedef int int_least32_t;

typedef unsigned char uint_least8_t;

typedef unsigned short uint_least16_t;

typedef unsigned int uint_least32_t;

typedef char int_fast8_t;

typedef int int_fast16_t;

typedef int int_fast32_t;

typedef unsigned char uint_fast8_t;

typedef unsigned int uint_fast16_t;

typedef unsigned int uint_fast32_t;

#ifdef _WIN64

typedef __int64 intptr_t;

#else /* _WIN64 */

typedef int intptr_t;

#endif /* _WIN64 */

#endif

亦即:VC++2010及其以上版本直接包含自带的stdint.h头文件,否则就自定义int8_tint16_t……

接下来,修改源代码中的#include <stdint.h>#include "../../include/stdint.h"。注意双引号内的相对路径不要弄错。

3.4.2 修改#include语句

如图3.7所示,使用libCEF的客户端项目,必须设置libCEF的头文件查找目录,否则将无法正常编译。

W:\libCEF\v3.2785.1466\unzip\libcef_dll\wrapper\libcef_dll_wrapper.cc为例进行说明。该文件包含如下代码:

#include "include/cef_app.h"

它其实想包含的是W:\libCEF\v3.2785.1466\unzip\include\cef_app.h。为此,需要设置W:\libCEF\v3.2785.1466\unzip为头文件查找目录,这样编译器在编译时就能顺利找到文件include/cef_app.h了。

现在,如果把#include语句修改一下,如下所示:

#include "../../include/cef_app.h"

编译W:\libCEF\v3.2785.1466\unzip\libcef_dll\wrapper\libcef_dll_wrapper.cc时,编译器会使用头文件W:\libCEF\v3.2785.1466\unzip\libcef_dll\wrapper\..\..\include\cef_app.h,即W:\libCEF\v3.2785.1466\unzip\include\cef_app.h。这样,不用设置头文件查找目录,编译器也能找到头文件cef_app.h了。

也就是说:#include语句中的包含路径全部更改为相对路径,就不再需要设置头文件查找目录了

#include语句那么多,手工逐条修改将是一项浩大的工程。可借助vcHelper完成此项工作。如下图所示:

3.9

具体操作步骤如下:

1、设置"源文件目录"和"引用目录"为W:\libCEF\v3.2785.1466\unzip

2、勾中"修改#include""语句"复选框,不要勾中"修改#include<>语句"复选框,不要勾中"修改#include里的扩展名"

3、单击"扫描"按钮;

4、单击"自动修改"按钮,vcHelper将自动修改#include""语句,并将无法修改的语句显示到右侧的列表中;

5、右侧列表中,单击"数量"列标题,整个列表将按数量进行排序。数量2表示包含文件(ef_callback.hresource.h)有两个。此时鼠标左键双击该行,将显示如下界面:

3.10

单击按钮"UltraEdit",vcHelper将调用UltraEdit,并打开文件urlrequest_test.cc,然后跳转至第10行。如下图所示:

3.11

根据上图可知,urlrequest_test.cc需要的是base目录下的cef_callback.h。因此应该选择图3.10中的第一条修改建议。鼠标左键双击此修改建议,然后粘贴到上图的第10行,即可完成此行语句的修改。

注意:如果vcHelper无法正常调用UltraEdit,请在vcHelperConfig.txt里配置UltraEdit的全路径名,如下图所示:

3.12

 

 

4 入门示例

4.1 简介

本章参考了开源项目CEF3SimpleSample,其下载网址为:

https://github.com/acristoffers/CEF3SimpleSample

CEF3SimpleSample是一个SDK程序,本章对其进行精简,并使用了MFC。最终实现了如下图所示的效果——运行后将显示新浪网的首页。

4.1

4.2 创建项目

创建一个MFC的对话框程序mfcCEF。创建项目时的配置请见下图:

4.2

4.3 编码

4.3.1 修改stdafx.h

stdafx.h的最后增加如下代码

#define STR(x) #x

#define STR2(x) STR(x)

#define INCLUDE(f) STR2(PATH(f))

//包含 libCEF 的头文件

//下面的宏定义,##之前是 libCEF include 目录相对于本文件的相对路径

#define PATH(f) ../../libCEF/v3.2785.1466/unzip/include/##f

#define USING_CEF_SHARED 1

#include INCLUDE(cef_app.h)

#include INCLUDE(cef_client.h)

#undef PATH

 

#include "cefSimple.h"

 

//下面的宏定义是 libCEF 库文件相对于 vc 项目文件(*.vcproj/*.vcxproj)的相对路径

#define PATH "../../libCEF/v3.2785.1466/"

//连接库文件 libcef.lib

#ifdef _WIN64

#ifdef _DEBUG

#define PATH1 "unzip/Debug64/"

#else

#define PATH1 "unzip/Release64/"

#endif

#else

#ifdef _DEBUG

#define PATH1 "unzip/Debug32/"

#else

#define PATH1 "unzip/Release32/"

#endif

#endif

#pragma comment(lib,PATH PATH1 "libcef.lib")

#undef PATH1

//连接库文件 libcef_dll_wrapper.lib

#define PATH1 "libcef_dll_wrapper/bin/"

#if _MSC_VER==1500 //VC++9.0(VC2008)

#define PATH2 "vc2008"

#elif _MSC_VER==1600 //VC++10.0(VC2010)

#define PATH2 "vc2010"

#elif _MSC_VER==1700 //VC++11.0(VC2012)

#define PATH2 "vc2012"

#elif _MSC_VER==1800 //VC++12.0(VC2013)

#define PATH2 "vc2013"

#elif _MSC_VER==1900 //VC++14.0(VC2015)

#define PATH2 "vc2015"

#else

#error 未知的 VC++ 编译器

#endif

#ifdef _WIN64

#define PATH3 "-x64"

#else

#define PATH3 "-Win32"

#endif

#ifdef _DEBUG

#ifdef _UNICODE

#define PATH4 "-DU/"

#else

#define PATH4 "-DA/"

#endif

#else

#ifdef _UNICODE

#define PATH4 "-RU/"

#else

#define PATH4 "-RA/"

#endif

#endif

#ifdef _MT

#ifdef _DLL //使用多线程 DLL 版的 C 函数库

#define PATH5 "libcef_dll_wrapperD.lib"

#else //使用多线程版的 C 函数库

#define PATH5 "libcef_dll_wrapperT.lib"

#endif

#else //使用单线程版的 C 函数库

#define PATH5 "libcef_dll_wrapperS.lib"

#endif

#pragma comment(lib,PATH PATH1 PATH2 PATH3 PATH4 PATH5)

#undef PATH1

#undef PATH2

#undef PATH3

#undef PATH4

#undef PATH5

#undef PATH

 

#undef STR

#undef STR2

#undef INCLUDE

说明:

1#define USING_CEF_SHARED 1 表示导入libcef.dll的导出函数;

2VC++编译预处理器对#include INCLUDE(cef_app.h)的预处理:

首先变为 #include STR2(PATH(cef_app.h))

然后变为 #include STR(../../libCEF/v3.2785.1466/unzip/include/cef_app.h)

最后变为 #include "../../libCEF/v3.2785.1466/unzip/include/cef_app.h"

"#define PATH(f) ../../libCEF/v3.2785.1466/unzip/include/##f"中的../../libCEF/v3.2785.1466/unzip/include/libCEF include 目录(W:\libCEF\v3.2785.1466\unzip\include)相对于本文件(W:\VC\mfcCEF\stdafx.h)的相对目录。请根据实际情况修改这个相对路径。

注意:若要使用相对路径包含libCEF头文件,请按照3.4.2节的说明把libCEF源代码里的#include语句都修改掉。

3、如果使用vc2010编译器,编译Debug版,则#pragma comment(lib,PATH PATH1 "libcef.lib")将被展开为:#pragma comment(lib,"../../libCEF/v3.2785.1466/" "unzip/Debug32/" "libcef.lib")也就是#pragma comment(lib,"../../libCEF/v3.2785.1466/unzip/Debug32/libcef.lib")

../../libCEF/v3.2785.1466/unzip/Debug32/libcef.lib是库文件(W:\libCEF\v3.2785.1466\unzip\Debug32\libcef.lib)相对于vc项目文件(W:\VC\mfcCEF\mfcCEF.vcxproj)的相对路径。请根据实际情况修改"#define PATH "../../libCEF/v3.2785.1466/""中的相对路径。

4libcef_dll_wrapper库文件

笔者的电脑上libcef_dll_wrapper库文件的路径为:

W:\libCEF\v3.2785.1466\libcef_dll_wrapper\bin\vc2010-Win32-RU

vc2010还有可能是vc2012/vc2013/vc2015

Win32还有可能是x64

RURelease Unicode)还有可能是RARelease Ansi)、DADebug Ansi)、DUDebug Unicode

库文件名有两种:

libcef_dll_wrapperT.lib        使用的C函数库是多线程版

libcef_dll_wrapperD.lib        使用的C函数库是多线程DLL

如果客户端程序Use MFC in a Static Library,就会自动连接libcef_dll_wrapperT.lib;如果客户端程序Use MFC in a Shared DLL,就会自动连接libcef_dll_wrapperD.lib

4.3.2 增加cefSimple.h

给项目增加头文件cefSimple.h,其内容如下所示:

#pragma once

 

class SimpleApp : public CefApp

{

private:

IMPLEMENT_REFCOUNTING(SimpleApp);

};

 

class SimpleHandler : public CefClient

, public CefLifeSpanHandler

{

public:

SimpleHandler()

{

m_hWndBrowser = NULL;

}

public://CefClient methods:

virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler()

OVERRIDE { return this; }

public://CefLifeSpanHandler methods:

virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser)

OVERRIDE {

m_hWndBrowser = browser->GetHost()->GetWindowHandle();

}

public:

HWND m_hWndBrowser;

IMPLEMENT_REFCOUNTING(SimpleHandler);

};

4.3.3 修改mfcCEF.h

修改后的内容如下

#pragma once

 

#ifndef __AFXWIN_H__

    #error "在包含此文件之前包含"stdafx.h"以生成 PCH 文件"

#endif

 

#include "resource.h"

 

class CmfcCEFApp : public CWinApp

{

public:

    CmfcCEFApp();

public:

    virtual BOOL InitInstance();

    DECLARE_MESSAGE_MAP()

};

 

extern CmfcCEFApp theApp;

4.3.4 修改mfcCEF.cpp

修改后的内容如下

#include "stdafx.h"

#include "mfcCEF.h"

#include "mfcCEFDlg.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

 

BEGIN_MESSAGE_MAP(CmfcCEFApp, CWinApp)

END_MESSAGE_MAP()

 

CmfcCEFApp::CmfcCEFApp()

{

}

 

CmfcCEFApp theApp;

 

BOOL CmfcCEFApp::InitInstance()

{

CWinApp::InitInstance();

_tsetlocale(LC_ALL,_T(""));

{//CEF 初始化

CefMainArgs ma(m_hInstance);

CefRefPtr<SimpleApp> app(new SimpleApp());

int nCEP = CefExecuteProcess(ma,app.get(),NULL);

if(nCEP >= 0) { exit(nCEP); }

CefSettings settings;

CefString(&settings.locale) = L"zh-CN";

settings.no_sandbox = 1;

settings.multi_threaded_message_loop = 1;

CefInitialize(ma, settings, app.get(),NULL);

}

{

CmfcCEFDlg dlg;

m_pMainWnd = &dlg;

dlg.DoModal();

}

CefShutdown(); //退出 CEF

return FALSE;

}

4.3.5 修改mfcCEFDlg.h

修改后的内容如下

#pragma once

 

class CmfcCEFDlg : public CDialog

{

public:

    CmfcCEFDlg(CWnd* pParent = NULL);

    enum { IDD = IDD_MFCCEF_DIALOG };

protected:

    virtual void DoDataExchange(CDataExchange* pDX);

protected:

    virtual BOOL OnInitDialog();

afx_msg void OnSize(UINT nType, int cx, int cy);

    DECLARE_MESSAGE_MAP()

protected:

CefRefPtr<SimpleHandler> m_Handler;

};

4.3.6 修改mfcCEFDlg.cpp

修改后的内容如下

#include "stdafx.h"

#include "mfcCEF.h"

#include "mfcCEFDlg.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

 

CmfcCEFDlg::CmfcCEFDlg(CWnd* pParent /*=NULL*/)

: CDialog(CmfcCEFDlg::IDD, pParent)

,m_Handler(new SimpleHandler())

{

}

 

void CmfcCEFDlg::DoDataExchange(CDataExchange* pDX)

{

    CDialog::DoDataExchange(pDX);

}

 

BEGIN_MESSAGE_MAP(CmfcCEFDlg, CDialog)

ON_WM_SIZE()

END_MESSAGE_MAP()

 

BOOL CmfcCEFDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

CefWindowInfo wi;

RECT rc;

GetClientRect(&rc);

wi.SetAsChild(m_hWnd,rc);

CefBrowserSettings bs;

CefBrowserHost::CreateBrowser(wi,m_Handler.get()

,L"www.sina.com",bs,NULL);

return TRUE;

}

 

void CmfcCEFDlg::OnSize(UINT nType, int cx, int cy)

{

CDialog::OnSize(nType, cx, cy);

HWND hWnd = m_Handler->m_hWndBrowser;

if(hWnd)

{

RECT rc;

GetClientRect(&rc);

::MoveWindow(hWnd,0,0,rc.right,rc.bottom,FALSE);

Invalidate();

}

}

4.4 运行

编译mfcCEF成功后,先不要急着运行程序。因为mfcCEF.exe运行时需要一些文件,需要把它们复制到mfcCEF.exe所在目录。

假如mfcCEF.exe32位的Release版,则

1、复制图1.3Release32文件夹内的所有文件到mfcCEF.exe所在目录。注意:*.lib259M,运行时不需要这些文件,所以就不用复制了。

2、复制图1.3Resources32文件夹内的所有文件到mfcCEF.exe所在目录。

文件复制完成后,mfcCEF.exe所在目录如下图所示:

4.3

现在,就可以运行mfcCEF.exe了。

4.5 程序运行逻辑

运行mfcCEF,代码的执行顺序如下:

1、执行 CmfcCEFApp::InitInstance,调用 CefInitialize 初始化 CEF

2CmfcCEFApp::InitInstance 里的 dlg.DoModal() 显示程序主界面;

3、程序主界面被创建时,执行 CmfcCEFDlg::OnInitDialog,调用 CefBrowserHost::CreateBrowser 创建浏览器窗口;

4、浏览器窗口创建完毕后,执行 SimpleHandler::OnAfterCreated,获得浏览器窗口的句柄 m_hWndBrowser

5、浏览器窗口是程序主界面窗口的子窗口。调整主界面大小时,将执行 CmfcCEFDlg::OnSizeOnSize 函数里移动浏览器窗口(句柄为 m_hWndBrowser)使其占满主界面窗口的客户区;

6、用户退出主界面,CmfcCEFApp::InitInstance 里的 dlg.DoModal() 将返回。同时对象 dlg 将被析构,其成员变量m_Handler 也将被析构。CefRefPtr<SimpleHandler> 使用了计数功能,m_Handler 析构时计数值减一后等于零,new SimpleHandler() 创建的对象将被自动 delete

7CmfcCEFApp::InitInstance 里的 CefShutdown() 被执行,退出 CEF

8CmfcCEFApp::InitInstance 返回 FALSE,整个程序不进入消息循环,而是结束。

下面说明一下对象new SimpleApp()的生命周期:

1CefRefPtr<SimpleApp> app(new SimpleApp()) 后对象的计数值为 1

2CefInitialize(ma, settings, app.get(),NULL) 后对象的计数值为2

3app 析构后对象的计数值为 1

4CefShutdown() 后,app 的计数值为 0,对象自动被 delete

posted @ 2016-12-13 10:06  hanford  阅读(2126)  评论(0编辑  收藏  举报