C++中插件使用举例
插件并不是在构建时链接的,而是在运行时发现并加载的。因此,用户可以利用你定义好的插件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" {
#endifFBC_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
#endiftypedef 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" {
#endifFBC_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 libraryfprintf(stdout, "csdn blog address: %s\n", get_csdn_blog_address());fprintf(stdout, "github address: %s\n", get_github_address());// test pluginconst std::string plugin_name {"plugin_area"}, plugin_suffix {"fbc"};fbc_rect_t rect = {1, 2, 31, 52};#ifdef _MSC_VERHINSTANCE 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);
#elsevoid* 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);
#endiffprintf(stdout, "test finish\n");return 0;
}
build.sh:
#! /bin/bashusage() {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 ]; thenusage
fireal_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})" ]; thenecho "directory is not empty: ${build_dir}"
elseecho "directory is empty: ${build_dir}"
fiplatform=`uname`
echo "##### current platform: ${platform}"if [ ${platform} == "Linux" ]; thenif [ $1 == 0 ]; thenecho "########## build plugin ##########"cmake -DBUILD_PLUGIN=ON ..elif [ $1 == 1 ]; thenecho "########## build src and test ##########"cmake -DBUILD_PLUGIN=OFF ..elseusagefimake
elseif [ $1 == 0 ]; thenecho "########## build plugin ##########"cmake -G"Visual Studio 15 2017" -A x64 -DBUILD_PLUGIN=ON ..elif [ $1 == 1 ]; thenecho "########## build src and test ##########"cmake -G"Visual Studio 15 2017" -A x64 -DBUILD_PLUGIN=OFF ..elseusageficmake --build . --target ALL_BUILD --config Release
ficd -
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 windowsADD_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下执行结果如下:
GitHub:https://github.com/fengbingchun/Messy_Test
相关文章:

C和C++安全编码笔记:指针诡计
指针诡计(pointer subterfuge)是通过修改指针值来利用程序漏洞的方法的统称。 可以通过覆盖函数指针将程序的控制权转移到攻击者提供的外壳代码(shellcode)。当程序通过函数指针执行一个函数调用时,攻击者提供的代码将会取代原本希望执行的代码而得到执行。 对象指…
runLoop和runtime的分析
一.RunLoop: Runloop是事件接收和分发机制的一个实现。 Runloop提供了一种异步执行代码的机制,不能并行执行任务。 在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。 (1).RunLoop的主要目的&#…
脑出血遇到深度学习,是否可以无所遁形?
近期大家对身体健康这个话题格外关注,而我们今天公开课的主题也恰巧与此不谋而合。我国脑卒的发病率已经超过心血管疾病,成为致死、致残率最高的疾病,并且发病率呈逐年上升的趋势,此外脑血管病和颅内肿瘤等脑部疾病也危害人们的健…

Cloudera Manager 5.3 和 CDH5.3.0 本地(离线)
为什么80%的码农都做不了架构师?>>> 声明一下:http://my.oschina.net/dataRunner/blog/369129 是本人所写,并非抄袭。 有部分内容来自 http://www.wangyongkui.com/hadoop-cdh5/ 这个文件是根据官网操作,翻译的不…
万字长文详解如何用Python玩转OpenGL | CSDN 博文精选
作者 | 天元浪子来源 | CSDN博文精选【编者按】OpenGL(开放式图形库),用于渲染 2D、3D 矢量图形的跨语言、跨平台的应用程序编程接口,C、C、Python、Java等语言都能支持 OpenGL。本文作者以 Python 语法为例,用两万字详…

模仿视频抓帧实现
路口或某些场所可能并不会把从摄像头获取到的视频全部存储下来或对所有的视频帧进行处理,即摄像设备是一直处于打开状态,可能会根据需要间隔性的抓取其中一帧,或当某事件触发时才会抓取当前的一帧数据进行处理。这里使用两个线程来模仿此场景…

