日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

使用CEF(七)详解macOS下基于CEF的多进程应用程序CMake项目搭建

發布時間:2023/12/24 windows 51 coder
生活随笔 收集整理的這篇文章主要介紹了 使用CEF(七)详解macOS下基于CEF的多进程应用程序CMake项目搭建 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

由于macOS下的應用程序結構導致了CEF這樣的多進程架構程序在項目結構、運行架構上有很多細節需要關注,這一塊的內容比起Windows要復雜的多,所以本文將會聚焦macOS下基于CEF的多進程應用架構的環境配置,并逐一說明了CMake的相關用法和CEF應用配置細節。

前言

在進行搭建之前,我們首先必須要弄清楚一個問題,我們最終到底要生成幾個可執行應用。為什么要搞清楚這個問題呢?了解CEF的讀者都知道,CEF屬于多進程架構體系,包含有一個主進程管理整個瀏覽器應用(包括原生GUI窗體等),以及多種類型的子進程各自獨立負責各自的職責(比如渲染進程以及GPU加速進程等)。

筆者在以前的文章中曾介紹過CEF中提供的樣例cefsimple在Windows操作系統上的構建流程,我們發現這個cefsimple項目在編譯后會最終只生成了一個exe可執行程序,而在運行時為了達到多進程的目的,該exe首先作為主進程入口啟動,內部在準備啟動子進程的時候,其做法是調用該exe本身,并通過命令行參數的形式來區分主進程和其他子進程。也就是說,該exe應用內部不僅包含了主進程代碼,也包含了子進程代碼,源代碼中會根據命令行參數(--type=xxx)通過分支讓主進程和子進程走到不同的邏輯:

而在macOS下,由于macOS本身對于應用程序的權限管理與Windows存在差異,它具備有一套特殊的沙盒機制來保證應用程序彼此獨立和安全。所以,我們不建議像Windows那樣最終通過編譯生成一個App Bundle,來多次啟動自己。一個很直觀的例子可以解釋這一點:假設我們現在基于CEF的應用程序編譯并構建了一個App Bundle,這個app內將主進程代碼和子進程代碼寫在了一起,通過運行時邏輯來區分。此時,假設主進程需要macOS的“鑰匙串”權限,讀取用戶的一些配置。由于macOS權限是給到Bundle應用層面的,所以盡管我們只想讓主進程得到“鑰匙串”訪問權限,但因為主進程和子進程都是同一個Bundle,無形中導致了子進程也同樣擁有了這個權限,而像渲染進程這樣的子進程,里面會運行js代碼、wasm等第三方代碼邏輯,一旦出現了BUG,就會存在權限泄漏風險。如果我們把主進程和子進程分離到兩個Bundle,主進程所在Bundle獲取某些系統權限,而渲染進程獲取某些必要權限,就能做到主進程和子進程權限分離的目的,為安全性提供了一定保證。

所以,在了解了macOS下的CEF應用構建思路以后,我們開始搭建對應項目,并在搭建過程中對涉及的配置逐一解釋,希望能夠幫助讀者理清項目脈絡。

搭建

基礎準備

搭建的步驟分為以下幾步:

1)下載cef的二進制分發文件(cef_binary_xxx),將它解壓存放到某個文件夾(可以不用放在項目目錄下);

2)配置一個環境變量CEF_ROOT,需要該環境變量值配置為cef_binary_xxx所在目錄:

? echo $CEF_ROOT
/Users/w4ngzhen/projects/thirds/cef_binary_119.4.7+g55e15c8+chromium-119.0.6045.199_macosarm64
# 配置完成后,請確保環境變量生效

3)創建項目目錄cef_app_macos_project,該目錄將會存放本次macOS下工程的所有配置、源代碼。

4)在項目根目錄下創建cmake目錄,并將步驟1中cef_binary_xxx/cmake/FindCef.cmake文件復制到cmake目錄中:

項目根目錄CMake配置

前期工作準備好以后,我們在項目根目錄下創建CMakeLists.txt文件,并編寫如下內容:

CMAKE_MINIMUM_REQUIRED(VERSION 3.21)

PROJECT(cef_app_macos_project LANGUAGES CXX)

# 基礎配置
SET(CMAKE_BUILD_TYPE DEBUG)
SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)
SET(CMAKE_INCLUDE_CURRENT_DIR ON)

# ===== CEF =====
if (NOT DEFINED ENV{CEF_ROOT})
    message(FATAL_ERROR "環境變量CEF_ROOT未定義!")
endif ()
# 執行下面之前,請確保環境變量CEF_ROOT已經配置為了對應cef_binary_xxx目錄
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(CEF REQUIRED)

# ===== 子模塊引入 =====
# 1. CEF前置準備完成后,此處便可以使用變量 CEF_LIBCEF_DLL_WRAPPER_PATH ,該值會返回libcef_dll_wrapper的目錄地址
add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper)

關于CMake本身的基礎配置定義我們不再贅述,這里主要解釋一下關于CEF引入的部分。首先,我們并沒有把cef_bin_xxx目錄復制到項目根目錄下,而是放在了“外部”,并通過環境變量CEF_ROOT指向了它。在上述CMake關于CEF配置部分,我們對CMAKE_MODULE_PATH路徑值追加了cef_app_macos_project/cmake目錄。

${CMAKE_CURRENT_SOURCE_DIR}就指代了項目根目錄cef_app_macos_project

接下來,在find_package(CEF REQUIRED)的時候,CMake會搜索CMAKE_MODULE_PATH路徑下的名為FindCEF.cmake的CMake配置,于是就能找到我們曾復制的cef_app_macos_project/cmake/FindCEF.cmake文件并進行加載。

如果CMake初始化的時候出現了:

CMake Error at CMakeLists.txt:20 (message):
環境變量CEF_ROOT未定義!

請確保CEF_ROOT環境變量確定配置了。

對于FindCEF.cmake本身的內容,其核心邏輯就是讀取環境變量CEF_ROOT值,然后定位到cef_binary_xxx目錄,并加載cef_binary_xxx/cmake/cef_variables.cmakecef_binary_xxx/cmake/cef_macros.cmake兩個CMake配置文件。

這兩個文件的作用分別是定義一些CEF提供的變量和宏方法,以便在后續的CMake加載邏輯中使用。

find_package以后,我們調用了add_subdirectory指令,該指令第一個參數${CEF_LIBCEF_DLL_WRAPPER_PATH}就使用了來自cef_variables.cmake中定義值,指代了libcef_dll_wrapper代碼工程的目錄:

因此,這里的邏輯就是將cef_binary_xxx/libcef_dll目錄作為了我們的CMake子模塊工程,于是CMake會進一步加載cef_binary_xxx/libcef_dll/CMakeLists.txt文件并進行CMake相關文件的生成。細心的讀者會注意到,這里還存在第二個參數libcef_dll_wrapper

這里需要這個參數值的原因在于,libcef_dll_wrapper所在目錄是一個外部路徑,所以需要提供一個目錄名作為的CMake文件二進制生成的路徑。如果不提供,則會收到錯誤:

那么第二個參數具體影響了什么呢?如果讀者使用CLion+CMake,會看到CLion會在項目根目錄下生成cmake-build-debug目錄,這個就是CMake生成文件目錄,編譯后的結果、CMake的過程文件都會在這個目錄下找到(該目錄其實就是cmake命令行的-B參數指定的路徑,CLion默認指定的項目根目錄下/cmake-build-debug目錄)。在這里,當我們add_subdirectory添加了libcef_dll_wrapper子模塊,經過CMake的初始化以后,會看到cmake-build-debug/libcef_wrapper_dll路徑的產生:

至此,我們添加了對CEF的libcef_dll_wrapper子模塊的引入,為了驗證模塊引入的正確性,我們嘗試在當前cef_app_macos_project這個項目中對引入的子模塊進行編譯。有兩種操作方式,方式1就是進入cmake-build-debug這個目錄下使用命令:cmake --build .;當然,我們還可以使用IDE提供的更加便利的方式2:CLion直接使用GUI即可。

如果一切沒有問題的情況下,我們可以在output目錄中找到libcef_dll_wrapper的生成出來的庫文件:

在繼續后面的講解前,我們先放慢腳步,對項目環境做一個總結。我們首先準備了兩個目錄,一個是我們自己的cef_app_macos_project目錄,我們會在這個項目中“引入”CEF相關庫,后續還會在里面編寫我們自己的應用程序;另一個則是在外部的cef_binary_xxx目錄,我們不會改動其中的內容。

對于我們自己的cef_app_macos_project,在根目錄下,我們編寫了一個CMakeLists.txt,它是我們項目頂層的CMake配置,該文件核心配置邏輯分以下幾步:

  1. 一些基本的項目、編譯配置;
  2. 加載CEF的CMake配置;
  3. 引入外部的cef_binary_xxx中的libcef_dll_wrapper模塊作為CMake子模塊。

但請注意,目前我們僅僅是通過CMake提供的add_subdirectory命令,將libcef_dll_wrapper作為子模塊引入,但目前還沒有任何的應用在依賴它,接下來我們將進一步,開始配置主進程應用,并依賴該libcef_dll_wrapper

主進程應用項目配置

在項目根目錄下,我們創建cef_app目錄,該目錄目前先存放CEF的macOS應用的主進程應用項目代碼。我們在cef_app目錄下創建process_main.mm,且暫時先編寫一段簡單的代碼:

#include <iostream>

int main(int argc, char *argv[]) {
  std::cout << "hello, this is main process." << std::endl;
  return 0;
}

PS:.mm為后綴文件是指Objective-C與C/C++混寫的源代碼文件后綴,所以這里我們是可以完全寫C++代碼的。

然后,在cef_app目錄中創建CMakeLists.txt文件,并編寫如下的配置:

# ===== 主進程target配置 =====
# 主進程target名稱
set(CEF_APP_TARGET cef_app)
# 最終 App Bundle生成的路徑
set(CEF_APP_BUNDLE "${CMAKE_CURRENT_BINARY_DIR}/${CEF_APP_TARGET}.app")
# 添加項目所有的源文件:
add_executable(
        ${CEF_APP_TARGET}
        MACOSX_BUNDLE # macOS 使用 "MACOSX_BUNDLE" 標識,最后編譯產物是一個mac下的App Bundle
        process_main.mm
)
# 使用CEF提供的預定義好的工具宏,該宏會幫助配置target一些編譯上的配置
# 如果出現不符合預期的編譯結果、運行錯誤,可以檢查該宏的內部實現
SET_EXECUTABLE_TARGET_PROPERTIES(${CEF_APP_TARGET})
# 添加對 libcef_dll_wrapper 庫的依賴
# 基于該配置,可以保證每次編譯當前 cef_app target時候,確保 libcef_dll_wrapper 靜態庫編譯完成
add_dependencies(${CEF_APP_TARGET} libcef_dll_wrapper)
# 鏈接庫配置
target_link_libraries(
        ${CEF_APP_TARGET}
        PRIVATE
        # libcef_dll_wrapper庫鏈接
        libcef_dll_wrapper
        # 該變量來自cef_variables.cmake中定義的配置
        # 主要是針對不同的平臺,鏈接對應平臺的一些標準庫(Windows、Linux)或者framework(macOS)
        ${CEF_STANDARD_LIBS}
)
# 主進程編譯后,會在輸出目錄下生成一個名為 cef_app.app 的macOS App Bundle。
# 該app內部 Contents/MacOS/cef_app 僅僅是包含了 add_executable 中的源碼二進制,以及libcef_dll_wrapper靜態庫
# 在macOS下,我們還需要將"cef_binary_xxx/Debug或Release目錄/Chromium Embedded Framework.framework"復制到
# cef_app.app/Contents/Frameworks目錄下
# 為了避免手動復制的麻煩,我們使用如下的指令完成復制工作
add_custom_command(
        # 對 CEF_APP_TARGET 進行操作
        TARGET ${CEF_APP_TARGET}
        # 在構建完成后(POST_BUILD)
        POST_BUILD
        # COMMAND ${CMAKE_COMMAND}:就是命令行執行 "cmake"
        # -E:指可以執行一些cmake內置的工具命令
        # copy_directory:進行目錄復制操作
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        # 復制源目錄、文件,
        # CEF_BINARY_DIR變量來源于cef_variables.cmake
        # 等價于"cef_binary_xxx目錄/Debug或Release目錄/"
        "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework"
        # 將上述 framework 復制到 當前生成的 cef_app.app/Contents/Frameworks/對應framework名稱
        "${CEF_APP_BUNDLE}/Contents/Frameworks/Chromium Embedded Framework.framework"
        # 不進行文本的解析,使用源文字,考慮會有表達式情況
        VERBATIM
)
# 簡單配置Info.plist的一些值
set_target_properties(
        ${CEF_APP_TARGET}
        PROPERTIES
        MACOSX_BUNDLE_BUNDLE_NAME ${CEF_APP_TARGET}
        MACOSX_BUNDLE_GUI_IDENTIFIER ${CEF_APP_TARGET}
)

我們接下來對上述的配置逐一解釋:

# 主進程target名稱
set(CEF_APP_TARGET cef_app)
# 最終 App Bundle生成的路徑
set(CEF_APP_BUNDLE "${CMAKE_CURRENT_BINARY_DIR}/${CEF_APP_TARGET}.app")

上述配置了我們接下來將會定義的target的名稱,以及后續生成的macOS特有的App Bundle的應用文件的路徑,后續會使用到該值。

add_executable(
        ${CEF_APP_TARGET}
        MACOSX_BUNDLE # macOS 使用 "MACOSX_BUNDLE" 標識,最后編譯產物是一個mac下的App Bundle
        process_main.mm
)

add_executable部分定義最終生成的target,除了包含編寫的源碼路徑(process_main.mm),這里還有一個很重要的參數MACOS_BUNDLE,配置該參數后,在macOS下,我們最終生成的可執行程序就不再是一個簡單的命令行程序,而是macOS下的App Bundle。下圖是沒有配置該值前后的對比:

可以看到,沒有配置MACOSX_BUNDLE時,最終項目會在輸出目錄(${CMAKE_CURRENT_BINARY_DIR})下生成名為cef_app的可執行命令行程序;而配置以后,項目會在輸出目錄下生成target名.app,這里就是cef_app.app

# 使用CEF提供的預定義好的工具宏,該宏會幫助配置target一些編譯上的配置
# 如果出現不符合預期的編譯結果、運行錯誤,可以檢查該宏的內部實現
SET_EXECUTABLE_TARGET_PROPERTIES(${CEF_APP_TARGET})

SET_EXECUTABLE_TARGET_PROPERTIES不是CMake提供的指令,而是由CEF提供的,存放于cef_macros.cmake中的宏。該宏主要的功能是對目標target配置一些可執行程序所需要的編譯參數等。如果讀者在實踐過程中,遇到了鏈接問題,可以優先檢查這個宏中的實現。由于篇幅原因,這塊后續單獨出一篇文章水一水,>_<。

# 添加對 libcef_dll_wrapper 庫的依賴
# 基于該配置,可以保證每次編譯當前 cef_app target時候,確保 libcef_dll_wrapper 靜態庫編譯完成
add_dependencies(${CEF_APP_TARGET} libcef_dll_wrapper)

add_dependencies的作用則是為當前target指定依賴。因為我們的項目本身會通過靜態鏈接庫的形式鏈接libcef_dll_wrapper,通過這add_dependencies能夠保證最終構建過程中,確保優先將libcef_dll_wrapper編譯出來,供后續鏈接過程使用。當然,你也可以不閑麻煩的手動先編譯libcef_dll_wrapper,再編譯這個cef_app

# 鏈接庫配置
target_link_libraries(
        ${CEF_APP_TARGET}
        PRIVATE
        # libcef_dll_wrapper庫鏈接
        libcef_dll_wrapper
        # 該變量來自cef_variables.cmake中定義的配置
        # 主要是針對不同的平臺,鏈接對應平臺的一些標準庫(Windows、Linux)或者framework(macOS)
        ${CEF_STANDARD_LIBS}
)

target_link_libraries處理則是配置當前target的鏈接庫,包括不限于libcef_dll_wrapper的靜態鏈接、各種平臺特定的鏈接庫等。最后一個參數變量CEF_STANDARD_LIBS,由CEF在cef_variables.cmake中定義,包含平臺特定的鏈接庫。

例如,在Windows下我們可能需要gdi32.lib,在Linux構建窗體可能需要X11庫,以及在macOS下需要CocoaAppKit等框架庫。讀者可以翻閱cef_variables.cmake中關于這個變量的配置了解具體的內容。

