C++中插件使用举例

2023-11-11

插件并不是在构建时链接的,而是在运行时发现并加载的。因此,用户可以利用你定义好的插件API来编写自己的插件。这样他们就能以指定方式扩展API的功能。插件库是一个动态库,它可以独立于核心API编译,在运行时根据需要显示加载。不过插件也可以使用静态库,比如在嵌入式系统中,所有插件都是在编译时静态链接到应用程序中的

你总是可以引入自己的插件文件扩展名。例如,Adobe Illustrator使用的插件扩展名是.aip,而Microsoft Excel插件的扩展名是.xll。

很多商业化软件包都允许使用C/C++插件扩展其核心功能。在API中采样插件模型有以下优点:

(1).更为通用:使你的API可以用于解决更大范围内的问题,而不需要为所有问题提供解决方案。

(2).更新量小:以插件形式存在的功能很容易独立于应用程序而更新,只需引入新版本的插件即可。相比发布整个应用程序的新版本,这种方式的更新量要小得多。

一般而言,如果要创建插件系统,有两个主要特性是必须要设计的:

(1).插件API:要创建插件,用户必须编译并链接插件API。插件API是你提供给用户的用于创建插件的接口。

(2).插件管理器:这是核心API代码中的一个对象(一般设计为单例),负责管理所有插件的生命周期,即插件的加载、注册和卸载等各个阶段。该对象也叫做插件注册表。

当为API设计插件时,一些设计决策会影响插件系统的精确架构:

(1).C还是C++:C++规范并没有明确地定义ABI。因此,不同的编译器,甚至同一编译器的不同版本,生成的代码可能无法做到二进制兼容。这就暗示我们,对插件系统而言,如果客户在开发插件时使用了一个ABI不同的编译器,那么这样的插件可能无法加载。相反,纯C代码的ABI有明确的含义,可以跨平台和跨编译器工作。

(2).版本控制:需要确定某插件构建时使用的API版本是否与你的API版本兼容。

(3).内部元数据还是外部元数据:元数据,比如可读的名字和版本信息,既可以在插件代码内部定义,也可以通过一种简单的外部文件格式指定。使用外部元数据的优点是,并不需要加载所有插件,就能了解所有可用对象的集合。

(4).插件管理器是通用的还是专用的:插件管理器的一种实现方法是,使它的层次非常低,实现通用性,也就是说,它只是简单地加载插件并访问其中的符号。然而,这样做意味着插件管理器不了解API中是否存在具体类型。其结果是对象可能必须以void*指针的形式返回,在使用之前再转化为具体的类型。或者,插件管理器可以以最低限度前向声明插件中任何对象的类型,这种方案的类型安全性更好,但也正因如此,它无法独立于你的API而实现。

(5).安全性:你必须决定你对用户插件的信任程度。插件是可以运行在进程之中的任意编译过的代码。因此,插件有可能做任何事情,包括访问它不应该访问的数据,以及删除最终用户硬盘上的文件,甚至让整个应用程序奔溃。如果你需要防护这种恶意插件,可以考虑创建一种基于套接字的方案,使插件运行在独立的进程中,通过IPC通道与核心API通信。

(6).静态库还是动态库:插件也可以定义为静态库,这意味着插件必须编译到应用程序中。对消费型应用程序而言,更常见的方案是采用动态库,因为用户可以编写自己的插件,并且在运行时扩展应用程序。编写静态插件还有一个约束,你必须确保任意两个插件中没有定义相同的符号,也就是说,每个插件中初始化函数的命名必须是唯一的。

因为跨平台和跨编译器的ABI问题,支持C++插件有些困难,有些方法可以在插件中更安全地使用C++:

(1).使用抽象基类:实现抽象基类的虚方法可以使插件与ABI问题隔离,因为虚方法调用通常是用类的虚函数表中的索引来表示的。

(2).自由函数使用C链接:为了避免C++ ABI问题,插件API中的所有全局函数都要使用C链接方式,也就是说,它们应该使用extern “C”声明。同理,为了最大化可移植性,插件传递给核心API的回调函数也应该使用C链接方式。

(3).避免使用STL和异常:STL类(如std::string和std::vector)的不同实现可能不是ABI兼容的。因此,核心API与插件API之间的函数调用应该尽量避免使用这些容器。同样,因为不同编译器之间异常的ABI往往也是不稳定的,所以在你的插件API中也应该避免。

(4).不要混用内存分配器:插件链接的内存分配器可能与你的API不同。要么所有的对象由插件分配并回收,要么将控制传给核心API,由核心API负责所有对象的创建与销毁。但核心API绝对不要释放插件分配的对象,反之亦然。

插件API:插件API是你提供给用户的用于创建插件的接口。当核心API加载一个插件时,为了让插件正常工作,它需要知道应该调用哪个函数或者要访问哪个符号。这意味着插件中应该明确定义具名的入口点,用户在创建插件时必须提供。这一点可以以不同的方式实现。

插件管理器:需要处理以下任务:

(1).加载所有插件的元数据:这些元数据既可以保存在单独的文件中(比如xml文件),也可以嵌入到插件内部。如果是后一种情况,为了收集所有插件的元数据,插件管理器需要加载所有可用的插件。你可以以元数据的形式向用户提供可用插件列表,以供他们选择。

(2).将动态库加载到内存中,提供对库中符号的访问能力,并在必要时卸载库。在Unix(也包括Mac OSX)平台上,这会涉及dlopen、dlclose、dlsym等函数,而在Windows平台上,涉及的是LoadLibrary、FreeLibrary及GetProcAddress等函数。

(3).当插件加载时,调用其初始化例程;而当插件卸载时,调用其清理例程。

因为插件管理器为系统中的所有插件提供了单一访问点,所以它往往以单例模式实现。从设计角度看,我们可以将插件管理器看做一组插件实例的集合,其中每个插件实例表示一个插件,并提供了加载和卸载该插件的功能。

插件版本控制:既可以让插件使用与核心API相同的版本号,也可以为其引入专门的插件API版本号。我建议采用后者,因为插件API实际上是从核心API分离出来的接口,两者可能以不同的频率修改。除此之外,用户可以选择指定该插件支持的API的最小版本号和最大版本号。更普遍的做法是指定最小版本号。最小/最大版本号也可以通过外部元文件格式指定。

注:以上内容摘自《C++ API设计

以下是测试代码:组织结构如下图所示:

src目录下存放所有的API和核心库code,common.hpp中的接口可认为是核心API,plugin.hpp中为插件API,编译此目录可生成动态库address。

plugin目录下存放插件API的实现,编译此目录可生成一个名字为plugin_area.fbc的插件。

tests目录为调用核心API和插件API的code,用来验证生成动态库address和插件plugin_area.fbc的正确性。

可通过配置文件如json或xml来指定需要加载的插件,在code中解析此配置文件,这样替换插件时可无需重新编译,直接修改配置文件即可。

通过脚本build.sh和CMakeLists.txt来编译测试代码,执行build.sh 0生成plugin_area.fbc插件,执行build.sh 1生成address动态库和执行文件Plugin_Test。插件和动态库/执行文件的生成是独立的,它们在编译生成时无任何依赖关系。

各个文件内容如下:

plugin/plugin_area.cpp:

#include <string.h>
#include <iostream>
#include <string>
#include <stdexcept>
#include "plugin.hpp"

class Area : public Base {
public:
	Area() = default;
	~Area() = default;

	const char* version() override { return "1.0.0"; }
	const char* name() override { return "plugin_area"; }
	int get_area(const fbc_rect_t& rect) override { return ((rect.right - rect.left) * (rect.bottom - rect.top) + 10); }
};

#ifdef __cplusplus
extern "C" {
#endif

FBC_API Base* get_plugin_instance(const char* name)
{
	Area* area = new Area();
	if (strcmp(area->name(), name) != 0) {
		fprintf(stderr, "plugin name mismatch: %s, %s\n", area->name(), name);
		delete area;
		throw std::runtime_error("plugin name mismatch");
		return nullptr;
	}

	return area;
}

FBC_API std::string get_plugin_name_version(Base* handle)
{
	if (!handle) {
		fprintf(stdout, "handle cann't equal nullptr\n");
		throw std::runtime_error("handle cann't equal nullptr");
		return "";
	}

	Area* area = dynamic_cast<Area*>(handle);
	std::string str(area->name());
	str += ".fbc.";
	str += area->version();
	return str;
}

FBC_API void release_plugin_instance(Base* handle)
{
	delete dynamic_cast<Area*>(handle);
}

#ifdef __cplusplus
}
#endif 

src/common.hpp:

#ifndef FBC_PLUGIN_TEST_COMMON_HPP_
#define FBC_PLUGIN_TEST_COMMON_HPP_

#ifdef _MSC_VER
    #ifdef DLL_EXPORTS
        #define FBC_API __declspec(dllexport)
    #else
        #define FBC_API
    #endif // _MSC_VER
#else
    #ifdef DLL_EXPORTS
        #define FBC_API __attribute__((visibility("default")))
    #else
        #define FBC_API
    #endif
#endif

typedef struct fbc_rect_t {
    int left, top;
    int right, bottom;
} fbc_rect_t;

FBC_API char* get_csdn_blog_address();
FBC_API char* get_github_address();

#endif // FBC_PLUGIN_TEST_COMMON_HPP_

src/common.cpp:

#include "common.hpp"

FBC_API char* get_csdn_blog_address()
{
	return "https://blog.csdn.net/fengbingchun";
}

FBC_API char* get_github_address()
{
	return "https://github.com//fengbingchun";
}

src/plugin.hpp:

#ifndef FBC_PLUGIN_TEST_PLUGIN_HPP_
#define FBC_PLUGIN_TEST_PLUGIN_HPP_

#include "common.hpp"

class Base {
public:
	virtual const char* version() = 0;
	virtual const char* name() = 0;
	virtual int get_area(const fbc_rect_t& rect) = 0;

	virtual ~Base() = default;
};

#ifdef __cplusplus
extern "C" {
#endif

FBC_API Base* get_plugin_instance(const char* name);
FBC_API std::string get_plugin_name_version(Base* handle);
FBC_API void release_plugin_instance(Base* handle);

#ifdef __cplusplus
}
#endif 

#endif // FBC_PLUGIN_TEST_PLUGIN_HPP_

test/test.cpp:

#include <iostream>
#include <string>
#include <stdexcept>
#ifdef _MSC_VER
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include "common.hpp"
#include "plugin.hpp"

int main()
{
    // test general dynamic library
	fprintf(stdout, "csdn blog address: %s\n", get_csdn_blog_address());
	fprintf(stdout, "github address: %s\n", get_github_address());

    // test plugin
	const std::string plugin_name {"plugin_area"}, plugin_suffix {"fbc"};
	fbc_rect_t rect = {1, 2, 31, 52};

#ifdef _MSC_VER
    HINSTANCE handle = LoadLibrary((plugin_name+"."+plugin_suffix).c_str());
    if (!handle) {
        fprintf(stderr, "fail to load plugin: %s, %d\n", plugin_name.c_str(), GetLastError());
		return -1;
    }

	typedef Base* (*LPGETINSTANCE)(const char* name);
	LPGETINSTANCE lpGetInstance = (LPGETINSTANCE)GetProcAddress(handle, "get_plugin_instance");
	if (!lpGetInstance) {
		fprintf(stderr, "fail to GetProcAddress: get_plugin_instance, %d\n", GetLastError());
		return -1;
	}

	Base* instance = nullptr;
	try {
		instance = (*lpGetInstance)(plugin_name.c_str());
		fprintf(stdout, "plugin name: %s, version: %s\n", instance->name(), instance->version());
	} catch (const std::exception& e) {
		fprintf(stderr, "exception: %s\ntest fail\n", e.what());
		return -1;
	}

	fprintf(stdout, "area: %d\n", instance->get_area(rect));

	typedef std::string (*LPVERSIONNAME)(Base* base);
	LPVERSIONNAME lpVersionName = (LPVERSIONNAME)GetProcAddress(handle, "get_plugin_name_version");
	if (!lpVersionName) {
		fprintf(stderr, "fail to GetProcAddress: get_plugin_name_version, %d\n", GetLastError());
		return -1;
	}

	try {
		fprintf(stdout, "plugin name version: %s\n", (*lpVersionName)(instance).c_str());
	} catch (const std::exception& e) {
		fprintf(stderr, "exception: %s\ntest fail\n", e.what());
		return -1;
	}

	typedef void (*LPRELEASEINSTANCE)(Base* base);
	LPRELEASEINSTANCE lpReleaseInstance = (LPRELEASEINSTANCE)GetProcAddress(handle, "release_plugin_instance");
	if (!lpReleaseInstance) {
		fprintf(stderr, "fail to GetProcAddress: release_plugin_instance, %d\n", GetLastError());
		return -1;
	}
	fprintf(stdout, "destroy Base\n");
	(*lpReleaseInstance)(instance);

	FreeLibrary(handle);
#else
	void* handle = dlopen((plugin_name+"."+plugin_suffix).c_str(), RTLD_LAZY);
	if (!handle) {
		fprintf(stderr, "fail to load plugin: %s\n", plugin_name.c_str());
		return -1;
	}

	typedef Base* (*pGetInstance)(const char* name);
	pGetInstance pInstance = (pGetInstance)dlsym(handle, "get_plugin_instance");
	if (!pInstance) {
		fprintf(stderr, "fail to dlsym: get_plugin_instance\n");
		return -1;
	}

	Base* instance = nullptr;
	try {
		instance = (*pInstance)(plugin_name.c_str());
		fprintf(stdout, "plugin name: %s, version: %s\n", instance->name(), instance->version());
	} catch (const std::exception& e) {
		fprintf(stderr, "exception: %s\ntest fail\n", e.what());
		return -1;
	}

	fprintf(stdout, "area: %d\n", instance->get_area(rect));

	typedef std::string (*pVersionName)(Base* base);
	pVersionName pvername = (pVersionName)dlsym(handle, "get_plugin_name_version");
	if (!pvername) {
		fprintf(stderr, "fail to dlsym: get_plugin_name_version\n");
		return -1;
	}

	try {
		fprintf(stdout, "plugin name version: %s\n", (*pvername)(instance).c_str());
	} catch (const std::exception& e) {
		fprintf(stderr, "exception: %s\ntest fail\n", e.what());
		return -1;
	}

	typedef void (*pReleaseInstance)(Base* base);
	pReleaseInstance prelins = (pReleaseInstance)dlsym(handle, "release_plugin_instance");
	if (!prelins) {
		fprintf(stderr, "fail to dlsym: release_plugin_instance\n");
		return -1;
	}
	fprintf(stdout, "destroy Base\n");
	(*prelins)(instance);

	dlclose(handle);
#endif

	fprintf(stdout, "test finish\n");
	return 0;
}

build.sh:

#! /bin/bash

usage() {
    echo "usage: $0 param"
    echo "if build plugin, then execute: $0 0"
    echo "if build src and test, then execute: $0 1"
    exit -1
}

if [ $# != 1 ]; then
    usage
fi

real_path=$(realpath $0)
echo "real_path: ${real_path}"
dir_name=`dirname "${real_path}"`
echo "dir_name: ${dir_name}"

build_dir=${dir_name}/build
mkdir -p ${build_dir}
cd ${build_dir}
if [ "$(ls -A ${build_dir})" ]; then
	echo "directory is not empty: ${build_dir}"
else
	echo "directory is empty: ${build_dir}"
fi

platform=`uname`
echo "##### current platform: ${platform}"

if [ ${platform} == "Linux" ]; then
    if [ $1 == 0 ]; then
        echo "########## build plugin ##########"
        cmake -DBUILD_PLUGIN=ON ..
    elif [ $1 == 1 ]; then
        echo "########## build src and test ##########"
        cmake -DBUILD_PLUGIN=OFF ..
    else
        usage
    fi

    make
else
    if [ $1 == 0 ]; then
        echo "########## build plugin ##########"
        cmake -G"Visual Studio 15 2017" -A x64 -DBUILD_PLUGIN=ON ..
    elif [ $1 == 1 ]; then
        echo "########## build src and test ##########"
        cmake -G"Visual Studio 15 2017" -A x64 -DBUILD_PLUGIN=OFF ..
    else
        usage
    fi

    cmake --build . --target ALL_BUILD --config Release
fi

cd -

CMakeLists.txt:

PROJECT(Plugin_Test)
CMAKE_MINIMUM_REQUIRED(VERSION 3.9)

SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=gnu++0x")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -std=gnu++0x")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O2 -std=gnu++0x")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x -Wall -O2")

SET(PATH_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/./../../../demo/Plugin_Test/demo1/test)
SET(PATH_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/./../../../demo/Plugin_Test/demo1/src)
SET(PATH_PLUGIN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/./../../../demo/Plugin_Test/demo1/plugin)

INCLUDE_DIRECTORIES(${PATH_SRC_DIR})

FILE(GLOB_RECURSE PLUGIN_CPP_LIST ${PATH_PLUGIN_DIR}/*.cpp)
FILE(GLOB_RECURSE SRC_CPP_LIST ${PATH_SRC_DIR}/*.cpp)
FILE(GLOB_RECURSE TEST_CPP_LIST ${PATH_TEST_DIR}/*.cpp)

ADD_DEFINITIONS(-DDLL_EXPORTS)

IF(BUILD_PLUGIN)
	MESSAGE(STATUS "########## BUILD PLUGIN ##########")
    ADD_LIBRARY(plugin_area SHARED ${PLUGIN_CPP_LIST})
    SET_TARGET_PROPERTIES(plugin_area PROPERTIES PREFIX "" SUFFIX ".fbc")
ELSE()
	MESSAGE(STATUS "########## BUILD SRC AND TEST ##########")
    #SET(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) # can generate address.lib in windows
    ADD_LIBRARY(address SHARED ${SRC_CPP_LIST})  

    ADD_EXECUTABLE(Plugin_Test ${TEST_CPP_LIST})
    IF(WIN32)
        TARGET_LINK_LIBRARIES(Plugin_Test address)
    ELSE()
        TARGET_LINK_LIBRARIES(Plugin_Test address dl)
    ENDIF()
ENDIF()

此测试代码可同时在Windows和Linux下执行。

在Windows下执行结果如下:

在Linux下执行结果如下:

GitHubhttps://github.com/fengbingchun/Messy_Test

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中插件使用举例 的相关文章

  • 有没有办法让我简化这些回声? [复制]

    这个问题在这里已经有答案了 我仍在学习如何编写 shell 脚本 并且我面临着一个挑战 让我更容易回显 Name1 Name2 Name15 我不太确定从哪里开始 我已经想法 但如果我搞砸了 我不想看起来很傻 有什么帮助吗 我实际上还没有尝
  • sed 错误“未终止的 's' 命令”故障排除

    我正在构建一个script https stackoverflow com questions 4036832 replacing a specific term in an xml file其中 它将用文件夹路径替换 XML 文件中的模式
  • 从 shell 命令调用 SOAP 请求

    我使用curl 向Web 服务发送SOAP 请求 并使用shell 脚本获取响应 请在下面找到我正在使用的命令 curl H Content Type text xml charset utf 8 H SOAPAction d sample
  • 如何查看正在运行的 tcsh 版本?

    如何查看我的 UNIX 终端中运行的 tcsh 的当前版本 看着那 这version多变的 echo version tcsh 6 14 00 Astron 2005 03 25 i386 intel linux options wide
  • sh / Bash shell 脚本中 !# (bang-pound) 的含义是什么?

    我想了解这个 Scala 脚本是如何工作的 usr bin env bash exec scala 0 object HelloWorld def main args Array String println Hello world arg
  • 在退出脚本之前等待后台进程完成

    在退出脚本 TCL Bash 之前 如何确保所有后台进程已完成执行 我正在考虑将所有后台进程 pid 写入 pid 文件 然后最后 pgrep pidfile 以查看在退出之前是否有任何进程仍在运行 有一些更简单的方法可以做到这一点吗 TC
  • 在 shell 脚本中将一个子字符串替换为另一个字符串

    我有 我爱苏子并结婚 我想将 苏子 更改为 萨拉 firstString I love Suzi and Marry secondString Sara 期望的结果 firstString I love Sara and Marry 要更换
  • 每个命令都返回“bash:<命令>:找不到命令...”[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我刚刚安装了 Scala 并添加了路径gedit bashrc export SCALA HOME home avijit sca
  • shell中如何从数组中随机选择一个项目

    我正在 Shell 脚本中创建一个机器人 Array with expressions expressions Ploink Poink I Need Oil Some Bytes are Missing Poink Poink Piiii
  • 无法使用 Java ProcessBuilder 启动带参数的 shell 脚本

    我正在尝试使用 ProcessBuilder 执行带有命令行参数的 shell 脚本 该 shell 脚本又调用使用此参数的其他两个 shell 脚本 第一个 shell 脚本运行良好 但当第二个 shell 脚本启动时 它返回退出代码 1
  • 在 UNIX 中删除相同的文件

    我正在处理大量 30 000 个 大小约为 10MB 的文件 其中一些 我估计 2 实际上是重复的 我只需要为每个重复的对 或三元组 保留一个副本 您能建议我一种有效的方法吗 我正在unix上工作 您可以尝试使用此代码片段在删除之前先获取所
  • shell中如何求数组的长度?

    shell中如何求数组的长度 例如 arr 1 2 3 4 5 我想得到它的长度 在本例中是 5 a 1 2 3 4 echo a 4
  • 检查 Bash 数组中是否存在元素[重复]

    这个问题在这里已经有答案了 我想知道是否有一种有效的方法来检查 Bash 数组中是否存在元素 我正在寻找类似于我可以在Python中做的事情 例如 arr a b c d if d in arr do your thing else do
  • 如何列出 nginx 中的所有虚拟主机

    有没有一个命令可以列出 CentOS 上 nginx 下运行的所有虚拟主机或服务器 我想将结果通过管道传输到文本文件以用于报告目的 我正在寻找与我用于 Apache 的命令类似的命令 apachectl S 2 gt 1 grep 端口 8
  • 如何使用 bash 中提供的工具生成一系列非周末日期?

    我想生成一个文件列表 其中名称包含 filename date 例如file 20111101 file 20120703 开始November 1 2011直到今天 应该不包括周末 Thanks 2011年试试这个 for y in 20
  • Grep 递归和计数

    需要在具有大量子目录的目录中搜索文件内的字符串 我在用着 grep c r string here 我怎样才能找到总数量 如何仅输出至少具有一个实例的文件 使用 Bash 的进程替换 这给出了我认为是您想要的输出 如果不是 请澄清问题 gr
  • 在 Django shell 会话期间获取 SQL 查询计数

    有没有办法打印 Django ORM 在 Django shell 会话期间执行的原始 SQL 查询的数量 Django 调试工具栏已经提供了此类信息 例如 5 QUERIES in 5 83MS但如何从 shell 中获取它并不明显 您可
  • tcsh 脚本 if 语句

    我需要循环遍历一堆不同的场景 变量场景 但无法弄清楚如何在 tcsh shell 脚本中使用 if 语句 收到错误 if 表达式语法 有人可以告诉我我有什么问题吗 简化代码如下 谢谢 bin tcsh f set val 0 foreach
  • 是否可以从应用程序执行 ADB shell 命令?

    我有一个安卓电脑 http www timingpower com rk3288 with root 开箱即用 连接到始终以横向显示的外部显示器 HDMI 和 USB 即使我的应用程序在清单中的活动声明中指定纵向 android scree
  • 将 apache documentRoot 设置为符号链接(以便于部署)

    我们正在寻找一种将 Apache DocumentRoot 指向符号链接的方法 例如 文档根目录 var www html finalbuild Finalbuild 应该指向 home user build3 之类的文件夹 当我们将新构建

随机推荐

  • Unity开发(2)建片草地

    文章目录 1 简述 2 创建 2 1 创建项目 2 2 进入开发窗体 3 建个地面 3 1 新建地面 3 2 调整地面大小 3 3 添加草地 3 3 1 初识Unity图片资源 3 3 2 添加图片资源 3 3 3 修改图片在场景中大小 1
  • C语言入门知识1(零基础新手适用)

    C语言入门知识1 零基础新手适用 程序语言 1 机器语言 机器语言是低级语言 是用01码来编写的二进制代码语言 2 汇编语言 汇编语言也是低级语言 是用英文字母和符号串编写的 3 高级语言 由于汇编语言依赖于硬件体系且符合较多 为了方便高级
  • Go中 defer的使用

    文章目录 简介 示例 使用场景 捕获异常 文件操作 简介 defer 是 Golang 中的一个非常有用的关键字 它用于注册延迟调用 也就是一个函数的执行被延迟到调用它的函数返回之后 常用于资源清理 异常处理等场景 示例 defer 是注册
  • python实现电子邮件编程

    一 几个专业名词 MUA MTA MDA 假设我们自己的电子邮件地址是me 163 com 对方的电子邮件地址是friend sina com 注意地址都是虚构的哈 现在我们用Outlook或者Foxmail之类的软件写好邮件 填上对方的E
  • C++提高8: 类模板成员函数类外实现和类模板分文件编写

    1 类模板成员函数类外实现 类外实现主要有三个关键点 作用域 识别T的数据类型 告诉编译器这是一个类模板 剩下的 就还是基础的类内声明类外定义实现了 直接上代码观察一下 include
  • redis后台实现投票功能

    原创文章 转载请注明出处https blog csdn net qq 41969845 article details 108406059 一 前言 本文以投票功能为例 从实际例子中熟练掌握redis的应用 阅读本文需要有一定的Java基础
  • SparkStreaming与Kafka010之05之01 Consumer

    package Kafka010 import Kafka010 Utils MyKafkaUtils import org apache kafka clients consumer ConsumerRecord import org a
  • 常用网络数据帧格式

    常用网络数据帧格式 1 ARP帧格式 2 ICMP帧格式 3 UDP帧格式 4 TCP帧格式 本文主要介绍ARP ICMP UDP TCP等常用网络数据帧格式 1 ARP帧格式 当一个应用层的数据在网络中传输时 会被逐步封装成链路层的帧 而
  • ffplay源码解析-main入口函数

    main入口函数 初始化 变量 缓存区 SDL窗口初始化等 int main int argc char argv int flags VideoState is av log set level AV LOG TRACE init dyn
  • L1-086 斯德哥尔摩火车上的题(15分) Python

    上图是新浪微博上的一则趣闻 是瑞典斯德哥尔摩火车上的一道题 看上去是段伪代码 s a 1112031584 for i 1 i lt length a i if a i 2 a i 1 2 s max a i a i 1 goto url
  • 2020-11-24-ElasticSearch7.x学习笔记

    笔记记录 B站狂神说Java的ElasticSearch课程 https www bilibili com video BV17a4y1x7zq 在学习ElasticSearch之前 先简单了解一下Lucene Doug Cutting开发
  • 根据PV或者QPS来计算需要多少台机器

    QPS 单个进程每秒请求服务器成功的次数 req sec 总请求数 进程总数 请求时间 一般使用http load进行统计 每台服务器每天的PV QPS x 3600 x 6 或者乘以8小时 一天按照6或者8小时计算 晚上可能没人访问 服务
  • Conda环境 下载Jupyter Lab并使用

    1 下载Jupyter Lab conda 安装方式 conda install jupyterlab conda install c conda forge jupyterlab python 安装方式 pip install jupyt
  • python waitress_python 角度理解web服务器

    概述 web服务器实际上就是一个运行在物理机上的网络服务器 它等待客户端给他发送请求 成功接收后将客户端请求的资源响应给它 客户端与服务端的通信通过http协议实现 客户端可以是浏览器或者可以发送请求的一段程序 一 一个简单的web服务器
  • Android11 热点设置永不关闭

    Android11 热点设置永不关闭 文章目录 Android11 热点设置永不关闭 一 前言 二 framework设置热点永不超时关闭 三 基于 SoftApManager java 研究超时逻辑 三 总结 1 设置热点不关闭的方法 1
  • cutlass入门: 调用cutlass做通用矩阵乘法Gemm(附代码)

    cutlass是CUDA C 模板抽象的集合 用于实现CUDA中所有级别和规模的高性能矩阵乘法 GEMM 和相关计算 相较于cuBLAS和cuDNN cutlass中包含了更多可重用的模块化软件组件 这使得cutlass相较于前两者更为灵活
  • 详细介绍InnoDB数据存储结构

    InnoDB数据存储结构 1 数据库的存储结构 页 索引结构给我们提供了高效的索引方式 不过索引信息以及数据记录都是保存在文件上的 确切说是存储在页结构中 另一方面 索引是在存储引擎中实现的 MySQL服务器上的存储引繁负责对表中数据的读取
  • 接口测试简介以及接口测试用例设计思路

    接口测试简介 1 什么是接口 接口就是内部模块对模块 外部系统对其他服务提供的一种可调用或者连接的能力的标准 就好比usb接口 他是系统向外接提供的一种用于物理数据传输的一个接口 当然仅仅是一个接口是不能进行传输的 我们还的对这个接口怎么进
  • OpenCV读取图像_显示图像和保存图像

    配置好 OpenCV 以后 包含以下两个头文件 include cv h include highgui h IplImage image cvLoadImage D 123 jpg 1 函数cvLoadImage 的第1 个参数是图像文件
  • C++中插件使用举例

    插件并不是在构建时链接的 而是在运行时发现并加载的 因此 用户可以利用你定义好的插件API来编写自己的插件 这样他们就能以指定方式扩展API的功能 插件库是一个动态库 它可以独立于核心API编译 在运行时根据需要显示加载 不过插件也可以使用