iOS--MD5加密封装
#import <Foundation/Foundation.h> interface MD5 : NSObject /** * md5加密 * * param inPutText 需要加密的字符串 * * return 加密好的字符串 */ (NSString *)md5:(NSString *)inPutText; end #import "MD5.h" #import "CommonCrypto/CommonDiges…

Akka路由_RoundRobinRoutingLogic
2019独角兽企业重金招聘Python工程师标准>>> Akka路由_RoundRobinRoutingLogic 使用Round Robin算法的Router,代码中有注释,基本和上篇文章中的代码一样 http://my.oschina.net/xinxingegeya/blog/369721, 具体如下,关…

iOS ---网络请求封装(自动缓存与手动缓存)
#import <Foundation/Foundation.h> interface WNetworkCache : NSObject /** * 手动写入/更新缓存 * * param jsonResponse 要写入的数据 * param URL 请求URL * * return 是否写入成功 */ (BOOL)saveJsonResponseToCacheFile:(id)jsonResponse andURL:(NSStrin…

Windows下获取视频设备的一种改进实现
之前在https://blog.csdn.net/fengbingchun/article/details/102806822中介绍过在Windows下获取视频设备列表的方法。其实那种实现方法是有缺陷的,当PC机上连接多个视频设备,并且其中有设备处于启动运行状态时,再调用相关接口获取视频设备可能…
最新单步目标检测框架,引入双向网络,精度和速度均达到不错效果
作者 | Tiancai Wang等译者 | 路一直都在出品 | AI科技大本营(ID:rgznai100)one-stage的目标检测方法因其具有实时性强、检测精度高等特点,近年来受到广泛关注。目标检测包括分类和定位两个子任务,通常来说,one-stage目…

基于Sentinel的Redis3.2高可用方案
默认情况下,Redis node和sentinel的protected-mode都是yes,在搭建集群时,若想从远程连接redis集群,需要将redis.conf和sentinel.conf的protected-mode修改为no,若只修改redis node,从远程连接sentinel后&am…
从YARN迁移到k8s,滴滴机器学习平台二次开发是这样做的
整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】人工智能时代,机器学习已经渗透进每个领域,改变了这些领域的业务模式、技术架构以及方法论。随着深度学习技术近年来快速发展,高效、易用的机器学习平台对于互联…

最新 macOS Sierra 10.12.3 安装CocoaPods及使用详解
cocoapods官网:https://guides.cocoapods.org 一、什么是CocoaPods 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如 Java 语言的 Maven,nodejs 的 npm。随着 iOS 开发者的增多,业界也出现了为 iOS 程序提供…

libusb中的热插拔使用举例
以下为判断usb设备是插入还是拔出状态(热插拔)的测试代码: 在Windows下是不支持的,在Linux是支持的,下一个版本可能会支持Windows下的热插拔: #include <chrono> #include <thread> #include <iostream> #incl…