# 主進程編譯后,會在輸出目錄下生成一個名為 cef_app.app 的macOS App Bundle。
# 該app內部 Contents/MacOS/cef_app 僅僅是包含了 add_executable 中的源碼二進制,以及libcef_dll_wrapper靜態庫
# 在macOS下,我們還需要將"cef_binary_xxx/Debug或Release目錄/Chromium Embedded Framework.framework"復制到
# cef_app.app/Contents/Frameworks目錄下
# 為了避免手動復制的麻煩,我們使用如下的指令完成復制工作
add_custom_command(
        # 對 CEF_APP_TARGET 進行操作
        TARGET ${CEF_APP_TARGET}
        # 在構建完成后(POST_BUILD)
        POST_BUILD
        # COMMAND ${CMAKE_COMMAND}:就是命令行執行 "cmake"
        # -E:指可以執行一些cmake內置的工具命令
        # copy_directory:進行目錄復制操作
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        # 復制源目錄、文件,
        # CEF_BINARY_DIR變量來源于cef_variables.cmake
        # 等價于"cef_binary_xxx目錄/Debug或Release目錄/"
        "${CEF_BINARY_DIR}/Chromium Embedded Framework.framework"
        # 將上述 framework 復制到 當前生成的 cef_app.app/Contents/Frameworks/對應framework名稱
        "${CEF_APP_BUNDLE}/Contents/Frameworks/Chromium Embedded Framework.framework"
        # 不進行文本的解析,使用源文字,考慮會有表達式情況
        VERBATIM
)

倒數第二個指令add_custom_command,在介紹它的作用前,先簡單說明在macOS下基于CEF的App Bundle的一應用結構。基于前面的配置,主進程編譯后,會在輸出目錄下生成一個名為cef_app.app的macOS App Bundle,該Bundle內部/Contents/MacOS/cef_app可執行程序,就是鏈接了源碼二進制、libcef_dll_wrapper靜態庫后的可執行二進制程序。然而,CEF核心庫Chromium Embedded Framework.framework我們并沒有靜態鏈接到執行程序內,而是在實際運行過程中,動態加載這個framework。為了達到該目的,我們思路是通過腳本將cef_binary_xxx中提供的CEF的核心庫framework拷貝到App Bundle中指定路徑下。

所以,在了解了App Bundle運行邏輯以后,關于add_custom_command作用就顯而易見了,其邏輯就是配置在構建完成以后,通過CMake的工具指令(-E copy_directories)將Chromium Embedded Framework.framework整個內容復制到生成的Bundle的/Contents/Frameworks目錄下:

在上面的講解中我們大致理解了macOS的App Bundle的應用程序組織結構,細心的讀者會發現,在構建后的Bundle中的根目錄下有一個文件Info.plist

該文件的核心作用是定義macOS下App Bundle的基礎應用程序配置,包括不限于該應用的名稱、應用ID、圖標資源等。因為我們將主進程target定義為了MACOS_BUNDLE,CMake會在構建的時候,默認為我們的Bundle生成了一份plist并寫入到Bundle中。同時我們會發現,Info.plist配置中關于CFBundleNameCFBundleIdentifier等值就是我們現在的target的名稱:

原因在于配置文件中緊接著add_custom_command后面的set_target_properties

# 簡單配置Info.plist的一些值
set_target_properties(
        ${CEF_APP_TARGET}
        PROPERTIES
        MACOSX_BUNDLE_BUNDLE_NAME ${CEF_APP_TARGET}
        MACOSX_BUNDLE_GUI_IDENTIFIER ${CEF_APP_TARGET}
)

使用set_target_properties指令指定了MACOSX_BUNDLE_BUNDLE_NAMEMACOSX_BUNDLE_GUI_IDENTIFIER的值。關于這段配置的說明,官方文檔提到:https://cmake.org/cmake/help/latest/prop_tgt/MACOSX_BUNDLE_INFO_PLIST.html,我們可以直接通過相關屬性值來替換CMake內置的plist模板文件內容。

注意,CMake支持的變量只有上述官方文檔提供的Key,如果有其他的Key需要處理,只能通過自己提供模板方法進行處理,這點會在后面構建子進程Bundle再次說明。

至此,我們基本完成了在macOS對主進程的CMake配置。此時,請務必注意,記得在項目根目錄的CMakeLists.txt追加如下將cef_app目錄作為子模塊引入的配置:

# 1. CEF前置準備完成后,此處便可以使用變量 CEF_LIBCEF_DLL_WRAPPER_PATH ,該值會返回libcef_dll_wrapper的目錄地址
add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper)
+ # 2. 將cef_app作為子模塊引入
+ add_subdirectory(./cef_app)

當然,我們主進程應用的源代碼還是只是簡單的在控制臺輸出一段話,我們不著急編寫主進程代碼,接下來還需要配置對應的子進程項目。

子進程應用項目配置

我們在一開始已經提到過,在macOS建議將主進程和子進程分別構建為兩個不同的App Bundle,這里我們有兩種做法:

  • 方式1:通過CMake的定義target,在前面主進程CMakeLists.txt中直接定義子進程的target,讓構建系統同時生成另外的子進程應用。

  • 方式2:直接重新創建一個目錄來定義子進程CMake模塊并存放子進程模塊代碼。

這里筆者使用第一種方式來進行配置,或許配置上略顯復雜,但只要讀者一旦理解,筆者相信今后對于其他CMake項目配置應該也能很快上手。

我們先在cef_app目錄中創建一個名為process_helper.mm的文件,暫時作為子進程的入口源碼:

#include <iostream>

int main(int argc, char *argv[]) {
  std::cout << "hello, this is sub helper process." << std::endl;
  return 0;
}

同時,在該子模塊目錄下創建一個templates目錄,并在其中創建helper-Info.plist文件,具體的意義和其內容我們后面介紹,這里讀者可以將它理解為一份模板文件。

此時,我們的項目結構如下:

為了閱讀的方便,我們都將子進程叫做helper

接下來,我們在cef_app/CMakeLists.txt內容的基礎上,添加如下的針對helper子進程應用的配置:

# ===== 主進程target配置 =====
# ... ...
# ===== 子進程 helper target配置 =====
# 定義helper子進程target名
set(CEF_APP_HELPER_TARGET "cef_app_helper")
# 定義helper子進程構建后的app的名稱
set(CEF_APP_HELPER_OUTPUT_NAME "cef_app Helper")
# 注意,上述的名稱都不是最終名稱,它們更準確的意義是作為下面循環定義target的基礎名稱
# 后續循環的時候,會基于上述名稱進行拼接

# 創建多個不同類型helper的target
# CEF_HELPER_APP_SUFFIXES來自cef_variables.cmake,是一個“字符串數組”,值有:
# "::"、" (Alerts):_alerts:.alerts"、" (GPU):_gpu:.gpu"、
# " (Plugin):_plugin:.plugin"、" (Renderer):_renderer:.renderer"
# 這里通過foreach,實現對字符串數組的遍歷,每一次循環會得到一個字符串,存放在“_suffix_list”
foreach (_suffix_list ${CEF_HELPER_APP_SUFFIXES})
  # 將字符串轉為";"分割,這樣可以使用CMake支持的list(GET)指令來讀取每一節字符串
  # 以 " (Renderer):_renderer:.renderer" 為例
  string(REPLACE ":" ";" _suffix_list ${_suffix_list}) # " (Renderer);_renderer;.renderer"
  list(GET _suffix_list 0 _name_suffix) # " (Renderer)"
  list(GET _suffix_list 1 _target_suffix) # "_renderer"
  list(GET _suffix_list 2 _plist_suffix) # ".renderer"
  # 當然,需要注意 CEF_HELPER_APP_SUFFIXES 中有一個"::"的字符串,
  # 會使得 _name_suffix = ""、_target_suffix = ""、_plist_suffix = ""

  # 定義一個Helper target以及BUNDLE名稱
  # 以 " (Renderer):_renderer:.renderer" 為例
  # _helper_target = "cef_app_helper" + "_renderer" -> "cef_app_helper_renderer"
  # _helper_output_name = "cef_app Helper" + " (Renderer)" -> "cef_app Helper (Renderer)"
  set(_helper_target "${CEF_APP_HELPER_TARGET}${_target_suffix}")
  set(_helper_output_name "${CEF_APP_HELPER_OUTPUT_NAME}${_name_suffix}")

  # 讀取templates/helper-Info.plist模板文件內容到_plist_contents
  # 然后使用上面得到的 _helper_output_name、_plist_suffix等變量進行文本內容的替換操作
  # 以便得到當前正在處理的helper對應的一份Info.plist
  file(READ "${CMAKE_CURRENT_SOURCE_DIR}/templates/helper-Info.plist" _plist_contents)
  string(REPLACE "\${HELPER_EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
  string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
  string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents})
  # helper的Info.plist文件路徑,例如:"${CMAKE_CURRENT_BINARY_DIR}/helper-Info[_renderer].plist"
  set(_helper_info_plist_file "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist")
  # 通過CMake提供file(WRITE)命令,將前面定義的內容寫入到對應.plist文件中
  file(WRITE ${_helper_info_plist_file} ${_plist_contents})

  # 創建當前helper的executable target,當然,也是一個App Bundle
  add_executable(${_helper_target}
      MACOSX_BUNDLE
      process_helper.mm
  )
  # 與主進程應用一樣,
  # 通過cef提供的SET_EXECUTABLE_TARGET_PROPERTIES宏,來設置編譯參數、頭文件路徑等
  SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target})
  # 編譯當前Helper target前,先編譯 libcef_dll_wrapper target
  add_dependencies(${_helper_target} libcef_dll_wrapper)
  # 當前Helper target的庫鏈接
  target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS})
  # 定義當前Helper target的一些屬性
  set_target_properties(${_helper_target} PROPERTIES
      # 這里使用“MACOSX_BUNDLE_INFO_PLIST”,
      # 來定義構建過程Bundle使用的Info.plist來源于前面我們通過模板文件生成的.plist
      MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist_file}
      # 定義最終生成的App Bundle的名稱
      OUTPUT_NAME ${_helper_output_name}
  )

  # 構建主進程應用前,會先構建當前Helper target
  add_dependencies(${CEF_APP_TARGET} "${_helper_target}")

  # 將構建的Helper App Bundle拷貝到主進程cef_app的Bundle中
  add_custom_command(
      TARGET ${CEF_APP_TARGET}
      POST_BUILD
      COMMAND ${CMAKE_COMMAND} -E copy_directory
      "${CMAKE_CURRENT_BINARY_DIR}/${_helper_output_name}.app"
      "${CEF_APP_BUNDLE}/Contents/Frameworks/${_helper_output_name}.app"
      VERBATIM
  )
endforeach ()

讓我們從頭到尾一一道來。

# 定義helper子進程target名
set(CEF_APP_HELPER_TARGET "cef_app_helper")
# 定義helper子進程構建后的app的名稱
set(CEF_APP_HELPER_OUTPUT_NAME "cef_app Helper")
# 注意,上述的名稱都不是最終名稱,它們更準確的意義是作為下面循環定義target的基礎名稱
# 后續循環的時候,會基于上述名稱進行拼接

首先,我們會定義helper子進程的target名稱和輸出應用名稱。但需要注意的是,這里的名稱不完全是最終輸出的應用程序的名稱。因為在后續的配置中,我們會使用CMake支持的循環命令來支持生成多個target。

# 創建多個不同類型helper的target
# CEF_HELPER_APP_SUFFIXES來自cef_variables.cmake,是一個“字符串數組”,值有:
# "::"、" (Alerts):_alerts:.alerts"、" (GPU):_gpu:.gpu"、
# " (Plugin):_plugin:.plugin"、" (Renderer):_renderer:.renderer"
# 這里通過foreach,實現對字符串數組的遍歷,每一次循環會得到一個字符串,存放在“_suffix_list”
foreach (_suffix_list ${CEF_HELPER_APP_SUFFIXES})
 ... ...
endforeach ()

接著,我們使用CMake的foreach指令,來遍歷變量CEF_HELPER_APP_SUFFIXES這個變量值。這個變量來自于cef提供的變量(cef_variables.cmake):

  # CEF Helper app suffixes.
  # Format is "<name suffix>:<target suffix>:<plist suffix>".
  set(CEF_HELPER_APP_SUFFIXES
    "::"
    " (Alerts):_alerts:.alerts"
    " (GPU):_gpu:.gpu"
    " (Plugin):_plugin:.plugin"
    " (Renderer):_renderer:.renderer"
    )

在這里通過CMake的遍歷能力,我們每一次迭代都能讀取到對應一條字符串并存放到_suffix_list變量中。

接下來介紹在foreach包裹的內部配置:

    # 將字符串轉為";"分割,這樣可以使用CMake支持的list(GET)指令來讀取每一節字符串
    # 以 " (Renderer):_renderer:.renderer" 為例
    string(REPLACE ":" ";" _suffix_list ${_suffix_list}) # " (Renderer);_renderer;.renderer"
    list(GET _suffix_list 0 _name_suffix) # " (Renderer)"
    list(GET _suffix_list 1 _target_suffix) # "_renderer"
    list(GET _suffix_list 2 _plist_suffix) # ".renderer"
    # 當然,需要注意 CEF_HELPER_APP_SUFFIXES 中有一個"::"的字符串,
    # 會使得 _name_suffix = ""、_target_suffix = ""、_plist_suffix = ""

我們將_suffix_list變量中所有的:字符替換為;,然后就可以使用CMake支持的list(GET)指令來讀取每一節字符串。

" (Renderer):_renderer:.renderer"為例,在替換后,通過list(GET)可以分別得到:

  • _name_suffix = " (Renderer)"
  • _target_suffix = "_renderer"
  • _plist_suffix = ".renderer"

這三個suffix將在后續的流程拼接出相關名稱變量。但需要注意的是,在CEF_HELPER_APP_SUFFIXES中存在一個特殊的字符串:"::"。這個字符串會導致最后提取出來的前面三個suffix都是""(空字符串),這并不是BUG,后續會用到。

    # 定義一個Helper target以及BUNDLE名稱
    # 以 " (Renderer):_renderer:.renderer" 為例
    # _helper_target = "cef_app_helper" + "_renderer" -> "cef_app_helper_renderer"
    # _helper_output_name = "cef_app Helper" + " (Renderer)" -> "cef_app Helper (Renderer)"
    set(_helper_target "${CEF_APP_HELPER_TARGET}${_target_suffix}")
    set(_helper_output_name "${CEF_APP_HELPER_OUTPUT_NAME}${_name_suffix}")

接下來,我們開始消費suffix。首先,我們通過拼接操作得到_helper_target_helper_output_name。這兩個變量分別代表了當前正在構建的helper的真正target名和對應后續構建的應用名稱。還是以 " (Renderer):_renderer:.renderer"為例。我們能夠得到:

  • _helper_target = "cef_app_helper" + "_renderer" 得到 "cef_app_helper_renderer"
  • _helper_output_name = "cef_app Helper" + " (Renderer)" 得到 "cef_app Helper (Renderer)"
    # 讀取templates/helper-Info.plist模板文件內容到_plist_contents
    # 然后使用上面得到的 _helper_output_name、_plist_suffix等變量進行文本內容的替換操作
    # 以便得到當前正在處理的helper對應的一份Info.plist
    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/templates/helper-Info.plist" _plist_contents)
    string(REPLACE "\${HELPER_EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
    string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
    string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents})
    # helper的Info.plist文件路徑,例如:"${CMAKE_CURRENT_BINARY_DIR}/helper-Info[_renderer].plist"
    set(_helper_info_plist_file "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist")
    # 通過CMake提供file(WRITE)命令,將前面定義的內容寫入到對應.plist文件中
    file(WRITE ${_helper_info_plist_file} ${_plist_contents})

接下來,我們使用CMake提供的能力,讀取了前面提到的存放在cef_app/templates目錄下的helper-Info.plist文件。這是一個模板文件,打開后讀者能從中看到一些${XXX}的占位字符串,我們會在這一步進行對應文本的替換。這里我們用到了CMake的幾個知識點:

  1. file(READ)讀取某個文件并存放到文本變量中;
  2. string(REPLAECE)替換文本變量中某些字符串并寫回到變量中;
  3. file(WRITE)將文本數據寫入到某個文件中。

這一步我們還得到了_helper_info_plist_file變量,它指向了我們寫入的plist文件,以便在后續配置中進行使用。

    # 創建當前helper的executable target,當然,也是一個App Bundle
    add_executable(${_helper_target}
            MACOSX_BUNDLE
            process_helper.mm
    )
    # 與主進程應用一樣,
    # 通過cef提供的SET_EXECUTABLE_TARGET_PROPERTIES宏,來設置編譯參數、頭文件路徑等
    SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target})
    # 編譯當前Helper target前,先編譯 libcef_dll_wrapper target
    add_dependencies(${_helper_target} libcef_dll_wrapper)
    # 當前Helper target的庫鏈接
    target_link_libraries(${_helper_target} libcef_dll_wrapper ${CEF_STANDARD_LIBS})
    # 定義當前Helper target的一些屬性
    set_target_properties(${_helper_target} PROPERTIES
            # 這里使用“MACOSX_BUNDLE_INFO_PLIST”,
            # 來定義構建過程Bundle使用的Info.plist來源于前面我們通過模板文件生成的.plist
            MACOSX_BUNDLE_INFO_PLIST ${_helper_info_plist_file}
            # 定義最終生成的App Bundle的名稱
            OUTPUT_NAME ${_helper_output_name}
    )

和前面主進程應用target類似。我們將helper的構建結果同樣定義為App Bundle;使用SET_EXECUTABLE_TARGET_PROPERTIES來進行編譯參數等設置;使用add_dependencies告訴CMake編譯構建子進程target的時候,保證libcef_dll_wrapper優先于helper構建完成;使用target_link_libraries鏈接子進程Helper。但,最后一個set_target_properties和之前主進程target設置有所不同。在之前的主進程應用配置時,我們直接使用了諸如MACOSX_BUNDLE_BUNDLE_NAMEMACOSX_BUNDLE_GUI_IDENTIFIER等參數來讓CMake使用內置的plist模板文件生成主進程應用App Bundle中的plist文件。但因為CMake內置的模板plist只能設置部分字段值,而在Helper配置的時候,我們需要更改更多的占位字段,所以我們自己提供了helper Bundle的模板plist,并通過內容讀取、字符串替換的方式生成了對應Helper的Bundle的plist文件內容。要讓CMake不再使用內置的模板plist,而是使用我們生成的plist文件,我們使用參數MACOSX_BUNDLE_INFO_PLIST指定前面生成好的plist文件路徑。最后,我們還定義了OUTPUT_NAME這個參數,這個參數主要的作用是可以自定義生成的應用程序的名稱,如果沒有這個參數,我們最終在構建結果目錄中生成應用名稱就是target。

    # 構建主進程應用前,會先構建當前Helper target
    add_dependencies(${CEF_APP_TARGET} "${_helper_target}")

告訴CMake,構建主進程target應用的時候,會先構建當前Helper target。

    # 將構建的Helper App Bundle拷貝到主進程cef_app的Bundle中
    add_custom_command(
            TARGET ${CEF_APP_TARGET}
            POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${CMAKE_CURRENT_BINARY_DIR}/${_helper_output_name}.app"
            "${CEF_APP_BUNDLE}/Contents/Frameworks/${_helper_output_name}.app"
            VERBATIM
    )

在循環的最后,我們再次使用add_custom_command通過CMake提供的文件復制能力,讓主進程應用構建完成以后,將當前子進程helper應用app復制到主進程應用.app/Contents/Frameworks目錄下。至于為什么要這么做,我們將會在下一篇文章中介紹應用程序運行時架構來說明。

基于現在完成的配置,我們可以通過對cef_app進行構建,檢查最終構建的產物來驗證項目的正確性。筆者使用CLion的GUI生成cef_app,最終會在輸出目錄中找到cef_app.app,同時會看到會生成多個helper的App Bundle,并已經成功復制到了對應目錄中:

寫在最后

在本文,我們基本上完成了在macOS下基于CEF的多進程應用架構的項目CMake配置,并結合實際的配置,逐一說明了CMake的相關用法和配置細節。在下一篇文章中,我們會基于此文搭建的項目,逐步介紹并編寫macOS下基于CEF應用程序的代碼,其中會涉及到macOS下Cocoa框架知識簡介。

本文倉庫鏈接:w4ngzhen/cef_app_macos_project (github.com)

總結

以上是生活随笔為你收集整理的使用CEF(七)详解macOS下基于CEF的多进程应用程序CMake项目搭建的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

奇米影视777影音先锋 | 亚洲 成人 一区 | 亚洲精品在线观看中文字幕 | 狠狠综合网 | 免费一级片久久 | 狠狠操狠狠 | 在线蜜桃视频 | 日韩国产欧美在线播放 | 27xxoo无遮挡动态视频 | 国产打女人屁股调教97 | 中文字幕视频一区二区 | 久久色视频| 91精品久久久久久久久久入口 | 黄色a一级视频 | 国产精品第一视频 | 免费又黄又爽视频 | 久草在线视频在线观看 | 亚洲欧美精品一区二区 | 久久久99国产精品免费 | 国产亚洲激情视频在线 | 91插插影库| 蜜臀aⅴ精品一区二区三区 久久视屏网 | 欧美精品久久久久久久免费 | 激情综合色播五月 | 在线视频中文字幕一区 | 免费a v观看 | 国产精品乱码在线 | 色综合天天射 | 日本动漫做毛片一区二区 | 国产精品 视频 | 五月天六月色 | av视屏在线播放 | 九九热免费在线观看 | 国产精品国产自产拍高清av | 日韩精品欧美视频 | 91av蜜桃 | av在线免费在线观看 | 亚洲欧美精品在线 | 久草综合在线 | 黄色软件视频大全免费下载 | 久草国产在线 | 国产亚洲精品成人av久久ww | 国产九九九视频 | 在线v片免费观看视频 | 国产高清在线 | 中文字幕免费高清在线观看 | 国产精品亚洲综合久久 | 成人a视频 | 操操操影院 | 99r国产精品 | 亚洲视频电影在线 | 久久经典国产 | 久久成人国产精品 | 欧美伦理一区二区三区 | 中文字幕色在线视频 | 视频直播国产精品 | 天天操天天射天天舔 | 久久亚洲电影 | 九九色综合| 欧美另类交人妖 | 久久免费在线观看 | 97电影在线观看 | 激情久久网| av 一区二区三区 | 日韩av手机在线观看 | 99精品视频免费 | 免费看一及片 | www久久国产 | 亚洲九九九| 狠狠干激情 | 中文字幕日本特黄aa毛片 | 国产精品久久久久aaaa | 国产精品99久久久久久久久久久久 | 久久精品亚洲一区二区三区观看模式 | 中文字幕在线视频免费播放 | 国产99久久久欧美黑人 | 成人在线免费看视频 | 国产一区二区三区在线免费观看 | 99久久婷婷国产精品综合 | 久久久国产精品久久久 | 国产一区二区在线精品 | 久久久久久久毛片 | 99久久久国产精品免费99 | 国产三级精品三级在线观看 | 精品一区二区三区久久久 | 久久久www成人免费毛片麻豆 | 欧美在线不卡一区 | 日韩一级成人av | 免费看成年人 | 99久久婷婷国产综合亚洲 | 天天射天天射天天 | 亚洲 av网站 | 欧美一区二区三区在线 | 亚洲蜜桃av| 大片网站久久 | 在线视频观看亚洲 | 久久视频二区 | 波多野结衣电影一区二区 | 在线观看中文字幕一区 | 色婷婷欧美 | 成人h视频 | 91丝袜美腿| 久久久国产精品免费 | 国产精品成人国产乱 | 97在线看 | 色橹橹欧美在线观看视频高清 | 欧美9999 | 日韩免费成人av | 在线成人一区 | av不卡免费看 | 天天在线视频色 | 国产精品va在线观看入 | 成年人av在线播放 | 中文字幕 成人 | 看av免费 | 精品女同一区二区三区在线观看 | 98涩涩国产露脸精品国产网 | 久久不射电影院 | 久久在线精品 | 天天操天天干天天摸 | 国内视频在线观看 | 91看片一区二区三区 | 福利视频午夜 | 狠狠综合久久av | 久久激情五月丁香伊人 | 中文字幕日本特黄aa毛片 | 99视频| 91传媒在线观看 | 欧美一级在线观看视频 | 福利网址在线观看 | 国产精品久久久久影院日本 | 麻豆免费在线播放 | 在线观看理论 | 99se视频在线观看 | 91人人在线 | 天天射天天干天天插 | 成人毛片一区二区三区 | 九九热在线观看视频 | 国产一区在线视频观看 | 天天做天天爱天天综合网 | 欧美二区在线播放 | 永久免费观看视频 | 国产精品国产三级在线专区 | 最近2019年日本中文免费字幕 | 国产精品欧美一区二区三区不卡 | 丁香六月久久综合狠狠色 | 亚洲国内精品在线 | 久久久久成人精品免费播放动漫 | 国产在线综合视频 | 色大片免费看 | 国产精品少妇 | 婷婷伊人综合亚洲综合网 | 亚洲高清视频在线观看 | 精品亚洲欧美无人区乱码 | 可以免费看av | 激情网在线视频 | 天海翼一区二区三区免费 | 天天干夜夜想 | 色偷偷人人澡久久超碰69 | 亚洲精品玖玖玖av在线看 | 久久任你操 | 亚洲少妇久久 | 国产精品视频内 | 国产成人在线精品 | 色婷婷狠 | 亚洲精品乱码白浆高清久久久久久 | 欧美性超爽 | 99热国产在线 | 国产精品av久久久久久无 | 精品国产诱惑 | 国产一区在线视频 | 天天艹天天 | 你操综合 | 香蕉在线播放 | 中文字幕一区二区三区四区在线视频 | 日韩三级视频在线看 | 91九色视频在线播放 | 日本护士三级少妇三级999 | 黄色网中文字幕 | 中文在线字幕免 | 99精品视频在线免费观看 | 国产无限资源在线观看 | 亚洲精品在线网站 | 国产视频在线观看免费 | 国产亚洲精品久久久久久无几年桃 | 97超碰人人澡 | 欧美日韩aaaa| 毛片网站在线 | 欧美粗又大 | 又爽又黄又刺激的视频 | 国产成人三级在线观看 | 成人av电影免费 | 中文字幕第一页在线 | 黄污视频网站大全 | 日韩精品免费 | 日韩中文在线字幕 | 又黄又色又爽 | 亚洲视频www| 亚洲国产中文字幕在线观看 | 国产精品久久久久久久久久妇女 | 一区二区久久 | 99热.com | 精品亚洲视频在线 | 精品国产乱码久久 | a天堂免费 | 国产韩国精品一区二区三区 | 亚洲国产999 | 九九热在线视频 | 免费精品国产va自在自线 | 国产96在线视频 | 欧美一区二区伦理片 | 亚洲欧美日韩中文在线 | 久久精品久久综合 | 色婷婷88av视频一二三区 | 超碰午夜 | 免费视频 三区 | 97精产国品一二三产区在线 | 视频在线一区二区三区 | 在线视频久久 | 久久亚洲综合国产精品99麻豆的功能介绍 | 天天插天天干天天操 | 日韩婷婷 | 国产91全国探花系列在线播放 | 欧美中文字幕久久 | 日韩毛片在线一区二区毛片 | 久久午夜国产精品 | aaa黄色毛片 | 91在线影院| 色网站免费在线观看 | 91视频免费| 精品国产一区二区三区噜噜噜 | 精品xxx | 九色91福利 | 97国产精品 | 911国产 | 91在线操| 亚洲a免费| 久久免费资源 | 成人免费在线看片 | 亚洲精品久久久久58 | 久草视频99| 在线 视频 亚洲 | 国产精品久久久久久久久免费 | 欧美日韩高清国产 | 99久久久免费视频 | 91欧美在线 | 999亚洲国产996395 | 国产精品一区欧美 | 伊人开心激情 | 日韩成人精品在线观看 | 日韩在线观看电影 | 中文字幕视频一区 | 探花视频免费观看 | 亚洲激情在线 | 国产一级黄色免费看 | 国产精品一区二区三区免费看 | 欧美一级久久 | 天天综合色网 | 51久久夜色精品国产麻豆 | 久久久久色 | 国产玖玖视频 | 夜夜操网 | 欧美日韩在线视频一区二区 | 久久99热精品这里久久精品 | 国产一级视屏 | 国产精品久久久影视 | 极品美女被弄高潮视频网站 | 国产一区二区三区四区在线 | 三级av在线 | 免费成人黄色av | 久久香蕉一区 | 狠狠久久婷婷 | 欧美性生活久久 | 日韩一区二区三区在线看 | 日日操夜 | 六月丁香色婷婷 | 天天综合网在线观看 | 四虎精品成人免费网站 | 成人99免费视频 | 色网站在线免费 | 成人国产精品av | 97在线看 | 高清不卡一区二区三区 | 国产私拍在线 | 亚洲国产一二三 | 中文字幕电影高清在线观看 | 久久艹久久| 亚洲精品综合在线 | 在线黄色国产 | 777视频在线观看 | 国产日韩一区在线 | 亚洲综合少妇 | 国产在线更新 | 亚洲成人精品久久久 | 久久成视频 | 久久99热国产 | 97夜夜澡人人双人人人喊 | 国产91精品看黄网站在线观看动漫 | 国产麻豆电影 | 2022久久国产露脸精品国产 | 欧美一级免费黄色片 | 欧美一区二区三区在线观看 | 国产精品99蜜臀久久不卡二区 | 久久午夜国产 | 女人18毛片a级毛片一区二区 | 国产精品黄色 | 欧美激情在线网站 | 久久久午夜精品福利内容 | 天天摸日日摸人人看 | 国产啊v在线观看 | 日韩精品一区二区三区水蜜桃 | www.香蕉视频 | 开心激情婷婷 | 国产精品a久久久久 | 久久精久久精 | 国产一级二级在线观看 | 久久久久观看 | 久久成人精品电影 | 手机看片国产 | 国产色女人 | 黄色免费网站下载 | 亚洲伦理中文字幕 | 国产亚洲精品久久久久秋 | 国产成人高清 | 不卡电影免费在线播放一区 | 国产不卡免费av | 69国产盗摄一区二区三区五区 | 久久99精品久久久久久秒播蜜臀 | 国产亚洲精品久久久久久电影 | av网站在线观看播放 | 最近2019好看的中文字幕免费 | 亚洲精品国产精品国自产观看 | 成人a v视频 | 久草视频在线新免费 | 久久国产精品视频观看 | 天堂视频中文在线 | 亚洲精品国产日韩 | 999免费视频 | 国产视频2| 国产一级免费在线观看 | 日本精品久久 | 天天射天天拍 | 久久成年人| 视频一区在线免费观看 | 五月婷久 | 国产码电影| 成人av片免费看 | 亚洲精选在线观看 | 香蕉视频在线视频 | 久久国产精品99久久久久 | 精品在线小视频 | 欧美日韩裸体免费视频 | 国产亚洲精品久久久久久大师 | av爱干 | 欧美动漫一区二区三区 | 伊人小视频 | 999毛片| 久久精品视频99 | 国产人成在线观看 | 狠狠干网址 | 91九色在线观看 | 久久伊人热 | 99操视频 | www.夜夜爱 | 国产精品毛片一区二区 | 久久精品中文视频 | 色婷婷精品 | 国产精品久久久久国产精品日日 | 中文视频在线看 | 精品一区二区三区电影 | 亚洲伊人第一页 | 在线成人一区 | 狠狠色伊人亚洲综合网站野外 | 国产精品原创av片国产免费 | 国产精品免费人成网站 | 黄色免费高清视频 | 日韩在线观看视频在线 | 亚洲一一在线 | 五月婷婷综 | 极品久久久久久久 | 久久午夜影视 | 欧美韩日在线 | 日本女人b| 在线观看av国产 | 成人免费视频网址 | 国产成人久久精品77777 | 国产精品99精品 | 特级a老妇做爰全过程 | 成人丁香花 | 日本久久久久久科技有限公司 | 狠狠色噜噜狠狠 | 久久久久久久久久久久久9999 | 色94色欧美 | 日韩理论片中文字幕 | 国产最新在线视频 | 天天射天天干天天插 | 中文字幕刺激在线 | 亚洲国产精品激情在线观看 | www.97视频| 成人久久精品 | 伊人资源站 | 成人午夜免费剧场 | 天天综合网在线 | 亚州精品在线视频 | 福利视频网站 | av电影免费 | 亚洲传媒在线 | 欧美最新另类人妖 | 久久歪歪 | 欧美激情视频免费看 | 日韩av进入| 国产男女爽爽爽免费视频 | 欧美一区,二区 | av在线直接看 | 欧美巨乳网 | 一区二区不卡视频在线观看 | 亚洲国产日韩欧美 | 国产一级黄色片免费看 | 日韩精品播放 | 干天天 | 人人爽影院 | 国语自产偷拍精品视频偷 | 91av在线看 | 国产小视频在线观看免费 | 日日夜夜精品免费 | 91九色在线 | 中文字幕欲求不满 | 黄色aaa级片| 在线免费观看欧美日韩 | 99久久精品日本一区二区免费 | 高清国产午夜精品久久久久久 | 久久99中文字幕 | 蜜桃视频成人在线观看 | 亚洲精品久久久蜜桃直播 | 日韩a级黄色片 | 亚洲欧美日韩国产一区二区三区 | 国产黄色特级片 | 久久久久久久久久久久av | 久99视频 | 最新av电影网址 | 中文字幕欧美日韩va免费视频 | 欧美 国产 视频 | 亚洲精品在线视频网站 | 2018亚洲男人天堂 | 在线国产片| 九九视频免费在线观看 | 五月天天av | 在线小视频 | 日韩色中色 | 国产精品96久久久久久吹潮 | 精品一区二区日韩 | 69国产成人综合久久精品欧美 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 91日韩在线专区 | 狠狠操导航 | 欧美一级性生活视频 | 国产日韩欧美在线观看视频 | 91专区在线观看 | 日韩综合一区二区 | 亚洲综合小说电影qvod | 久久免费看 | 免费看污污视频的网站 | 成人av电影网址 | 国产在线精品一区二区不卡了 | 99视频在线观看免费 | 久久综合免费视频影院 | 2017狠狠干 | 最新av在线免费观看 | 日韩精品欧美视频 | 久草在线在线精品观看 | 国产精品黄色 | 免费福利在线视频 | 狠狠干五月天 | 国产91精品久久久久 | 在线免费观看国产视频 | 麻豆超碰| 久久久久成人精品 | 91福利国产在线观看 | 一本大道久久精品懂色aⅴ 五月婷社区 | 日韩一区正在播放 | 天堂av在线网站 | 国产一级视频在线观看 | 亚洲三级影院 | 夜夜骑首页| 久久综合丁香 | 91视频黄色 | 成人国产网站 | 天堂av色婷婷一区二区三区 | 人人澡人摸人人添学生av | 久久免费看av | 国产亚洲免费观看 | 极品国产91在线网站 | 99精品视频精品精品视频 | 欧美成年黄网站色视频 | 国内久久看 | 中文理论片 | 狠狠色噜噜狠狠狠狠 | 午夜精品福利影院 | 韩国一区二区在线观看 | 日韩区欧美久久久无人区 | 国产xx在线 | 中文字幕亚洲欧美 | 免费情缘 | a黄色片在线观看 | 天天射夜夜爽 | 久久激情小视频 | 久久精彩免费视频 | 国内精品久久久久久久久久久 | 狠狠躁夜夜躁人人爽超碰91 | 99视频这里只有 | 国产麻豆精品一区二区 | 久久久久区| 精品国产精品一区二区夜夜嗨 | 欧美孕妇与黑人孕交 | 色综合天天狠天天透天天伊人 | 夜色成人网| 成年人在线免费看视频 | 国产精品一区免费看8c0m | 亚洲在线黄色 | 亚洲第一区精品 | 久草色在线观看 | 久草视频免费播放 | 精品久久久久国产免费第一页 | 中文字幕在线观看网站 | 丁香六月综合网 | 91最新中文字幕 | 国产一区二区三区免费观看视频 | 亚洲国产网址 | 99精品视频一区 | 免费成人结看片 | 日批在线观看 | 九九综合在线 | 国产精品21区 | 国产在线中文 | 婷婷久久综合网 | 日韩免费观看一区二区 | 亚洲国产欧美一区二区三区丁香婷 | 涩涩爱夜夜爱 | 国产精品成人久久久久 | 99re中文字幕 | 婷婷中文字幕综合 | 亚洲欧美日韩中文在线 | 黄色三级在线看 | 黄色亚洲大片免费在线观看 | 91成品人影院 | av免费在线看网站 | 欧美精品九九 | www激情com | 日本成人中文字幕在线观看 | 国产亚洲精品日韩在线tv黄 | 在线精品视频免费播放 | 成人h在线播放 | 99精品一区二区 | a在线观看国产 | 色婷婷中文 | 一性一交视频 | 丁香五月亚洲综合在线 | 欧美日韩国产精品一区二区 | 在线观看 国产 | 国产午夜精品久久久久久久久久 | 天天爽天天做 | 国产又黄又猛又粗 | 成人小视频在线观看免费 | 福利在线看片 | 国产成人久久精品亚洲 | 四川bbb搡bbb爽爽视频 | 亚洲色综合 | 99久久99久久精品 | av成人动漫在线观看 | 国产九九精品视频 | 久久免费视频这里只有精品 | 国产福利一区二区三区视频 | 天天操天天操天天爽 | 亚洲成av人影片在线观看 | 99久久99久久精品国产片果冰 | 91激情视频在线观看 | 日日摸日日添夜夜爽97 | 久久亚洲欧美日韩精品专区 | 国产成人一区二区三区影院在线 | 92av视频| 国产精品一区二区中文字幕 | 毛片网在线播放 | 欧美日产一区 | 午夜精品久久久久久久99热影院 | 久久艹国产视频 | 免费观看黄 | 在线观看免费版高清版 | 九九久久影院 | 国产免费嫩草影院 | 一级大片在线观看 | 国产一区二三区好的 | 色国产精品一区在线观看 | 久久精品视频在线免费观看 | 91亚洲精品国偷拍自产在线观看 | 韩日精品中文字幕 | 四虎影视成人精品 | 天堂av免费看 | 成人av网站在线 | 91在线视频免费观看 | 成人国产综合 | 亚洲在线黄色 | 精品一区电影国产 | 久草国产在线 | 一级黄色在线免费观看 | 国产欧美精品一区二区三区四区 | 91精品在线免费 | 久久高清精品 | 成人黄色在线 | 国产一级二级三级在线观看 | 香蕉一区| 免费视频资源 | 成人在线免费av | 99产精品成人啪免费网站 | 国产精品久久久免费看 | 九色在线视频 | 亚洲国产伊人 | 成人网中文字幕 | 国产成人av电影在线 | 精品9999| 一区二区日韩av | 国产综合91 | 亚洲欧美怡红院 | 久久精彩| 国产一级视频在线观看 | 亚洲综合视频在线播放 | 91精品国产高清自在线观看 | 99re亚洲国产精品 | 日韩va欧美va亚洲va久久 | 中文字幕影视 | 西西www4444大胆视频 | 国产一区二区在线视频观看 | av在线播放国产 | 日韩亚洲欧美中文字幕 | 日韩在线观看你懂得 | 日韩免费播放 | 国产精品高清在线观看 | 日韩国产精品一区 | 国产黄免费在线观看 | 亚洲另类视频在线 | 久草视频在线播放 | 久久久久久久久久国产精品 | 国产麻豆视频在线观看 | 色小说在线| 蜜臀av性久久久久蜜臀aⅴ四虎 | 国产精品一区免费看8c0m | 99九九热只有国产精品 | 亚洲九九九在线观看 | 日韩av三区 | 国产精品每日更新 | 免费av观看 | 狠狠色丁香久久婷婷综合丁香 | 国产福利av | 久久成人免费视频 | 国产精品孕妇 | 欧美看片 | 91久久偷偷做嫩草影院 | 成人一级黄色片 | 中文字幕在线影视资源 | 精品少妇一区二区三区在线 | 欧美大片大全 | 96超碰在线 | 亚洲成人第一区 | 亚洲综合在线一区二区三区 | 成人免费在线观看av | 日韩精品免费在线观看 | 日韩视频a | av成人在线网站 | 91精品少妇偷拍99 | 久久精品视频网站 | 18国产精品白浆在线观看免费 | 99视频精品全国免费 | 国内精品久久天天躁人人爽 | 色吊丝在线永久观看最新版本 | 久久夜色精品国产欧美乱 | 国产精品入口麻豆 | 久久99精品久久久久蜜臀 | 久久电影中文字幕视频 | 九九精品毛片 | 狠狠的操狠狠的干 | 国产成人精品一区二区三区在线观看 | 99久久久久久久久久 | 久久精品中文字幕一区二区三区 | 中文超碰字幕 | 免费精品人在线二线三线 | 久久精品波多野结衣 | 国产中文字幕91 | 天天在线免费视频 | av在线免费播放网站 | 成人久久久久久久久 | 在线国产精品视频 | 日韩高清观看 | 五月婷婷综合久久 | 色香网| 亚洲在线观看av | 91精品国产福利 | 成人在线视频网 | 国产一级免费视频 | 九九久久成人 | 亚洲精品99久久久久中文字幕 | 人人揉人人揉人人揉人人揉97 | japanesexxxxfreehd乱熟 | 久久99国产精品自在自在app | 日批视频在线 | 国产精品精品视频 | 亚洲精品在线观看不卡 | 日韩欧美精品一区二区三区经典 | 国产系列在线观看 | 久久www免费人成看片高清 | 91成人精品一区在线播放 | 日本最新中文字幕 | 在线观看免费视频 | 99久久精品国产一区二区三区 | 日日日日日 | 久久视屏网 | 日韩深夜在线观看 | 美女免费视频观看网站 | 不卡av在线免费观看 | 在线 你懂 | 久操视频在线播放 | av电影免费在线看 | 天堂va欧美va亚洲va老司机 | 免费视频国产 | 久久99精品久久久久久久久久久久 | 五月天电影免费在线观看一区 | 久久久久电影网站 | 亚洲激情免费 | 久久免费大片 | 国产精品女同一区二区三区久久夜 | 日p视频在线观看 | 国产原创av在线 | 中文av在线免费观看 | 91麻豆精品国产91久久久无需广告 | 在线观看小视频 | 最近中文字幕高清字幕在线视频 | 91视频免费播放 | 日韩最新av在线 | 日韩精品视频免费专区在线播放 | 香蕉视频啪啪 | 亚洲国产wwwccc36天堂 | 中文一区二区三区在线观看 | 天天躁天天操 | 国产精品h在线观看 | 四虎成人网 | 日日干夜夜草 | 午夜久久福利视频 | 亚洲精品国产欧美在线观看 | 97超碰.com | 国产精品综合久久久久 | 日韩精品视频久久 | 欧美性黄网官网 | 亚洲色图av| 国产精品久久久久久久婷婷 | 亚洲毛片一区二区三区 | 国产1区2区 | 91精品推荐 | 97爱| 青春草免费在线视频 | 91av在线看| 国产精品免费视频观看 | 久久久久久久99精品免费观看 | 夜又临在线观看 | 国产精品成人一区二区三区吃奶 | 国产视频1区2区3区 久久夜视频 | 色在线网 | 99热只有精品在线观看 | 欧洲视频一区 | 天天爱天天插 | 丁香五月亚洲综合在线 | 久久久久久久久久久综合 | 欧美一级日韩三级 | 91看成人| 欧美一级视频在线观看 | 天堂av色婷婷一区二区三区 | 日日夜夜狠狠 | 啪啪动态视频 | 超碰在线最新地址 | 亚洲理论电影网 | 国产特级毛片aaaaaaa高清 | 亚洲精品在线观看中文字幕 | 欧美色图东方 | 久久久www免费电影网 | 狠狠狠狠狠狠天天爱 | 久久视频99 | 午夜精品导航 | 久久影视精品 | 国产亚洲一区 | 中文字幕国内精品 | 黄色一级性片 | 在线激情网 | 色老板在线视频 | 成人免费xyz网站 | 人人看人人爱 | 久久这里有 | 亚洲一区二区视频在线 | 久久69av| 久草视频免费观 | 国产一级免费av | av再线观看 | 玖玖在线免费视频 | 国产一线天在线观看 | 波多野结衣在线中文字幕 | 欧美日韩不卡一区 | 一区二区三区高清 | 亚洲精品国产第一综合99久久 | 日韩精品久久久 | 成人免费观看视频网站 | 又黄又爽又色无遮挡免费 | 亚洲精品国产麻豆 | 国产免费视频在线 | 欧美精品一区二区免费 | 人人爽人人干 | 热re99久久精品国产66热 | 视频福利在线 | www.狠狠操 | 日韩理论在线播放 | 午夜av电影院 | 91九色成人 | 国产精品一区免费在线观看 | 国内精品在线观看视频 | 涩涩色亚洲一区 | 激情综合五月网 | 成人午夜毛片 | 808电影 | 欧美日韩国产一区二 | 欧美色精品天天在线观看视频 | 精品久久久久久亚洲综合网 | 在线视频精品播放 | 精品在线视频一区二区三区 | 91入口在线观看 | 免费h精品视频在线播放 | 亚洲国产免费av | 欧美色就是色 | 欧美日韩精品在线播放 | 国产亚洲成人网 | 成人看片 | 亚洲精品观看 | 综合色天天| 欧美视频网址 | 精品视频www| 亚洲精品动漫成人3d无尽在线 | 国产精品国产三级国产不产一地 | 久久a久久 | 人人爽人人爽人人爽人人爽 | 最新av网址在线 | 一区在线播放 | 亚洲欧美999| 欧美一二三区在线观看 | 91在线免费播放视频 | 国产视频在线免费 | 69人人 | 欧美日韩性视频在线 | 91久久人澡人人添人人爽欧美 | 亚洲国产中文在线 | 成人在线免费看 | 四虎成人精品永久免费av | 久久艹中文字幕 | 国产精品每日更新 | 天天天干夜夜夜操 | 亚洲精品在 | 热久久这里只有精品 | 91成熟丰满女人少妇 | 一区二区三区四区精品视频 | 欧美日韩精品影院 | 欧美在线资源 | 97超碰.com | 91成人短视频在线观看 | av在线播放不卡 | 波多野结衣在线观看一区 | 五月婷婷视频在线观看 | 国产玖玖在线 | 97电影院网 | 国产日韩精品一区二区在线观看播放 | 国产日韩精品在线观看 | 国内精品久久久久久久久久久久 | 中文字幕人成人 | 成人在线你懂得 | 国产日产精品一区二区三区四区的观看方式 | av中文字幕av | 五月天综合网 | 91完整版 | 日本精品一二区 | 五月婷婷一区二区三区 | 天天综合网国产 | 日韩三级av | 最近中文字幕在线播放 | 99热99 | 亚洲精品18日本一区app | 黄色成年 | 亚洲最新av在线网站 | 成人在线播放免费观看 | 一级a毛片高清视频 | 精品天堂av | 狠狠躁夜夜躁人人爽超碰97香蕉 | 婷婷在线免费观看 | 99久久国产免费,99久久国产免费大片 | 久久久www免费电影网 | 久久这里只有精品1 | 久久国产成人午夜av影院宅 | 男女男视频 | 色在线最新| av大片免费在线观看 | 亚洲综合在线播放 | 久久久久久久久网站 | 狠狠色狠狠色合久久伊人 | 欧美男女爱爱视频 | 中文字幕第 | 欧美一级小视频 | 精品视频免费观看 | 手机成人av在线 | 成人资源在线 | 射久久久 | 国产四虎影院 | 国产精品入口麻豆 | 黄在线免费看 | 国产无套精品久久久久久 | 国内精品久久久久久久97牛牛 | 国产一区二区在线播放视频 | 欧美在线观看视频一区二区三区 | 久久久亚洲麻豆日韩精品一区三区 | 国产日韩欧美在线影视 | 亚洲精品系列 | 超碰国产在线 | 日日夜夜精品免费 | 中文字幕免费在线看 | 色综合中文综合网 | 国产成人精品一区二区在线观看 | 亚洲视频在线免费看 | 中文字幕在线观看免费 | 国产精品99爱 | 国产一区二区在线观看免费 | 久久视屏网 | 操操碰| 欧美伦理电影一区二区 | 日韩婷婷 | 成人精品国产免费网站 | 日韩视频在线观看视频 | www九九热| 麻豆精品视频在线观看免费 | 欧美日韩另类在线观看 | 久艹视频在线免费观看 | 免费福利视频网 | 久久亚洲在线 | 午夜精品一区二区三区在线视频 | 久草视频在线资源站 | 久草在线费播放视频 | 91精品网站| 亚洲国产精品成人女人久久 | 色五月成人 | 日本特黄一级片 | 91女神的呻吟细腰翘臀美女 | 午夜性福利 | 日韩激情一二三区 | 丁香花在线观看视频在线 | 久久国产精品免费一区二区三区 | 精品亚洲欧美无人区乱码 | 久久精久久精 | 成人资源在线观看 | 国产香蕉久久精品综合网 | 国产成人久久久久 | 中文字幕 国产专区 | 在线观看日韩免费视频 | av丁香花 | 91成人精品一区在线播放69 | 日日干视频 | 黄色av电影一级片 | 在线视频 影院 | 在线播放视频一区 | 西西人体4444www高清视频 | 国产手机在线 | 丁香婷婷综合激情五月色 | 日韩欧美高清视频在线观看 | 91麻豆操| 国产一级视频在线免费观看 | 久久久精品午夜 | 天天视频色 | 韩日色视频 | 蜜臀一区二区三区精品免费视频 | 日韩av中文在线观看 | 亚洲国产精品va在线看黑人动漫 | 婷婷激情5月天 | 久久国产系列 | www色网站 | 国产精品久久麻豆 | 成人在线观看资源 | 免费看黄色小说的网站 | 国产精品久久久久四虎 | 91在线入口| 久久国产精品小视频 | 国内精品福利视频 | 日日夜夜操操操操 | 国产v欧美 | 亚洲精品午夜久久久 | 91高清免费观看 | 欧美日韩一区二区三区在线观看视频 | 亚洲精品免费观看 | 免费av福利 | 久久综合久久综合久久综合 | 国产一区二区三精品久久久无广告 |