Ubuntu和Win 10下用CMAKE + SWIG 為C#寫動態庫


本文采用這個項目的教學代碼:

https://github.com/Mizux/dotnet-native

作者自稱是cmake的開發人員,不知道真假,不過這個項目代碼組織看起來挺專業的,就它了。這里主要研究如何用cmake + swig + dotnet + gcc/vc 將代碼部署到ubuntu和winows下面,代碼本身功能不是重點。

1 Make It Work

1.1 Ubuntu

可以參考https://github.com/Mizux/dotnet-native/blob/master/ci/docker/ubuntu/Dockerfile,這里略有修改,下載了各個軟件的最新版本

sudo apt-get update
sudo apt install g++
g++ --version                                                         #檢查一下版本,顯示7.5.0, 這里我們想用最新版本g++-9
sudo apt install software-properties-common                           #參考https://linuxize.com/post/how-to-install-gcc-compiler-on-ubuntu-18-04/#installing-gcc-on-ubuntu
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt install g++-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9 --slave /usr/bin/gcov gcov /usr/bin/gcov-9
g++ --version                                                         #檢查一下版本,本例子中已經更新為9.3.0
wget "https://cmake.org/files/v3.17/cmake-3.17.2-Linux-x86_64.sh"     #sudo apt install cmake只能下載到3.10版本,本例子需要在3.14或以上版本
chmod a+x cmake-3.17.2-Linux-x86_64.sh
sudo ./cmake-3.17.2-Linux-x86_64.sh --prefix=/usr/local/ --skip-license
rm cmake-3.17.2-Linux-x86_64.sh
cmake --version                                                       #檢查一下版本,3.17.2。使用terminal的需要重開一個窗口,否則會顯示找不到cmake
wget ftp://ftp.pcre.org/pub/pcre/pcre-8.00.tar.gz                     #下載pcre,安裝swig 4.0版本需要, sudo apt-get install -y swig只能拿到3.0.12版本
tar -xzf pcre-8.00.tar.gz
cd pcre-8.00/
./configure --prefix=/usr/local/pcre                                  #安裝到/usr/local/pcre/bin/pcre-config
make
sudo make install                                                     #pcre安裝完成
export PATH=/usr/local/pcre/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/pcre/lib:$LD_LIBRARY_PATH
cd ..
wget http://prdownloads.sourceforge.net/swig/swig-4.0.2.tar.gz             
tar -xzf swig-4.0.2.tar.gz
cd swig-4.0.2                                                         #參考http://www.swig.org/Doc4.0/SWIGDocumentation.html#Preface_unix_installation
./configure
make
sudo make install
export LD_LIBRARY_PATH=/usr/local/pcre/lib:$LD_LIBRARY_PATH           #把libpcre.so.0加入系統庫目錄中
swig -version                                                         #檢查一下版本,本例子中使用的是4.0.2版本
cd ..
sudo apt-get update -qq                                               #安裝.NET
sudo apt-get install -yq wget apt-transport-https
wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq dotnet-sdk-3.1
sudo apt-get clean
dotnet --version                                                      #檢查版本,本例子中是3.1.301
git clone https://github.com/Mizux/dotnet-native                      #下載項目代碼
cd dotnet-native
cmake -S. -Bbuild
cmake --build build --target all -v
sudo cmake --build build --target install -v
cmake --build build --target test -v                                  #should read 100% tests passed, 0 tests failed out of 3
cd ..
mkdir sample
cd sample
cp ../dotnet-native/build/dotnet/packages/*.nupkg ./
cp ../dotnet-native/ci/samples/* ./                                   #sample目錄下有Mizux.Foo.1.0.0.nupkg  Mizux.Foo.runtime.linux-x64.1.0.0.nupkg  Mizux.Sample.csproj  Sample.cs
dotnet build
dotnet run                                                            #[1] Enter FooApp
                                                                      #[2] Enter Foo::hello(int)
                                                                      #[3] Enter fooHello(int)
                                                                      #[3] Exit fooHello(int)
                                                                      #[2] Exit Foo::hello(int)
                                                                      #[1] Exit FooApp
                                                                      #成功!!

1.2 Windows

安裝CMake

去https://cmake.org/download/ 下載並安裝cmake-3.16.8-win64-x64.msi

安裝完應該能看到

安裝visual studio 2019

現在都免費了.去https://visualstudio.microsoft.com/vs/,點擊上面的Free Visual Studio,然后下載左邊的Visual Studio Community->Free download

下載完之后,雙擊打開。本項目需要c++和c#,所以要安裝兩個workload

下載github教學代碼

可以下載zip,並解壓

構建項目

打開CMake,點擊右上角Browse Source,選擇上面解壓后dotnet-native-master目錄

browse build里面的路徑=Browse Source里面的路徑 + /build,完成后點擊左下configure

這時會讓你選擇generator,這里選擇Visual Studio 16 2019,然后點擊Finish

這個過程需要十幾秒,然后會在下框中顯示Configuration done

然后點擊Generate按鈕,下框中顯示Generating done。

點擊Open Project,這時會彈出Visual Studio 2019,點擊Build Solution, 開始編譯項目

編譯完成后,File->Close Solution關閉項目。 此時build/dotnet/packages下有了nupkg文件,這就是build產生的包文件。接下來用例子說們怎么用這個包文件

測試樣例

重新打開一個項目:Ctrl+Shift+O 打開 dotnet-native-master\ci\samples\Mizux.Sample.csproj

設置nupkg,首先卸載Mizux.Sample.csproj中的Mizux.Foo包,因為這個包默認是從nuget.org上下載的, 具體可以打開Mizux.Sample.csproj,看里面的設置。

Tools->NuGet Package Manager-> Manage NuGet Packages for Solution,在installed面板左邊選擇Mizux.Foo,在右邊選擇Mizux.Sample,點擊Uninstall

接着需要載入本地編譯好的Mizux.Foo包。Tools->Options->NuGet Package Manager->Packages Sources,在右邊點綠色的+按鈕添加一個包,name填寫Mizux.Foo,Source里面選擇
dotnet-native-master\dotnet-native-master\build\dotnet\packages,即含有nupkg和snupkg的文件夾。然后點擊OK,這樣就設置好路徑了。

接下來添加nupkg文件,右鍵Mizux.Sample,選擇Manager NuGet Packages


在Browse面板沖,在右上角的下拉框中選擇Mizux.Foo (默認是nuget.org),然后在左邊選擇Mizux.Foo,最后點擊右邊的install。

現在已經把Mizux.Foo.1.0.0.nupkg安裝到項目中了。最后點擊綠色的運行按鈕,執行結果如下:

2 代碼分析

先看看代碼組織:

xian@chaos:~/code/dotnet-native$ tree
.
├── AUTHORS
├── ci
│   ├── doc
│   │   ├── deps.dot
│   │   ├── deps.svg
│   │   └── generate_image.sh
│   ├── docker
│   │   ├── alpine
│   │   │   └── Dockerfile
│   │   ├── archlinux
│   │   │   └── Dockerfile
│   │   ├── centos
│   │   │   └── Dockerfile
│   │   ├── debian
│   │   │   └── Dockerfile
│   │   ├── fedora
│   │   │   └── Dockerfile
│   │   ├── opensuse
│   │   │   └── Dockerfile
│   │   └── ubuntu
│   │       └── Dockerfile
│   ├── Makefile
│   ├── README.md
│   └── samples
│       ├── Mizux.Sample.csproj
│       └── Sample.cs
├── cmake
│   ├── CMakeLists.txt.swig
│   ├── cpp.cmake
│   ├── dotnet.cmake
│   ├── FooConfig.cmake.in
│   └── swig.cmake
├── CMakeLists.txt
├── doc
│   ├── full_pipeline.dot
│   ├── full_pipeline.png
│   ├── full_pipeline.svg
│   ├── generate_image.sh
│   ├── legend.dot
│   ├── legend.png
│   ├── legend.svg
│   ├── local_pipeline.dot
│   ├── local_pipeline.png
│   └── local_pipeline.svg
├── dotnet
│   ├── base.i
│   ├── Directory.Build.props
│   ├── FooApp.cs
│   ├── FooTests.cs
│   ├── logo.png
│   ├── logo.svg
│   ├── Mizux.FooApp.csproj.in
│   ├── Mizux.Foo.csproj.in
│   ├── Mizux.Foo.runtime.csproj.in
│   └── Mizux.FooTests.csproj.in
├── Foo
│   ├── CMakeLists.txt
│   ├── dotnet
│   │   ├── CMakeLists.txt
│   │   └── foo.i
│   ├── include
│   │   └── foo
│   │       └── Foo.hpp
│   ├── src
│   │   └── Foo.cpp
│   └── test
│       ├── CMakeLists.txt
│       └── src
│           ├── Foo_UT.cpp
│           └── main.cpp
├── LICENSE
└── README.md
21 directories, 51 files

先看一下代碼,知道這個項目做了什么

2.1 Foo/src/Foo.cpp

#include "foo/Foo.hpp"

#include <iostream>

namespace foo {
void fooHello(int level) {
  std::cout << "[" << level << "] Enter fooHello(int)" << std::endl;
  std::cout << "[" << level << "] Exit fooHello(int)" << std::endl;
}

void fooHello(int64_t level) {
  std::cout << "[" << level << "] Enter fooHello(int64_t)" << std::endl;
  std::cout << "[" << level << "] Exit fooHello(int64_t)" << std::endl;
}

void Foo::hello(int level) {
  std::cout << "[" << level << "] Enter Foo::hello(int)" << std::endl;
  foo::fooHello(level + 1);
  std::cout << "[" << level << "] Exit Foo::hello(int)" << std::endl;
}

void Foo::hello(int64_t level) {
  std::cout << "[" << level << "] Enter Foo::hello(int64_t)" << std::endl;
  foo::fooHello(level + 1);
  std::cout << "[" << level << "] Exit Foo::hello(int64_t)" << std::endl;
}

std::string Foo::operator()() const {
  return std::string{"\"Foo\":{\"int\":"} + std::to_string(_intValue) +
         ",\"int64\":" + std::to_string(_int64Value) + "}";
}

int Foo::getInt() const {
  return _intValue;
}

void Foo::setInt(int input) {
  _intValue = input;
}

int64_t Foo::getInt64() const {
  return _int64Value;
}

void Foo::setInt64(int64_t input) {
  _int64Value = input;
}

} // namespace foo

就是一些set和print,根據輸入是int64還是int調用不同的函數

2.2 Foo/include/foo/Foo.hpp

#pragma once

#include <cstdint>
#include <string>

namespace foo {
void fooHello(int level);
void fooHello(int64_t level);

class Foo {
 public:
  static void hello(int level);

  static void hello(int64_t level);

  int getInt() const;

  void setInt(int input);

  int64_t getInt64() const;

  void setInt64(int64_t input);

  std::string operator()() const;

 private:
  int     _intValue   = 0;
  int64_t _int64Value = 0;
};
} // namespace foo

2.3 Foo/dotnet/foo.i

其中/* */是本文追加注釋
%module csFoo
/* c#沒有普通函數,所有函數必須是類的成員函數,如何調用c++的普通函數呢,可以用csFoo.函數名來調用,比如csFoo.fooHello(12) */