C++复制控制:拷贝构造函数
一、拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。与默认构造函数一样 ,拷贝构造函数可由编译器隐式调用。拷贝构造函数应用的场合为: (1࿰…

关于IOS获取本地通讯录信息(包含iOS9.0前后)
在ios开发当中,获取用户本地的通讯录功能愈加频繁的出现,七两自己也在自己公司的项目当中遇到的获取本地的通讯录信息的功能(俗称“种子用户功能”,太可怕了)。对此七两总结了自己使用本地通讯录时的注意点,…

C和C++安全编码笔记:动态内存管理
4.1 C内存管理: C标准内存管理函数: (1).malloc(size_t size):分配size个字节,并返回一个指向分配的内存的指针。分配的内存未被初始化为一个已知值。 (2).aligned_alloc(size_t alignment, size_t size):为一个对象…
作为一名程序员,数学到底对你有多重要?
最近在知乎上看到一个贴子,看完后我沉默了.....沉思后想想,其实每个行业都会分等级,程序员也不例外!说好听一点的叫工程师,普通一点的叫程序员,差一点的叫码农,更差的还会叫码畜,码奴…

经典SQL(sqlServer)
一、基础 1、说明:创建数据库CREATE DATABASE database-name 2、说明:删除数据库drop database dbname3、说明:备份sql server--- 创建 备份数据的 deviceUSE masterEXEC sp_addumpdevice disk, testBack, c:\mssql7backup\MyNwind_1.dat--- …

iOS UITextField输入框随键盘弹出界面上移
//点击输入框界面跟随键盘上移 - (void)textFieldDidBeginEditing:(UITextField *)textField { CGRect frame textField.frame; int offSet frame.origin.y 70 - (self.view.frame.size.height - 216.0); //iphone键盘高度为216.iped键盘高度为352 [UIView beginAnimations:…
IEEE分享 | 机器学习在领英的规模化应用
人工智能和机器学习仍然是全球持续增长的领域之一,近年来涌现出越来越多本科生或者非人工智能专业出身的工程师,他们努力学习和使用技术来改进产品,几乎每天都有新的机器学习技术和框架发布。这篇文章将讨论领英如何规模化利用技术࿰…

GitHub/GitLab/Gitee中项目互拷贝后仍保留历史提交记录的方法
GitHub、GitLab、Gitee等在同一个网站中执行复制或拷贝一个已有项目到一个新项目比较简单,因为它们在每一个项目上都有一个Fork按钮,直接点击此Fork按钮即可,Fork后的新项目会保留原有项目的历史提交记录。但是如果不在同一个网站上进行此操作…

基于mimeTex的数学公式Webservice的部署和实现
通过Latex语法,实现生成数学公式的解决方案也很多。这里介绍一种方法,使用开源的mimeTex。该项目的官网地址如下:http://www.forkosh.com/mimetex.html网站主页有一个声明。如果你的服务器上已经安装了latex,那么推荐使用mathTex&…

对称加密算法AES之GCM模式简介及在OpenSSL中使用举例
AES(Advanced Encryption Standard)即高级加密标准,由美国国家标准和技术协会(NIST)于2000年公布,它是一种对称加密算法。关于AES的更多介绍可以参考:https://blog.csdn.net/fengbingchun/article/details/100139524 AES的GCM(Galois/Counte…

iOS UITextField清空按钮
extField.clearButtonModeUITextFieldViewModeWhileEditing; 就可以了,表明编辑输入框的时候启动一键清空按钮。另外,clearButtonMode还有三个属性: UITextFieldViewModeNever, 清空按钮永不出现 UITextFieldViewModeUnlessEditing, 不编…
腾讯“疯狂”开源
作者 | 马超责编 | 胡巍巍出品 | CSDN(ID:CSDNnews)近日,腾讯自研的万亿级分布式消息中间件TubeMQ正式开源,并捐赠给Apache基金会,成为基金会官方认可的Incubator项目。我们知道与TubeMQ功能类似的kafka是领…

[Android]开发摇一摇分歧表决器过程
心血来潮,走进Android,准备开发一个摇一摇分歧表决器(PS:这个想法源自去年看的一个都市剧《约会专家》中主人公杭杭开发的分歧表决器APP)。简述:摇一摇分歧表决器是一款Android App,通过将传统的…

C和C++安全编码笔记:整数安全
5.1 整数安全导论:整数由包括0的自然数(0, 1, 2, 3, …)和非零自然数的负数(-1, -2, -3, …)构成。 5.2 整数数据类型:整数类型提供了整数数学集合的一个有限子集的模型。一个具有整数类型的对象的值是附着在这个对象上的数学值。一个具有整数类型的对象…
8.3折特惠票仅剩3天!「2019 嵌入式智能国际大会」全日程大公开!
8.3折特惠票仅剩3天立即抢购:https://t.csdnimg.cn/otBk还有5天,大伙期待的「2019嵌入式智能国际大会」正式开幕了!2019年12月6日-7日,我们在深圳市人才研修院见!大会以“万物互联泛在智能”为主题,邀请30位…