%include "std_string.i"
/* 提供std::string接口 */
%include "base.i"

// Add necessary symbols to generated header
%{
#include <foo/Foo.hpp>
%}
/* 在翻譯產生的 /home/xian/code/dotnet-native/build/dotnet/Foo/Foo/fooCSHARP_wrap.cxx 文件中加入#include <foo/Foo.hpp> */
/* swig只負責翻譯,對於翻譯好的c++代碼中的函數由來,需要人工指定其頭文件 */

%ignore ""; /* 忽略頭文件中所有的聲明 參考 http://www.swig.org/Doc3.0/SWIGDocumentation.html#SWIG_chosen_unignore */
%define %unignore %rename("%s") %enddef
/* %rename(A) B;
   %include "header.h"
   將 header.h中的B函數重新命名為A供c#調用。這是為了防止c#和c++函數重名。
   
   %rename("%s") "" 相當於 對於任意的A %rename(A) A
   %define %unignore %rename("%s") %enddef 是一個宏,
   %rename("A") 展開為 %unignore %rename("A") 
   比如 %rename("FooHello") fooHello(int); 展開為
   %unignore %rename("FooHello") fooHello(int); 即不忽略fooHello(int)函數,並將其重命名為FooHello
*/


%unignore foo;
namespace foo {
%rename("FooHello") fooHello(int);
%rename("FooHello") fooHello(int64_t);

%unignore Foo;
%rename("Hello") Foo::hello(int);
%rename("Hello") Foo::hello(int64_t);

%rename("GetInt") Foo::getInt() const;
%rename("SetInt") Foo::setInt(int);

%rename("GetInt64") Foo::getInt64() const;
%rename("SetInt64") Foo::setInt64(int64_t);

%rename ("ToString") Foo::operator();
/* c#中 A.ToString()調用的是A()
} // namespace foo

// Process symbols in header
%include "foo/Foo.hpp"

%unignore ""; // unignore all

2.4 dotnet/base.i

%include "stdint.i"
%include "typemaps.i"

#if defined(SWIGCSHARP)
#if defined(SWIGWORDSIZE64)
// By default SWIG map C++ long int (i.e. int64_t) to C# int
// But we want to map it to C# long so we reuse the typemap for C++ long long.
// ref: https://github.com/swig/swig/blob/master/Lib/csharp/typemaps.i
// ref: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types
%define PRIMITIVE_TYPEMAP(NEW_TYPE, TYPE)
%clear NEW_TYPE;
%clear NEW_TYPE *;
%clear NEW_TYPE &;
%clear const NEW_TYPE &;
%apply TYPE { NEW_TYPE };
%apply TYPE * { NEW_TYPE * };
%apply TYPE & { NEW_TYPE & };
%apply const TYPE & { const NEW_TYPE & };
%enddef // PRIMITIVE_TYPEMAP
PRIMITIVE_TYPEMAP(long int, long long);
PRIMITIVE_TYPEMAP(unsigned long int, unsigned long long);
#undef PRIMITIVE_TYPEMAP
#endif // defined(SWIGWORDSIZE64)
#endif // defined(SWIGCSHARP)

映射了一些類型,這一段代碼應該對所有c#項目有效,無需修改,就不深究了。

2.5 dotnet/FooApp.cs

using System;

using Mizux.Foo;

namespace FooApp {
  class Program {
    static void Main(string[] args) {
      int level = 1;
      Console.WriteLine($"[{level}] Enter FooApp");
      Foo.Hello(level+1);
      Console.WriteLine($"[{level}] Exit FooApp");
    }
  }
}

根據前面的分析,該代碼應該輸出

[1] Enter FooApp
[2] Enter Foo::hello(int)
[3] Enter fooHello(int)
[3] Exit fooHello(int)
[2] Exit Foo::hello(int)
[1] Exit FooApp

3 CMake構建、安裝代碼分析

項目的構建順序是先由CMake+Swig生成.so 和cs的wrapper,即mizux-foo-native。這一步只產生了一個c#頭文件,可以供其他c#代碼調用。但沒有成為c#的庫

然后由dotnet對其進行封裝,加入csproj文件,並打包成nupkg,可供其他c#項目導入。所以這里先看CMake代碼,再看csproj代碼

3.1 CMakeLists.txt

現在開始理解cmake的流程了。從根目錄的CMakeLists.txt看起。###后面跟的是本文的注釋

# This file is just an orchestration
cmake_minimum_required(VERSION 3.14)  ### cmake版本至少3.14 
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")  ###相當於 g++ -Icmake
###${CMAKE_CURRENT_SOURCE_DIR}指的是含有當前CMakeLists.txt的文件夾,即根目錄

option(CMAKE_EXPORT_COMPILE_COMMANDS "Export compile command" TRUE)
###設置了一個cmake build的參數,默認值為TRUE,具體作用應該在后面作為條件build的根據

project(Foo VERSION 1.0 LANGUAGES CXX)
###設置${PROJECT_NAME} = Foo, ${PROJECT_VERSION} = 1.0 語言是C++
message(STATUS "project: ${PROJECT_NAME}") ###打印 project: Foo
message(STATUS "version: ${PROJECT_VERSION}")  ### version: 1.0

# Force default build type to Release
if(NOT CMAKE_BUILD_TYPE)   ###cmake build時候可以用-DCMAKE_BUILD_TYPE=Release/Debug/... 
                           ### 設置build type是release還是debug還是其他,
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING   
                           ###默認是Release, 
                           ###CACHE=global,表明這個type對其他cmake,CMakeLists文件有效
    "Choose the type of build, options are: Debug, Release (default), RelWithDebInfo and MinSizeRel."
    FORCE) ###如果CMAKE_BUILD_TYPE被其他調用該項目的項目設置過,這里則強制重新設置。
endif(NOT CMAKE_BUILD_TYPE)

# Layout build dir like install dir
include(GNUInstallDirs)   ### 定義了一些安裝所需要的路徑變量,比如CMAKE_INSTALL_BINDIR用於安裝執行程序的相對路徑。 
                          ###(安裝實際上就是復制build好的文件到指定的目錄,以便系統調用)
if(UNIX)
  option(BUILD_SHARED_LIBS "Build shared libraries(.so or .dyld)." ON)    ###build 動態連接庫,默認是TRUE
  set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)  
    ### 啟用RPATH。當CMake build好可執行文件a.exe以及它所依賴的動態庫b.so后,安裝時會將他們復制到指定的目錄中去,
    ### 根據系統不同,安裝的絕對路徑也不同,比如python3.6/...和python3.7/... 這樣安裝好的a.exe不可能通過絕對路徑尋找b.so,因此RPATH
    ### 就用來提供尋找b.so的相對路徑
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 
    ### CMAKE_LIBRARY_OUTPUT_DIRECTORY = build/lib 存放build好的*.so或*.dll等動態庫文件
  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 
    ### CMAKE_ARCHIVE_OUTPUT_DIRECTORY = build/lib 存放build好的*.a或*.lib等靜態庫文件
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
   ### CMAKE_RUNTIME_OUTPUT_DIRECTORY = build/bin 存放build好的可執行文件
  # for multi-config build system (e.g. xcode) 
  foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
    string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
  endforeach()
else()
  # Currently Only support static build for windows
  set(BUILD_SHARED_LIBS OFF)   ###如果是windows,不產生動態庫
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
  # for multi-config builds (e.g. msvc)
  foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) ###such as Debug, Release, RelWithDebInfo etc.
    string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)  ###Debug => DEBUG
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
       ###CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG = build/DEBUG/bin
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
  endforeach()
endif()

include(CTest)       ###先跳過單元測試
if(BUILD_TESTING)
  include(FetchContent)
  FetchContent_Declare(
    Catch2
    GIT_REPOSITORY https://github.com/catchorg/Catch2.git
    GIT_TAG master
    GIT_SHALLOW TRUE
    GIT_PROGRESS TRUE
  )
  FetchContent_MakeAvailable(Catch2)
endif()

include(cpp)  ### include "cmake/cpp.cmake"

if(WIN32)
  include(swig)    ###include "cmake/swig.cmake"
endif()

include(dotnet)    ###include "cmake/dotnet.cmake"

看來根目錄的CMakeLists.txt主要設置了一些公共變量,至於具體執行什么還要看cmake下面的*.cmake文件

3.2 cmake/cpp.cmake

# Check primitive types
include(CMakePushCheckState)    ###這幾行不知道要干嘛
cmake_push_check_state(RESET)   
set(CMAKE_EXTRA_INCLUDE_FILES "cstdint")
include(CheckTypeSize)
check_type_size("long" SIZEOF_LONG LANGUAGE CXX)
message(STATUS "Found long size: ${SIZEOF_LONG}")
check_type_size("long long" SIZEOF_LONG_LONG LANGUAGE CXX)
message(STATUS "Found long long size: ${SIZEOF_LONG_LONG}")
check_type_size("int64_t" SIZEOF_INT64_T LANGUAGE CXX)
message(STATUS "Found int64_t size: ${SIZEOF_INT64_T}")

check_type_size("unsigned long" SIZEOF_ULONG LANGUAGE CXX)
message(STATUS "Found unsigned long size: ${SIZEOF_ULONG}")
check_type_size("unsigned long long" SIZEOF_ULONG_LONG LANGUAGE CXX)
message(STATUS "Found unsigned long long size: ${SIZEOF_ULONG_LONG}")
check_type_size("uint64_t" SIZEOF_UINT64_T LANGUAGE CXX)
message(STATUS "Found uint64_t size: ${SIZEOF_UINT64_T}")

check_type_size("int *" SIZEOF_INT_P LANGUAGE CXX)
message(STATUS "Found int * size: ${SIZEOF_INT_P}")
cmake_pop_check_state()

add_subdirectory(Foo)  ### include ../Foo/CMakeList.txt

# Install
include(GNUInstallDirs)   ###和之前一樣,取出一些預定義的安裝路徑變量
install(EXPORT ${PROJECT_NAME}Targets 
    ###安裝FooTargets中的Devel部分(命令最后COMPONENT=Devel)
    ###FooTargets在別的地方定義,build完之后,會產生一個FooTargets.cmake文件
    ###該文件用來讓別的項目調用FooTargets
    ###用install(EXPORT 將FooTargets中的Devel部分安裝到指定目錄DESTINATION
  NAMESPACE ${PROJECT_NAME}::       
    ###在別的項目引用FooTargets的時候加上Foo::前綴
    ###即target_link_libraries(xxx Foo::FooTargets)
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}   
      ###${CMAKE_INSTALL_PREFIX}/lib/cmake/Foo 
      ###linux 上 ${CMAKE_INSTALL_PREFIX}默認為/usr/local 
      ###windows 上默認為 C:\Program Files
      ###注意默認的路徑需要root賬號才有寫權限,
      ###可以通過cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-01 修改
  COMPONENT Devel)
include(CMakePackageConfigHelpers) ###產生配置文件,這個文件用來檢查一些文件是否存在
configure_package_config_file(cmake/${PROJECT_NAME}Config.cmake.in
    ### 配置cmake/FooConfig.cmake.in (將文件中的變量替換)並復制到
  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"  ###build/FooConfig.cmake
  INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
    ###再安裝到lib/cmake/Foo下面
  NO_SET_AND_CHECK_MACRO  ###FooConfig.cmake中不含有set_and_check()
  NO_CHECK_REQUIRED_COMPONENTS_MACRO)  ###不含有 check_required_components()
write_basic_package_version_file(
  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  COMPATIBILITY SameMajorVersion) ###設置版本為主版本號 
   ###CMake的版本格式為 Release: <major>.<minor>.<patch>[-rc<n>] 
   ###                Develop: <major>.<minor>.<date>[-<id>]
install(
  FILES  ###復制文件
  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
  COMPONENT Devel)

看一下cmake/FooConfig.cmake.in文件,也就是一個include

include("${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake") 
### CMAKE_CURRENT_LIST_DIR為當前文件所在的目錄

3.3 Foo/CMakeLists.txt

add_library(Foo "") ###添加一個庫
target_sources(Foo  ###添加代碼
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/foo/Foo.hpp>
    $<INSTALL_INTERFACE:include/foo/Foo.hpp>
    ###此處帶有尖括號的部分稱為生成器表達式,這兩行的意思是:
    ###如果當前是build階段,則包含${CMAKE_CURRENT_SOURCE_DIR}/include/foo/Foo.hpp
    ###如果當前是install階段,則包含include/foo/Foo.hpp
    ###這是因為在build階段,我們用的是原始的代碼文件,CMAKE_CURRENT_SOURCE_DIR = /home/xian/code/dotnet-native/Foo
    ###在install階段,源文件會被安裝到某一個地方,所以需要用安裝地的路徑
  PRIVATE
    src/Foo.cpp
  )
  ### PUBLIC: 目標中public的部分對其他項目可見, 因為其他項目調用該庫的時候是需要知道其頭文件的,所以這里為public
  ### 如果有很多頭文件,其中只有一個頭文件是對外可見的,則將該文件放入到INSTALL_INTERFACE中
  ### 如果頭文件如果都放在一個目錄中(可以有子目錄),那么只需要設置該目錄(不需要例舉子目錄)即可包含所有頭文件。
  ### PRIVATE: 對其他項目不可見。常常是cpp文件。這部分需要顯式包含所有文件

target_include_directories(Foo ###包含頭文件目錄,相當於g++ -IFoo
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
  )
  
target_compile_features(Foo PUBLIC cxx_std_11) 
  ###std=c++11
  ###PUBLIC ???
set_target_properties(Foo PROPERTIES
  VERSION ${PROJECT_VERSION}
  PUBLIC_HEADER $<TARGET_PROPERTY:Foo,INTERFACE_SOURCES> 
   ### PUBLIC_HEADER = Foo.INTERFACE_SOURCES
   ### 可以用 file(GENERATE OUTPUT aaa.txt CONTENT $<TARGET_PROPERTY:Foo,INTERFACE_SOURCES>)
   ### 打印出值
   ### 這個值和BUILD_INTERFACE相同:/home/xian/code/dotnet-native/Foo/include/foo/Foo.hpp
   ###$<TARGET_PROPERTY:tgt,prop> = tgt.prop
  )

add_library(${PROJECT_NAME}::Foo ALIAS Foo)  ### 將Foo::Foo重新命名為Foo , Foo::為之前設置的NAMESPACE

if(BUILD_TESTING)
  add_subdirectory(test)
endif()

# Install
install(TARGETS Foo    ###此處定義install 目標
  EXPORT ${PROJECT_NAME}Targets  ###將Foo目標輸出為FooTargets目標 (FooTargets.cmake)
  PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/foo ###頭文件安裝到 include/foo
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}  ###靜態庫安裝到lib
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}  ###動態庫安裝到lib
  #RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  )

3.4 cmake/dotnet.cmake

# Will need swig
set(CMAKE_SWIG_FLAGS)  ###設置一些swig的變量,比如set(CMAKE_SWIG_FLAGS -DPL_DOUBLE_INTERFACE -DSWIG_PYTHON)
                       ###這里什么都沒有設置,只是引入,后面會設置
find_package(SWIG REQUIRED) ###尋找SWIG
include(UseSWIG)  ###include UseSWIG.cmake 參考https://github.com/Kitware/CMake/blob/master/Modules/UseSWIG.cmake

if(UNIX AND NOT APPLE)
  list(APPEND CMAKE_SWIG_FLAGS "-DSWIGWORDSIZE64")
endif()

# Find dotnet
find_program(DOTNET_EXECUTABLE dotnet)  ###尋找dotnet的執行文件,需要安裝dotnet
if(NOT DOTNET_EXECUTABLE)
  message(FATAL_ERROR "Check for dotnet Program: not found")
else()
  message(STATUS "Found dotnet Program: ${DOTNET_EXECUTABLE}")
endif()
### ${DOTNET_EXECUTABLE} = dotnet

# Create the native library
###native是最初始的包裝c++的庫,可供c#調用
add_library(mizux-foo-native SHARED "")  ###添加動態庫mizux-foo-native
set_target_properties(mizux-foo-native PROPERTIES
  PREFIX "" ###如果PREFIX = xxx則生成xxxmizux-foo-native.so
  POSITION_INDEPENDENT_CODE ON)   ###代碼位置獨立
     ###多個程序利用相同的動態庫時,動態庫只加載一遍,從而減小內存
     ###這個共享的動態庫只有一個代碼地址,與具體調用程序無關,所以是獨立的
     ###具體參考 How to write shared libraries https://akkadia.org/drepper/dsohowto.pdf
# note: macOS is APPLE and also UNIX !
if(APPLE)
  set_target_properties(mizux-foo-native PROPERTIES INSTALL_RPATH "@loader_path")
  # Xcode fails to build if library doesn't contains at least one source file.
  if(XCODE)
    file(GENERATE
      OUTPUT ${PROJECT_BINARY_DIR}/mizux-foo-native/version.cpp
      CONTENT "namespace {char* version = \"${PROJECT_VERSION}\";}")
    target_sources(mizux-foo-native PRIVATE ${PROJECT_BINARY_DIR}/mizux-foo-native/version.cpp)
  endif()
elseif(UNIX)
  set_target_properties(mizux-foo-native PROPERTIES INSTALL_RPATH "$ORIGIN")
  ###設置INSTALL_RPATH = $ORIGIN, $ORIGIN是指mizux-foo-native安裝的目錄,這句話說明安裝后,
  ###mizux-foo-native.so 調用同一目錄下的 libFoo.so 
  ###xian@chaos:~/code/dotnet-native/build/lib$ ls
  ###libFoo.so  libFoo.so.1.0  mizux-foo-native.so
endif()

set(DOTNET Mizux.Foo)   ###${DOTNET} = Mizux.Foo
foreach(SUBPROJECT IN ITEMS Foo) ###foreach(SUBPROJECT IN ITEMS A B C ...)
  add_subdirectory(${SUBPROJECT}/dotnet) ###include Foo/dotnet/CMakeLists.txt
  target_link_libraries(mizux-foo-native PRIVATE dotnet_${SUBPROJECT})
  ###mizux-foo-native依賴於dotnet_Foo
  ###這里的PRIVATE是指調用mizux-foo-native的項目不需要知道dotnet_Foo的實現和接口。
  ###mizux-foo-native對dotnet_Foo的所有功能進行重新封裝。
  ###具體參考 https://cmake.org/pipermail/cmake/2016-May/063400.html
endforeach()

file(COPY dotnet/logo.png DESTINATION dotnet) ###復制文件
file(COPY dotnet/Directory.Build.props DESTINATION dotnet)
###Directory.Build.props是.net的文件,為一組項目統一配置版本,作者等等

###接下來是一組lib或者可執行程序。這里選取Mizux.Foo.runtime.<RID>, Mizux.Foo和Mizux.FooApp分析
###############################
##  Mizux.Foo.runtime.<RID>  ##
###############################

### runtime 是一個可以獨立運行的程序,包含了所有依賴,是平台dependent的
set(DOTNET_PACKAGES_DIR ${PROJECT_BINARY_DIR}/dotnet/packages)
###DOTNET_PACKAGES_DIR = build/dotnet/packages , 所有build好的nupkg,snupkg包都放在這里
if(APPLE)
  set(RUNTIME_IDENTIFIER osx-x64)
elseif(UNIX)
  set(RUNTIME_IDENTIFIER linux-x64)
elseif(WIN32)
  set(RUNTIME_IDENTIFIER win-x64)
else()
  message(FATAL_ERROR "Unsupported system !")
endif()
set(DOTNET_NATIVE ${DOTNET}.runtime.${RUNTIME_IDENTIFIER})
###DOTNET_NATIVE = Mizux.Foo.runtime.linux-x64 (ubuntu) 或者 Mizux.Foo.runtime.win-x64 (windows)

# pom*.xml.in contains:
# CMake variable(s) (@PROJECT_NAME@) that configure_file() can manage and
# generator expression ($<TARGET_FILE:...>) that file(GENERATE) can manage.
configure_file(
  ${PROJECT_SOURCE_DIR}/dotnet/${DOTNET}.runtime.csproj.in
  ${PROJECT_BINARY_DIR}/dotnet/${DOTNET}.runtime.csproj.in
  @ONLY)  ###替換其中@xxx@的變量
### dotnet/Mizux.Foo.runtime.csproj.in 復制到 build/dotnet/Mizux.Foo.runtime/csproj.in 並替換其中@xxx@的變量

file(GENERATE
  OUTPUT ${PROJECT_BINARY_DIR}/dotnet/$<CONFIG>/${DOTNET}.runtime.csproj.in  
  INPUT ${PROJECT_BINARY_DIR}/dotnet/${DOTNET}.runtime.csproj.in)  
###根據build/dotnet/Mizux.Foo.runtime.csproj.in 產生文件 build/dotnet/Release/Mizux.Foo.runtime.csproj.in
###其中$<xxx>的部分會被替換

add_custom_command(
  OUTPUT dotnet/${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj  
    ###build/dotnet/Mizux.Foo.runtime.linux-x64/Mizux.Foo.runtime.linux-x64.csproj
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET_NATIVE}
  COMMAND ${CMAKE_COMMAND} -E copy ./$<CONFIG>/${DOTNET}.runtime.csproj.in ${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj 
    ###build/dotnet/Release/Mizux.Foo.runtime.csproj.in 復制到
    ###  build/dotnet/Mizux.Foo.runtime.linux-x64/Mizux.Foo.runtime.linux-x64.csproj
  WORKING_DIRECTORY dotnet)   ###在build/dotnet目錄下執行

add_custom_target(dotnet_native_package  
  DEPENDS
  mizux-foo-native
  dotnet/${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj         
    ### dotnet/Mizux.Foo.runtime.linux-x64/Mizux.Foo.runtime.linux-x64.csproj
  COMMAND ${CMAKE_COMMAND} -E make_directory packages     ### mkdir build/dotnet/packages
  COMMAND ${DOTNET_EXECUTABLE} build -c Release ${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj
     ### Builds a project and all of its dependencies.
  COMMAND ${DOTNET_EXECUTABLE} pack -c Release ${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj
     ### Packs the code into a NuGet package.
  WORKING_DIRECTORY dotnet)

#################
##  Mizux.Foo  ##
#################
configure_file(
  ${PROJECT_SOURCE_DIR}/dotnet/${DOTNET}.csproj.in  ###dotnet/Mizux.Foo.csproj.in
  ${PROJECT_BINARY_DIR}/dotnet/${DOTNET}.csproj.in  ###build/dotnet/Mizux.Foo.csproj.in
  @ONLY)
###dotnet/Mizux.Foo.csproj.in 復制到 build/dotnet/Mizux.Foo.csproj.in,並替換其中@xxx@的變量

add_custom_command(
  OUTPUT dotnet/${DOTNET}/${DOTNET}.csproj
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET}
  COMMAND ${CMAKE_COMMAND} -E copy ./${DOTNET}.csproj.in ${DOTNET}/${DOTNET}.csproj
  WORKING_DIRECTORY dotnet)
###在build/dotnet下,創建一個目錄Mizux.Foo , 並將Mizux.Foo.csproj.in復制到Mizux.Foo/Mizux.Foo.csproj
###OUTPUT 用於檢查復制完成的文件

add_custom_target(dotnet_package ALL   ###ALL : 該目標每次都要build
  DEPENDS
  dotnet_native_package
  dotnet/${DOTNET}/${DOTNET}.csproj
  COMMAND ${DOTNET_EXECUTABLE} build -c Release ${DOTNET}/${DOTNET}.csproj
  COMMAND ${DOTNET_EXECUTABLE} pack -c Release ${DOTNET}/${DOTNET}.csproj
  WORKING_DIRECTORY dotnet)

######################
##  Mizux.FooTests  ##
######################
add_custom_command(
  OUTPUT dotnet/${DOTNET}Tests/FooTests.cs
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET}Tests
  COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/dotnet/FooTests.cs ${DOTNET}Tests/FooTests.cs
  WORKING_DIRECTORY dotnet)

configure_file(
  ${PROJECT_SOURCE_DIR}/dotnet/${DOTNET}Tests.csproj.in
  ${PROJECT_BINARY_DIR}/dotnet/${DOTNET}Tests.csproj.in
  @ONLY)

add_custom_command(
  OUTPUT dotnet/${DOTNET}Tests/${DOTNET}Tests.csproj
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET}Tests
  COMMAND ${CMAKE_COMMAND} -E copy ./${DOTNET}Tests.csproj.in ${DOTNET}Tests/${DOTNET}Tests.csproj
  WORKING_DIRECTORY dotnet)

add_custom_target(FooTests ALL
  DEPENDS
  dotnet_package
  dotnet/${DOTNET}Tests/FooTests.cs
  dotnet/${DOTNET}Tests/${DOTNET}Tests.csproj
  COMMAND ${DOTNET_EXECUTABLE} build -c Release ${DOTNET}Tests/${DOTNET}Tests.csproj
  WORKING_DIRECTORY dotnet)

if(BUILD_TESTING)
  add_test(NAME FooTestsUT
    COMMAND ${DOTNET_EXECUTABLE} test -c Release ${DOTNET}Tests/${DOTNET}Tests.csproj
    WORKING_DIRECTORY dotnet)
endif()

####################
##  Mizux.FooApp  ##
####################
add_custom_command(
  OUTPUT dotnet/${DOTNET}App/FooApp.cs
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET}App
  COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/dotnet/FooApp.cs ${DOTNET}App/FooApp.cs
  WORKING_DIRECTORY dotnet)  
###復制dotnet/FooApp.cs到 build/Mizux.FooApp/FooApp.cs
###PROJECT_SOURCE_DIR = /home/xian/code/dotnet-native

configure_file(
  ${PROJECT_SOURCE_DIR}/dotnet/${DOTNET}App.csproj.in
  ${PROJECT_BINARY_DIR}/dotnet/${DOTNET}App.csproj.in
  @ONLY)
###替換dotnet/Mizux.FooApp.csproj.in中的@xxx@部分,並保存到build/dotnet/Mizux.FooApp.csproj.in

add_custom_command(
  OUTPUT dotnet/${DOTNET}App/${DOTNET}App.csproj
  COMMAND ${CMAKE_COMMAND} -E make_directory ${DOTNET}App
  COMMAND ${CMAKE_COMMAND} -E copy ./${DOTNET}App.csproj.in ${DOTNET}App/${DOTNET}App.csproj
  WORKING_DIRECTORY dotnet)
###復制build/dotnet/FooApp.csproj.in到 build/dotnet/Mizux.FooApp/Mizux.FooApp.csproj
add_custom_target(FooApp ALL
  DEPENDS
  dotnet_package
  dotnet/${DOTNET}App/FooApp.cs
  dotnet/${DOTNET}App/${DOTNET}App.csproj
  COMMAND ${DOTNET_EXECUTABLE} build -c Release ${DOTNET}App/${DOTNET}App.csproj
    ###這里沒有打包
  WORKING_DIRECTORY dotnet)

if(BUILD_TESTING)
  add_test(NAME FooAppUT
    COMMAND ${DOTNET_EXECUTABLE} run -c Release --project ${DOTNET}App/${DOTNET}App.csproj
    WORKING_DIRECTORY dotnet)
endif()

3.5 Foo/dotnet/CMakeLists.txt

set_property(SOURCE foo.i PROPERTY CPLUSPLUS ON)
  ###Call SWIG in c++ mode 參考 https://cmake.org/cmake/help/v3.14/module/UseSWIG.html
set_property(SOURCE foo.i PROPERTY COMPILE_OPTIONS
  ### 編譯foo.i時,用COMPILE_OPTIONS中的編譯選項
  -namespace ${DOTNET}
  -dllimport mizux-foo-native) ###foo.i文件中導入mizux-foo-native
swig_add_library(dotnet_Foo
  TYPE OBJECT
  LANGUAGE csharp
  OUTPUT_DIR ${PROJECT_BINARY_DIR}/dotnet/${PROJECT_NAME}/Foo
  SOURCES       foo.i)
###swig_add_library的作用就是將.i文件翻譯成.cxx文件。
###翻譯后的文件為 /home/xian/code/dotnet-native/build/dotnet/Foo/Foo/fooCSHARP_wrap.cxx
###上面 -namespace ${DOTNET}的作用就是翻譯foo.i中的函數名時加入namespace指定的前綴。
###比如如果-namespace ABCD.E.F.G 則FooHello(int);被翻譯為CSharp_ABCDfEfFfG_FooHello__SWIG_0___(int jarg1)
###並且相對應的C#文件 /home/xian/code/dotnet-native/build/dotnet/Foo/Foo/csFoo.cs 
###                /home/xian/code/dotnet-native/build/dotnet/Foo/Foo/csFoo.cs 也會有namspace:
### namespace ABCD.E.F.G {
### }

###swig_add_library實際上是執行了一行命令,輸入.i文件,輸出4個文件,這個命令可以在
###build/Foo/dotnet/CMakeFiles/dotnet_Foo_swig_compilation.dir/build.make
###中找到,
###/home/xian/.local/lib/python3.6/site-packages/cmake/data/bin/cmake -E env SWIG_LIB=/usr/local/share/swig/4.0.1 /usr/local/bin/swig -csharp -DSWIGWORDSIZE64 -I/home/xian/code/dotnet-native/dotnet -I/home/xian/code/dotnet-native/Foo/include -namespace Mizux.Foo -dllimport mizux-foo-native -outdir /home/xian/code/dotnet-native/build/dotnet/Foo/Foo -c++ -o /home/xian/code/dotnet-native/build/dotnet/Foo/Foo/fooCSHARP_wrap.cxx /home/xian/code/dotnet-native/Foo/dotnet/foo.i
###生成的4個文件是
###  xian@chaos:~/code/dotnet-native$ ls /home/xian/code/dotnet-native/build/dotnet/Foo/Foo
###  csFoo.cs  csFooPINVOKE.cs  Foo.cs  fooCSHARP_wrap.cxx
### 現在理解以下-dllimport mizux-foo-native做了什么,如果上面命令去掉-dllimport mizux-foo-native,則csFooPINVOKE.cs會少幾行,其中一行是
###  [global::System.Runtime.InteropServices.DllImport("mizux-foo-native", EntryPoint="CSharp_MizuxfFoo_FooHello__SWIG_0___")] 
###  public static extern void FooHello__SWIG_0(int jarg1);    #這一行不少,少上面一行
### 也就是導入mizux-foo-native中的CSharp_MizuxfFoo_FooHello__SWIG_0___函數,如果沒有-dllimport mizux-foo-native 那么foo.i中的函數就沒有實現
### 讓我不解的是,mizux-foo-native依賴於dotnet_Foo,而dotnet_Foo依賴於foo.i,foo.i又依賴mizux-foo-native,這不是一個循環嗎?

set_target_properties(dotnet_Foo PROPERTIES
  SWIG_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/dotnet
    ###include  dotnet/*
  SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON 
    ###???
  POSITION_INDEPENDENT_CODE ON) 
target_link_libraries(dotnet_Foo PRIVATE ${PROJECT_NAME}::Foo)

總結一下依賴關系。
Foo/CMakeLists.txt + Foo.cpp --> Foo.so
Foo/dotnet/CMakeLists.txt + foo.i + Foo.so --> dotnet_Foo.dir (這里需要swig)
dotnet_Foo.dir --> mizux-foo-native.so
mizux-foo-native.so --> dotnet_native_package (Mizux.Foo.runtime. )
dotnet_native_package --> dotnet_package (Mizux.Foo)
dotnet_package + FooApp.cs --> FooApp

4 dotnet項目組織代碼分析

4.1 dotnet/Directory.build.props

設置了一些公共屬性。

<?xml version="1.0" encoding="utf-8"?>
<Project>
  <!-- These settings are applied to all projects recursive from here (unless overridden).-->
  <PropertyGroup>
      <GenerateDocumentationFile>true</GenerateDocumentationFile>

      <!-- Nuget Properties -->
      <MinClientVersion>4.1.0</MinClientVersion>
      <NeutralLanguage>en-US</NeutralLanguage>
      <Authors>Mizux</Authors>
      <Company>Google LLC</Company>
      <Copyright>Copyright 2020 Google LLC</Copyright>

       <!-- Pack Option -->
       <PackageTags>native;sample</PackageTags>
       <PackageIcon>content/logo.png</PackageIcon>
       <PackageProjectUrl>https://github.com/mizux/dotnet-native</PackageProjectUrl>
       <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
       <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
       <PackageOutputPath>../packages</PackageOutputPath>
       <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
       <RepositoryType>git</RepositoryType>
       <RepositoryUrl>https://github.com/mizux/dotnet-native</RepositoryUrl>
        <!--Disable CS1591 "Missing XML comment for publicly visible type or member" (Swig doesn't generate documentation)-->
        <NoWarn>1591</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <None Include="../logo.png">
      <Pack>True</Pack>
      <PackagePath>content</PackagePath>
    </None>
  </ItemGroup>
  <PropertyGroup Label="BasePath">
    <SolutionDir Condition="'$(SolutionDir)'==''">$(MSBuildThisFileDirectory)</SolutionDir>
       <!-- 如果SolutionDir沒有設置或者為'',則SolutionDir = 含有該文件的目錄 -->
  </PropertyGroup>
</Project>

4.2 dotnet/Mizux.Foo.runtime.csproj.in

該文件由cmake/dotnet.cmake中的

${DOTNET_EXECUTABLE} build -c Release ${DOTNET_NATIVE}/${DOTNET_NATIVE}.csproj

dotnet build -c Release Mizux.Foo.runtime.linux-x64/Mizux.Foo.runtime.linux-x64.csproj

在目錄 build/dotnet 中執行

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks> 
       <!-- 生成兩種標准的lib -->
    <RuntimeIdentifier>@RUNTIME_IDENTIFIER@</RuntimeIdentifier>
       <!-- 這個在cmake/dotnet.cmake里面定義,@RUNTIME_IDENTIFIER@ = linux-x64 or win-x64 -->
    <AssemblyName>mizux-foo-native</AssemblyName>
       <!-- 如果改成mizux-foo-nativeASDF,則
       <!-- xian@chaos:~/code/dotnet-native$ ls build/dotnet/Mizux.Foo.runtime.linux-x64/bin/Release/netstandard2.1/linux-x64/  -->
       <!-- libFoo.so.1.0                   mizux-foo-nativeASDF.dll        mizux-foo-nativeASDF.xml                            -->
       <!-- mizux-foo-nativeASDF.deps.json  mizux-foo-nativeASDF.pdb        mizux-foo-native.so                                 -->
    <Version>@PROJECT_VERSION@</Version>
       <!-- @PROJECT_VERSION@ = 1.0 在根目錄的CMakeLists.txt中定義 project(Foo VERSION 1.0 LANGUAGES CXX)-->
    <!-- Nuget Properties -->
    <Description>.NET native wrapper for the Foo C++ project</Description>
    <IncludeBuildOutput>false</IncludeBuildOutput>
       <!-- always false-->

    <!-- Pack Option -->
    <Title>@DOTNET@ @RUNTIME_IDENTIFIER@ v@PROJECT_VERSION@</Title>
    <PackageId>@DOTNET_NATIVE@</PackageId>

    <!-- Signing -->
    <SignAssembly>false</SignAssembly>
    <PublicSign>false</PublicSign>
    <DelaySign>false</DelaySign>
  </PropertyGroup>

  <ItemGroup>
    <!-- Native library must be in native directory... -->
    <Content Include="$<TARGET_FILE:Foo>">
         <!-- $<TARGET_FILE:Foo> = /home/xian/code/dotnet-native/build/lib/libFoo.so.1.0 -->
      <PackagePath>runtimes/@RUNTIME_IDENTIFIER@/native/%(Filename)%(Extension)</PackagePath>
         <!-- runtimes/linux-x64/native/%(Filename)%(Extension) -->
      <Pack>true</Pack>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="$<TARGET_FILE:mizux-foo-native>">
         <!-- $<TARGET_FILE:mizux-foo-native> = /home/xian/code/dotnet-native/build/lib/mizux-foo-native.so -->
      <PackagePath>runtimes/@RUNTIME_IDENTIFIER@/native/%(Filename)%(Extension)</PackagePath>
         <!-- runtimes/linux-x64/native/%(Filename)%(Extension) -->
      <Pack>true</Pack>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM