这个话题并不是一个新的话题,网上有前辈做过详细的描述(Mixing Objective-C, C++ and Objective-C++: an Updated Summary)。之所以做为一篇文章,是因为在实际项目中用到了这种混合,加以记录。

首先需要澄清的是,Objective-C是strict superset of C。C++是基于C语言的面向对象的扩充,但不是strict superset of C。Objective-C++是Objective-C的扩展,使得Objective-C可以链接C++代码。通常情况下,要使用Objective-C++,需要将.m源文件改成.mm源文件。

其次需要澄清的是,Objective-C代码跟C++代码是不能混合使用的,也就是说,Objective-C的头文件中是看不到C++的头文件的。当然,C++的头文件是看不到Objective-C的头文件的。只有Objective-C++文件(也就是.mm文件)可以看到Objective-C的头文件和C++的头文件。

正因为Objective-C和C++互相不能见到对方的头文件,那么就意味着,现有的C++的代码库,想要在Objective-C中使用,是需要封装的,通常,这种封装是借助于Objective-C++写成的Wrapper.

另一方面,Wrapper本身也有技巧,那就是使用Objective-C的class extension特性(或者使用Pointer to implementation设计模式),避免将C++头文件暴露给Objective-C(否则无法编译通过)。具体细节可以参考文章开头的链接。

背景

在使用Qt给MacOS上的截图工具实现自动选中窗口区域需求的时候,原理无非就是获得桌面上可见的所有窗口的坐标以及尺寸,再将这些数据用一个结构体封装起来,按照在桌面的Z序进行排序,最后对鼠标的移动事件进行判断,按照Z序去对鼠标是否在某个窗口的矩形范围内进行判断,直到找到在范围内的矩形,这就是我们要自动选中的矩形了,如果将所有窗口都遍历完了还没发现鼠标正在某个矩形内的话,就不进行自动选中操作。

在Windows下我们可以很方便的使用EnumWindows这个Win32 API去获得桌面上所有窗口的坐标和尺寸,Win32 API可以由C++很方便的调用,而在MacOS上,单纯的使用C++仿佛无法获得我需要的这些数据,于是只好使用Apple的Objective-C提供的接口去获得这些数据。

通过在苹果开发者文档找到了MacOS上很多获得窗口信息的接口,使用CGWindowListCopyWindowInfo就能得到每个窗口的具体数据,接下来我们就要开始Qt与Objective-C混合编程了。

Objective-C是一种在C的基础上加入面向对象特性扩充而成的编程语言,通常称为jObC和较少用的 Objective C或ObjC。在一定程度上,可以把 Objective-C看成是ANSI版本C语言的一个超集,它支持相同的C语言基本语法,同时它还扩展了标准的 ANSI C语言的语法,包括定义类、方法和属性。当然还有其他一些结构的完善和拓展,如类别(Category)的出现。

所以Objective-c是可以直接调用C语言的,那么能否直接调用C++呢?答案是肯定的。

Objective-C源文件介绍

首先我要说一下Objective-C的源文件,后缀是.m或.mm,在.mm文件里,可以直接使用C++代码。所以,我们要混合Qt代码与Objective-C代码,就需要在Qt项目里加入mm文件。

而要混合Objective-C代码,需要更改一下pro文件,添加mm文件,如果有用到MacOS的API的话,则可能还需要添加MacOS的Framework。

添加源文件,需要在.pro中使用OBJECTIVE_SOURCES这个变量,如下所示:

OBJECTIVE_SOURCE += \
Getallvisiblewndpos.mm

添加头文件,需要在.pro中使用OBJECTIVE_HEADERS这个变量,如下所示:

OBJECTIVE_HEADERS += \
getallvisiblewndpos.h

添加Framework,需要在.pro中使用LIBS这个变量,如下所示:

LIBS += -framework CoreGraphics
LIBS += -framework CoreFoundation

混合代码

要使用MacOS提供的框架,需要在.mm文件内包含相关的头文件,又有几部分工作要做。一个是在.pro文件里加入Framework路径,使用LIBS变量即可,不多说了。另外一点是在mm文件内包含Objective-C的头文件,与C++头文件一个理儿,不过使用Objective-C的头文件要使用#import,而使用C++的头文件要使用#include,且所有的#include要写在#import的上面。

需要注意的是,凡是出现了Objective-C源代码和头文件的文件都需要将拓展名改成.mm,只有这样编译器在编译的时候才会既认识Objective-C代码,又认识C++代码。

注意事项

当遇到链接失败的问题时,如下图所示:

通常都是因为只引入了头文件而没有引入对应的Framework,通过在苹果开发者文档或者在Xcode中查找对应的Framework并添加到.pro文件中加以解决。

附赠Qt下Objective-C获得MacOS下所有窗口坐标、尺寸、Z序的方式:

//getAllVisibleWndPos.h
#ifndef GETALLVISIBLEWNDPOS_H
#define GETALLVISIBLEWNDPOS_H

#include <QRect>
#include <QList>
#include <vector>

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <CoreGraphics/CGWindow.h>
#import <CoreFoundation/CFArray.h>

struct WND_INFO{
	int layer;//Z序
	int index;//序号
	QRect pos;
};

bool CompareWNDINFO(const WND_INFO& A , const WND_INFO& B);
QList<WND_INFO> GetAllVisibleWndPos();

#endif // GETALLVISIBLEWNDPOS_H
//getAllVisibleWndPos.mm
#import <getallvisiblewndpos.h>

bool CompareWNDINFO(const WND_INFO& A , const WND_INFO& B)
{
	if(A.layer == B.layer)
	{
		return A.index < B.index;
	}
	if(A.layer > B.layer && B.layer > 0)
	{
		return true;
	}
	if(A.layer < B.layer && A.layer > 0)
	{
		return false;
	}
	return A.layer == 0;
}

QList<WND_INFO> GetAllVisibleWndPos()
{
	std::vector<WND_INFO> vec;
	CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements , kCGNullWindowID);
	CFIndex cnt = CFArrayGetCount(windowList);
	for (CFIndex i = 0; i < cnt ; i++)
	{
		NSDictionary *dict = (NSDictionary* )CFArrayGetValueAtIndex(windowList , i);
		int layer = 0;
		CFNumberRef numberRef = (__bridge CFNumberRef) dict[@"kCGWindowLayer"];
		CFNumberGetValue(numberRef , kCFNumberSInt32Type , &layer);
		if(layer < 0)
			continue;
		CGRect windowRect;
		CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)(dict[@"kCGWindowBounds"]) , &windowRect);
		
		QRectF pos;
		pos.setRect(windowRect.origin.x , windowRect.origin.y , windowRect.size.width , windowRect.size,height);
		
		WND_INFO info;
		info.layer = layer;
		info.pos.setRect(pos.x() , pos.y() , pos.width() , pos.height());
		info.index = i;
		
		vec.push_back(info);
	}
	
	std::sort(vec.begin() , vec.end() , CompareWNDINFO);
	QList<WND_INFO> list;
	for(int i = 0 ; i < vec.size() ; i++)
	{
		list.push_back(vec[i]);
	}
	return list;
}

Windows下打包Qt应用程序

在Qt Creator下用Release编译一遍程序,生成相应的exe文件。

 在Qt Creator下编译好的Release下的exe 拷贝到一个新建的文件夹下面;在开始菜单搜索Qt,找到如图这个命令行程序(命令行程序的版本必须与你使用的编译器版本严格对应,否则打包出来的程序将无法正常运行),然后运行。

打开QT命令行,然后利用cd语句进入你拷贝的exe文件放在的文件夹下; 如:”cd C:\Users\Final\Desktop\XXX”,接着运行命令行语句:”windeployqt XXX.exe”(XXX.exe用你的exe文件名替代),然后文件夹下就会出现所有QT依赖。

然后将这个文件夹打包好就可以发布了。如果需要将所有的dll以及其他资源文件全部打包成一个exe,我们可以使用Engima Virtual Box这款软件进行打包。

MacOS下打包Qt应用程序

在Qt Creator下用Release编译一遍程序,生成相应的应用程序。

然后找到Qt安装目录下的macdeployqt,比如在我的安装目录下该文件在“/users/dxf/Qt5.9.0/clang_64/bin/”目录,其中Qt5.9.0以及clang_64分别代表Qt的版本和Qt使用的C++编译器,在使用macdeployqt的时候Qt版本以及使用的编译器必须和编译时使用的Qt库版本及编译器严格对应,否则程序运行阶段将会出现不可预知的问题。

将macdeployqt拖入终端中,然后将你的应用程序也拖入终端中,如图所示,执行相应的打包命令,我们的Qt应用程序的所有依赖的库就会被打入应用程序中了。

如果需要打包成MacOS下的dmg文件,只需在命令行后加上-dmg即可,如图所示。

在Windows下对打包的应用程序进行压缩

在对Qt应用程序进行打包之后,这时候就会看到Qt已经把需要用到的DLL都复制过来了。如果觉得这时候整个程序比较大的话,可以根据需要去掉一些东东:

  • libEGL.dll,libGLESV2.dll这两个文件是ANGLE的文件,可以去掉。opengl32sw.dll是软件模拟OpenGL使用到的,除非用户的系统连DirectX支持都不完整——虚拟机环境就是这样——不然这个文件也完全没有用。 QtWidgets/C++程序都不用OpenGL,所以直接去掉即可。可在调用 windeployqt.exe时加”–no-angle”和”–no-opengl-sw”这两个参数。
  • 如果没有使用svg的话,iconenginesqsvgicon.dll, imageformatsqsvg.dll,Qt5Svg.dll这三个文件也可以删掉。
  • 如果没有对应用程序做国际化处理的话,translations里面的翻译文件也可以删掉。
  • QML程序没有使用QtWidgets/C++,可以删掉Qt5Widgets.dll。
  • 如果imageformats目录里面有几种图像格式没用上,也可以删掉。我自己通常把整个目录都删掉,Qt已经编译了png的支持,能读写程序包含的图标就够,其它格式不重要。
  • qmltooling和Qt5Network.dll是用于QML调试用的,可以删掉。

经过以上裁剪,使用7zip压缩完以后,一个QtWidgets/C++的HelloWorld程序最终只剩下5.64M,一个 QtControls程序是5.86M, QtQuick程序还会更小一些。

通常情况下,只需要platforms\qwindows.dll、Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll四个。Qt 5拆得那么散就是想解决这个问题。所以不要打包所有的dll,用到哪个就加哪个。

关于Qt版本对应用程序大小的影响

如果你用的Qt版本为Qt 5.x甚至Qt6.x,5.x和6.x增加了很多新特性的确大了一些。
如果只是写个小程序,不需要5.x的新特性,那么推荐Qt 4.7.x/4.8.x。

Qt适合三种场景:

  • 必须跨平台项目
  • 大项目,代码行数在30万+
  • Qt铁杆粉的项目

这三种场景下,安装包大小都不是主要问题。

减小安装包的方法也很简单:自己编译Qt库。
在configure的时候去掉RTTI,异常,Qt3支持,优化选项用最小大小(性能差不了太多)。
如果你的程序真的很小,那么可以直接静态链接,这样就更小了。

MacOS下应用程序“XXX”不能打开

当MacOS下的应用程序在传输过程中有遇到过压缩的话,经常会遇到「应用程序“XXX”不能打开」的提示,这是由于应用经过压缩和解压之后,其中的可执行文件的权限被抹除,导致无法打开。下面以ScreenShot应用为例子,解决一下这个问题。

1、右击该应用,选择“显示包内容”。

2、依次打开“Content”、“MacOS”目录,找到ScreenShot文件,我们会发现这个文件变成了文本编辑文稿类型的文件,而实际上这个文件应该为Unix可执行文件,由于在压缩的过程中权限丢失,所以这个文件变成了类型不明的文件,导致应用无法打开。

3、打开终端,输入“chmod +x”,注意“chmod”和“+x”中间有个空格,然后再输入空格,将ScreenShot文件拖入终端,接着按回车键执行指令。“chmod +x ScreenShot”的意思就是给ScreenShot这个文件执行权限的意思。

4、执行完这条命令之后,ScreenShot就被成功给予了执行权限,变成了Unix执行文件,这时这个应用就可以正常运行了。

枚举值键值备注
Key_Escape0x01000000Esc键(左上角)
Key_Tab0x01000001Tab键(制表键)
Key_Backtab0x01000002Shift+Tab
Key_BackTabKey_Backtab与Key_Backtab键值相同,但在Qt3版本使用
Key_Backspace0x01000003退格(回格)
Key_BackSpaceKey_Backspace与Key_Backspace键值相同,但在Qt3版本使用
Key_Return0x01000004字母键盘回车
Key_Enter0x01000005数字键盘回车
Key_Insert0x01000006插入
Key_Delete0x01000007删除
Key_Pause0x01000008暂停中断
Key_Print0x01000009打印
Key_SysReq0x0100000a系统请求键
Key_Clear0x0100000b清除
Key_Home0x01000010光标移动到起始值
Key_End0x01000011结束
Key_Left0x01000012左键
Key_Up0x01000013上键
Key_Right0x01000014右键
Key_Down0x01000015下键
Key_PageUp0x01000016上页
Key_PriorKey_PageUp与Key_PageUp键值相同,但在Qt3版本使用
Key_PageDown0x01000017下页
Key_NextKey_PageDown与Key_PageDown键值相同,但在Qt3版本使用
Key_Shift0x01000020上档(调节器),通常用于组合按键使用
Key_Control0x01000021控制键,通常用于组合按键使用
Key_Meta0x01000022变换按键,现代计算机键盘一般无此键,此键通常用Alt键或者Windows键仿真
Key_Alt0x01000023替换键,通常用于组合按键使用
Key_CapsLock0x01000024大写锁定
Key_NumLock0x01000025数码锁定
Key_ScrollLock0x01000026滚动锁定
Key_F10x01000030功能键F1
Key_F20x01000031功能键F2
Key_F30x01000032功能键F3
Key_F40x01000033功能键F4
Key_F50x01000034功能键F5
Key_F60x01000035功能键F6
Key_F70x01000036功能键F7
Key_F80x01000037功能键F8
Key_F90x01000038功能键F9
Key_F100x01000039功能键F10
Key_F110x0100003a功能键F11
Key_F120x0100003b功能键F12
Key_F130x0100003c功能键F13
Key_F140x0100003d功能键F14
Key_F150x0100003e功能键F15
Key_F160x0100003f功能键F16
Key_F170x01000040功能键F17
Key_F180x01000041功能键F18
Key_F190x01000042功能键F19
Key_F200x01000043功能键F20
Key_F210x01000044功能键F21
Key_F220x01000045功能键F22
Key_F230x01000046功能键F23
Key_F240x01000047功能键F24
Key_F250x01000048功能键F25(X11系统独有)
Key_F260x01000049功能键F26(X11系统独有)
Key_F270x0100004a功能键F27(X11系统独有)
Key_F280x0100004b功能键F28(X11系统独有)
Key_F290x0100004c功能键F29(X11系统独有)
Key_F300x0100004d功能键F30(X11系统独有)
Key_F310x0100004e功能键F31(X11系统独有)
Key_F320x0100004f功能键F32(X11系统独有)
Key_F330x01000050功能键F33(X11系统独有)
Key_F340x01000051功能键F34(X11系统独有)
Key_F350x01000052功能键F35(X11系统独有)
Key_Super_L0x01000053左超级按键(Window系统下左边键盘徽标)
Key_Super_R0x01000054右超级按键(Window系统下右边键盘徽标)
Key_Menu0x01000055菜单
Key_Hyper_L0x01000056左Hyper按键
Key_Hyper_R0x01000057右Hyper按键
Key_Help0x01000058帮助
Key_Direction_L0x01000059左方向按键
Key_Direction_R0x01000060右方向按键
Key_Space0x20空格键(7位可打印ASCII)
Key_AnyKey_Space与Key_Space键值相同,但在Qt3版本使用
Key_Exclam0x21!
Key_QuoteDbl0x22\
Key_NumberSign0x23#
Key_Dollar0x24$
Key_Percent0x25%
Key_Ampersand0x26&
Key_Apostrophe0x27
Key_ParenLeft0x28(
Key_ParenRight0x29)
Key_Asterisk0x2a*
Key_Plus0x2b+
Key_Comma0x2c,
Key_Minus0x2d
Key_Period0x2e.
Key_Slash0x2f/
Key_00x300
Key_10x311
Key_20x322
Key_30x333
Key_40x344
Key_50x355
Key_60x366
Key_70x377
Key_80x388
Key_90x399
Key_Colon0x3a:
Key_Semicolon0x3b;
Key_Less0x3c<
Key_Equal0x3d=
Key_Greater0x3e>
Key_Question0x3f?
Key_At0x40@
Key_A0x41A
Key_B0x42B
Key_C0x43C
Key_D0x44D
Key_E0x45E
Key_F0x46F
Key_G0x47G
Key_H0x48H
Key_I0x49I
Key_J0x4aJ
Key_K0x4bK
Key_L0x4cL
Key_M0x4dM
Key_N0x4eN
Key_O0x4fO
Key_P0x50P
Key_Q0x51Q
Key_R0x52R
Key_S0x53S
Key_T0x54T
Key_U0x55U
Key_V0x56V
Key_W0x57W
Key_X0x58X
Key_Y0x59Y
Key_Z0x5aZ
Key_BracketLeft0x5b[
Key_Backslash0x5c\
Key_BracketRight0x5d]
Key_AsciiCircum0x5e^
Key_Underscore0x5f_
Key_QuoteLeft0x60`
Key_BraceLeft0x7b{
Key_Bar0x7c\
Key_BraceRight0x7d}
Key_AsciiTilde0x7e~
Key_nobreakspace0x0a0不间断空格
Key_exclamdown0x0a1
Key_cent0x0a2
Key_sterling0x0a3英国货币;标准纯银;英镑
Key_currency0x0a4货币
Key_yen0x0a5日元
Key_brokenbar0x0a6间断条(仅供参考)
Key_section0x0a7分区(仅供参考)
Key_diaeresis0x0a8分音符(仅供参考)
Key_copyright0x0a9版权(仅供参考)
Key_ordfeminine0x0aa女性(仅供参考)
Key_guillemotleft0x0ab左引号
Key_notsign0x0ac告示牌(仅供参考)
Key_hyphen0x0ad连字号(仅供参考)
Key_registered0x0ae注册(仅供参考)
Key_macron0x0af长音符号(加于元音上)(仅供参考)
Key_degree0x0b0度数(仅供参考)
Key_plusminus0x0b1加减符(仅供参考)
Key_twosuperior0x0b2两个优势(仅供参考)
Key_threesuperior0x0b3三个优势(仅供参考)
Key_acute0x0b4加剧(仅供参考)
Key_mu0x0b5希腊字母第12字(仅供参考)
Key_paragraph0x0b6段落;分段符号(仅供参考)
Key_periodcentered0x0b7有圆心的句号”.”(仅供参考)
Key_cedilla0x0b8变音符号(仅供参考)
Key_onesuperior0x0b9一个优势(仅供参考)
Key_masculine0x0ba男性(仅供参考)
Key_guillemotright0x0bb直角引号(仅供参考)
Key_onequarter0x0bc四分之一(仅供参考)
Key_onehalf0x0bd二分之一(仅供参考)
Key_threequarters0x0be四分之三(仅供参考)
Key_questiondown0x0bf下一个问题(仅供参考)
Key_Agrave0x0c0
Key_Aacute0x0c1
Key_Acircumflex0x0c2
Key_Atilde0x0c3
Key_Adiaeresis0x0c4
Key_Aring0x0c5
Key_AE0x0c6
Key_Ccedilla0x0c7
Key_Egrave0x0c8
Key_Eacute0x0c9
Key_Ecircumflex0x0ca
Key_Ediaeresis0x0cb
Key_Igrave0x0cc
Key_Iacute0x0cd
Key_Icircumflex0x0ce
Key_Idiaeresis0x0cf
Key_ETH0x0d0
Key_Ntilde0x0d1
Key_Ograve0x0d2
Key_Oacute0x0d3
Key_Ocircumflex0x0d4
Key_Otilde0x0d5
Key_Odiaeresis0x0d6
Key_multiply0x0d7
Key_Ooblique0x0d8
Key_Ugrave0x0d9
Key_Uacute0x0da
Key_Ucircumflex0x0db
Key_Udiaeresis0x0dc
Key_Yacute0x0dd
Key_THORN0x0de
Key_ssharp0x0df
Key_agraveKey_Agrave
Key_aacuteKey_Aacute
Key_acircumflexKey_Acircumflex
Key_atildeKey_Atilde
Key_adiaeresisKey_Adiaeresis
Key_aringKey_Aring
Key_aeKey_AE
Key_ccedillaKey_Ccedilla
Key_egraveKey_Egrave
Key_eacuteKey_Eacute
Key_ecircumflexKey_Ecircumflex
Key_ediaeresisKey_Ediaeresis
Key_igraveKey_Igrave
Key_iacuteKey_Iacute
Key_icircumflexKey_Icircumflex
Key_idiaeresisKey_Idiaeresis
Key_ethKey_ETH
Key_ntildeKey_Ntilde
Key_ograveKey_Ograve
Key_oacuteKey_Oacute
Key_ocircumflexKey_Ocircumflex
Key_otildeKey_Otilde
Key_odiaeresisKey_Odiaeresis
Key_division0x0f7
Key_oslashKey_Ooblique
Key_ugraveKey_Ugrave
Key_uacuteKey_Uacute
Key_ucircumflexKey_Ucircumflex
Key_udiaeresisKey_Udiaeresis
Key_yacuteKey_Yacute
Key_thornKey_THORN
Key_ydiaeresis0x0ff
// International input method support (X keycode – 0xEE00, the definition follows Qt/Embedded 2.3.7) Only interesting if you are writing your own input method. International & multi-key character composition
Key_AltGr0x01001103
Key_Multi_key0x01001120Multi-key character compose
Key_Codeinput0x01001137
Key_SingleCandidate0x0100113c
Key_MultipleCandidate0x0100113d
Key_PreviousCandidate0x0100113e
// Misc Functions
Key_Mode_switch0x0100117eCharacter set switch
// Key_script_switch = 0x0100117e, // Alias for mode_switch
// Japanese keyboard support
Key_Kanji0x01001121Kanji, Kanji convert
Key_Muhenkan0x01001122Cancel Conversion
// Key_Henkan_Mode = 0x01001123, // Start/Stop Conversion
Key_Henkan0x01001123Alias for Henkan_Mode
Key_Romaji0x01001124to Romaji
Key_Hiragana0x01001125to Hiragana
Key_Katakana0x01001126to Katakana
Key_Hiragana_Katakana0x01001127Hiragana/Katakana toggle
Key_Zenkaku0x01001128to Zenkaku
Key_Hankaku0x01001129to Hankaku
Key_Zenkaku_Hankaku0x0100112aZenkaku/Hankaku toggle
Key_Touroku0x0100112bAdd to Dictionary
Key_Massyo0x0100112cDelete from Dictionary
Key_Kana_Lock0x0100112dKana Lock
Key_Kana_Shift0x0100112eKana Shift
Key_Eisu_Shift0x0100112fAlphanumeric Shift
Key_Eisu_toggle0x01001130Alphanumeric toggle
//Key_Kanji_Bangou = 0x01001137, // Codeinput
//Key_Zen_Koho = 0x0100113d, // Multiple/All Candidate(s)
//Key_Mae_Koho = 0x0100113e, // Previous Candidate
// Korean keyboard support
// In fact, many Korean users need only 2 keys, Key_Hangul and
// Key_Hangul_Hanja. But rest of the keys are good for future.
Key_Hangul0x01001131Hangul start/stop(toggle)
Key_Hangul_Start0x01001132Hangul start
Key_Hangul_End0x01001133Hangul end, English start
Key_Hangul_Hanja0x01001134Start Hangul->Hanja Conversion
Key_Hangul_Jamo0x01001135Hangul Jamo mode
Key_Hangul_Romaja0x01001136Hangul Romaja mode
//Key_Hangul_Codeinput0x01001137, // Hangul code input mode
Key_Hangul_Jeonja0x01001138Jeonja mode
Key_Hangul_Banja0x01001139Banja mode
Key_Hangul_PreHanja0x0100113aPre Hanja conversion
Key_Hangul_PostHanja0x0100113bPost Hanja conversion
//Key_Hangul_SingleCandidate = 0x0100113c, // Single candidate
//Key_Hangul_MultipleCandidate = 0x0100113d, // Multiple candidate
//Key_Hangul_PreviousCandidate = 0x0100113e, // Previous candidate
Key_Hangul_Special0x0100113fSpecial symbols
//Key_Hangul_switch = 0x0100117e, // Alias for mode_switch
// dead keys (X keycode – 0xED00 to avoid the conflict)
Key_Dead_Grave0x01001250
Key_Dead_Acute0x01001251
Key_Dead_Circumflex0x01001252
Key_Dead_Tilde0x01001253
Key_Dead_Macron0x01001254
Key_Dead_Breve0x01001255
Key_Dead_Abovedot0x01001256
Key_Dead_Diaeresis0x01001257
Key_Dead_Abovering0x01001258
Key_Dead_Doubleacute0x01001259
Key_Dead_Caron0x0100125a
Key_Dead_Cedilla0x0100125b
Key_Dead_Ogonek0x0100125c
Key_Dead_Iota0x0100125d
Key_Dead_Voiced_Sound0x0100125e
Key_Dead_Semivoiced_Sound0x0100125f
Key_Dead_Belowdot0x01001260
Key_Dead_Hook0x01001261
Key_Dead_Horn0x01001262
// multimedia/internet keys – ignored by default – see QKeyEvent c’tor
Key_Back0x01000061
Key_Forward0x01000062
Key_Stop0x01000063
Key_Refresh0x01000064
Key_VolumeDown0x01000070
Key_VolumeMute0x01000071
Key_VolumeUp0x01000072
Key_BassBoost0x01000073
Key_BassUp0x01000074
Key_BassDown0x01000075
Key_TrebleUp0x01000076
Key_TrebleDown0x01000077
Key_MediaPlay0x01000080
Key_MediaStop0x01000081
Key_MediaPrevious0x01000082
Key_MediaPrevKey_MediaPrevious
Key_MediaNext0x01000083
Key_MediaRecord0x01000084
Key_MediaPause0x1000085
Key_MediaTogglePlayPause0x1000086
Key_HomePage0x01000090
Key_Favorites0x01000091
Key_Search0x01000092
Key_Standby0x01000093
Key_OpenUrl0x01000094
Key_LaunchMail0x010000a0
Key_LaunchMedia0x010000a1
Key_Launch00x010000a2
Key_Launch10x010000a3
Key_Launch20x010000a4
Key_Launch30x010000a5
Key_Launch40x010000a6
Key_Launch50x010000a7
Key_Launch60x010000a8
Key_Launch70x010000a9
Key_Launch80x010000aa
Key_Launch90x010000ab
Key_LaunchA0x010000ac
Key_LaunchB0x010000ad
Key_LaunchC0x010000ae
Key_LaunchD0x010000af
Key_LaunchE0x010000b0
Key_LaunchF0x010000b1
Key_MonBrightnessUp0x010000b2
Key_MonBrightnessDown0x010000b3
Key_KeyboardLightOnOff0x010000b4
Key_KeyboardBrightnessUp0x010000b5
Key_KeyboardBrightnessDown0x010000b6
Key_PowerOff0x010000b7
Key_WakeUp0x010000b8
Key_Eject0x010000b9
Key_ScreenSaver0x010000ba
Key_WWW0x010000bb
Key_Memo0x010000bc
Key_LightBulb0x010000bd
Key_Shop0x010000be
Key_History0x010000bf
Key_AddFavorite0x010000c0
Key_HotLinks0x010000c1
Key_BrightnessAdjust0x010000c2
Key_Finance0x010000c3
Key_Community0x010000c4
Key_AudioRewind0x010000c5
Key_BackForward0x010000c6
Key_ApplicationLeft0x010000c7
Key_ApplicationRight0x010000c8
Key_Book0x010000c9
Key_CD0x010000ca
Key_Calculator0x010000cb
Key_ToDoList0x010000cc
Key_ClearGrab0x010000cd
Key_Close0x010000ce
Key_Copy0x010000cf
Key_Cut0x010000d0
Key_Display0x010000d1
Key_DOS0x010000d2
Key_Documents0x010000d3
Key_Excel0x010000d4
Key_Explorer0x010000d5
Key_Game0x010000d6
Key_Go0x010000d7
Key_iTouch0x010000d8
Key_LogOff0x010000d9
Key_Market0x010000da
Key_Meeting0x010000db
Key_MenuKB0x010000dc
Key_MenuPB0x010000dd
Key_MySites0x010000de
Key_News0x010000df
Key_OfficeHome0x010000e0
Key_Option0x010000e1
Key_Paste0x010000e2
Key_Phone0x010000e3
Key_Calendar0x010000e4
Key_Reply0x010000e5
Key_Reload0x010000e6
Key_RotateWindows0x010000e7
Key_RotationPB0x010000e8
Key_RotationKB0x010000e9
Key_Save0x010000ea
Key_Send0x010000eb
Key_Spell0x010000ec
Key_SplitScreen0x010000ed
Key_Support0x010000ee
Key_TaskPane0x010000ef
Key_Terminal0x010000f0
Key_Tools0x010000f1
Key_Travel0x010000f2
Key_Video0x010000f3
Key_Word0x010000f4
Key_Xfer0x010000f5
Key_ZoomIn0x010000f6
Key_ZoomOut0x010000f7
Key_Away0x010000f8
Key_Messenger0x010000f9
Key_WebCam0x010000fa
Key_MailForward0x010000fb
Key_Pictures0x010000fc
Key_Music0x010000fd
Key_Battery0x010000fe
Key_Bluetooth0x010000ff
Key_WLAN0x01000100
Key_UWB0x01000101
Key_AudioForward0x01000102
Key_AudioRepeat0x01000103
Key_AudioRandomPlay0x01000104
Key_Subtitle0x01000105
Key_AudioCycleTrack0x01000106
Key_Time0x01000107
Key_Hibernate0x01000108
Key_View0x01000109
Key_TopMenu0x0100010a
Key_PowerDown0x0100010b
Key_Suspend0x0100010c
Key_ContrastAdjust0x0100010d
Key_LaunchG0x0100010e
Key_LaunchH0x0100010f
Key_MediaLast0x0100ffff
// Keypad navigation keys
Key_Select0x01010000
Key_Yes0x01010001
Key_No0x01010002
// Newer misc keys
Key_Cancel0x01020001
Key_Printer0x01020002
Key_Execute0x01020003
Key_Sleep0x01020004
Key_Play0x01020005Not the same as Key_MediaPlay
Key_Zoom0x01020006
// Key_Jisho = 0x01020007, // IME: Dictionary key
// Key_Oyayubi_Left = 0x01020008, // IME: Left Oyayubi key
// Key_Oyayubi_Right = 0x01020009, // IME: Right Oyayubi key
// Device keys
Key_Context10x01100000
Key_Context20x01100001
Key_Context30x01100002
Key_Context40x01100003
Key_Call0x01100004set absolute state to in a call (do not toggle state)
Key_Hangup0x01100005set absolute state to hang up (do not toggle state)
Key_Flip0x01100006
Key_ToggleCallHangup0x01100007a toggle key for answering, or hanging up, based on current call state
Key_VoiceDial0x01100008
Key_LastNumberRedial0x01100009
Key_Camera0x01100020
Key_CameraFocus0x01100021
Key_unknown0x01ffffff

Qt键盘的一个小知识

Qt::Key_ReturnQt::Key_Enter比较

相同之处

  • Qt::Key_ReturnQt::Key_Enter都是回车键.

不同之处

  • Qt::Key_Return是字母键盘的回车键;
  • Qt::Key_Enter则为数字键盘的回车键.

看了下时间,此时已是凌晨01:17分,辗转难眠的我,空洞的双眼显得些许木纳,脑海一片空白的自己也不知道在想什么。

其实哪有什么好失眠的啊,不过是日常熬夜成瘾,一旦闲下来早睡也睡不着罢了。

大字躺着望着乌漆麻黑的天花板发呆的我,索性任性不睡了,那就竖起耳朵倾听着时间走向天亮吧。

这个点,阳台外面早已褪去了人群来往的喧嚣,不过偶尔还是会传来车经过的声响。

有的司机像是在着急赶路一样“呼”的一声飞驰而过;有的则像极了新手上路缓缓行走的车轮温柔地抚摸着洒落在街道上的月光,生怕把它的影子碾碎一般。

唯一相同的是,黄亮的车前灯伴着月光穿过围墙,透过窗台依然是那么刺眼,似乎在赤裸裸地挑衅着宣告它已经看光了所有,你不必躲躲藏藏。

我想,此时那条温顺的小狗应该和往常一样吧,会一如既往地乖巧地躺在守夜阿姨的身旁,默默地陪伴着看守着这凄清的街道。

但我并不敢起身去验证这个猜想,因为我害怕啊。

我害怕看到的是忠诚的小狗也有坚持不住每天都守护着主人的那一天,只剩下阿姨落寞的形影单只,孤单地守着本就孤单的夜晚。

其实主要是因为我胆小。

放在床头边乘凉的小风扇机械地在运转着,振动的嘎嘎声像是它发出疲倦呻吟的求救信号一般地荡漾在我耳边。

已远去的夏天,炎热却并没有随着跟走。我哪有那么大的慈悲让它停下来稍微歇息一会,哪怕一小会啊。

运转扇风本就是它活着的使命,谁不在疲惫的活着呢。

忘记了这是第几个凌晨还没睡,也忘记了多久没有十二点前睡着过了。

老实说,每天杂七杂八忙忙碌碌的生活,我都不知道自己在忙了些什么,但又真的没空。大概这就是混日子最后反被日子混了的结果吧。

记得,以前老师说过眼前辛苦点好好学习过后上了大学就轻松了,可我似乎并没有感到过一刻轻松。

我甚至不知道是我上了大学还是大学上了我,除了做不完的作业还是作业,恨不得时光倒流回去只需要一个脑袋栽进学习里就好了的高中生活。

每天由己或者身不由己的熬夜成瘾,一日复一日里成了死循环。想要改变的决心还没有萌生一秒,意识里就已经发出了否的反抗,可笑的是理由还挺理所应当。

不是不害怕过自己会突然毫无征兆的猝死在某一天的深夜里,可是比起死亡我更害怕活不下去。

活下去的压力一个又一个的接踵而来,死亡显得反倒像是一种奢望的解脱,只是有点不负责罢了。

那既然这样,还是接着好好熬夜好好生活下去吧。

也没什么不好的,至少能醒着感知这个世界多一点点。

首先请大家看一个视频: https://www.bilibili.com/bangumi/play/ep314554

这个视频讲的是北京的夜晚,一群男人在湖边钓鱼,不在乎能不能钓到鱼,在乎的是坐在湖边的那一份安静。这些人来自各行各业,不过他们大都有一个特点,就是人到中年,身上扛着各种各样的压力,白天到处奔波,面对各种柴米油盐,晚上终于有一点自己的时间,来湖边安安静静的坐着,释放着一天的疲惫,享受一个人的孤独和平静,天亮了,继续去奋斗。

视频很温馨,看完这个视频我还想起之前看过的一个文章,说有一些人,下班开车回家,到家之后并不马上回屋里,而是在车里安静的坐一会,或者点支烟抽一会,然后才回屋里,这是每天最平静的时候。走出这一块安静的地方,又得去面对各种各样的事情。

这就是人生,你永远不知道世界上这么多人每个人都是过着什么样的生活,有的人看完视频觉得原来有那么多人跟我一样啊,瞬间感觉自己不孤独,心里很温暖。有的人看了视频,感叹原来还有这样的生活,那我自己还觉得压力大,真是无病呻吟,瞬间压力减少了很多。还有的人看完视频,心有感触,哇的一下哭了了出来,哭完心里也好了很多。

其实这哪里是什么终极解法,视频只是给忙碌中的我们一丝温暖,一丝感动,希望我们能放下焦虑的心,世界这么大,我们永远不是一个人,休息过后继续奋斗才是真。

现代社会,焦虑和迷茫总是人们提及的最多的词,几乎所有的人都希望能有一份安稳,然而安稳几乎都是用付出换来的,我们不能一边呆在舒适区渴望着成功,渴望着改变,一边害怕自己的努力被辜负,害怕付出之后得不到结果,等到风雨来袭的时候,又后悔当初的自己没有努力。

有焦虑有迷茫是好事,坏的是感受不到焦虑每天沉浸在娱乐中不能自拔,坏的是感到焦虑却不去努力改变。其实不仅中年人有危机,年轻的时候如果不努力,那时候危机就已经有了,只不过当时是一个人吃饱了全家不饿,并没有切身的感受到。当中年到来的时候,才感受到了种种的危机,开始还以前欠下的债务。人生短短几十载,我们能做的就是把握住当下,想想未来五年,十年之后的我们,不要让那时的我们在因为今天的不努力而继续悔恨。

世界很大,无论我们现在处于什么状态,世界上总会有很多人跟我们一样,虽然我们彼此不认识,但是我们只要知道在前进的道路上我们并不孤单就行了,知道总会有人与我们同行就行了。

一起加油吧,不管我们是否认识,我们都在努力着。

理解动态库与静态库区别

静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。

静态链接库是什么?

一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
将自己设计的类导出为二进制形式的可执行代码。静态链接库有两种形式

  • MSVC编译器生成的文件后缀为 “.lib”
  • MinGW编译器生成的文件后缀为 “.a”

动态链接库是什么?

动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。

总结

从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。

在Qt中生成和调用静态库

在QtCreator中按照如下步骤创建静态库,静态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。

选择静态链接库,用来创建静态库。

创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。

//mylib.h
#ifndef MYLIB_H
#define MYLIB_H

#include <QMessageBox>
class MyLib
{    
    public:
        MyLib();
};

#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"

MyLib::MyLib()
{
    QMessageBox::information(NULL , "Title" , "Hello World");
}

将该项目进行编译,生成两个文件:libMyLib.a和mylib.o,我们真正要用到的只需要libMyLib.a以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.a放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的静态链接库:

//UseLib.pro
LIBS += \
        $$PWD/libMyLib.a

接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该静态链接库里的MyLib对象啦。
项目结构如下:

在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:

    MyLib *lib = new MyLib();

编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们静态库MyLib中的构造函数中的弹窗~调用静态库成功!

在Qt中生成和调用动态库

在QtCreator中按照如下步骤创建动态库,动态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。

选择共享库,用来创建动态库。

创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,一个mylib_global.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。

//mylib.h
#ifndef MYLIB_H
#define MYLIB_H

#include <QMessageBox>
class MyLib
{    
    public:
        MyLib();
};

#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"

MyLib::MyLib()
{
    QMessageBox::information(NULL , "dll title" , "Hello World");
}

将该项目进行编译,生成三个文件:libMyLib.a、MyLib.dll和mylib.o,我们真正要用到的只需要MyLib.dll以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.dll放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的动态链接库:

//UseLib.pro
LIBS += -L$$PWD -lMyLib

接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该动态链接库里的MyLib对象啦。
项目结构如下:

在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:

    MyLib *lib = new MyLib();

编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们动态库MyLib中的构造函数中的弹窗~调用动态库成功!

需要注意的是,如果要将应用程序发布,需要将动态链接库跟随主程序一起发布。

将dylib库嵌入MacOS应用的方法

当你的应用程序使用了第三方的动态库,或自己开发的动态库的时候,使用macdeployqt则会报错:

ERROR: no file at "/usr/lib/libXXXX.1.dylib"

用otool -L untitled.app/Contents/MacOS/untitled 可以看到输出中包含如下这一行。

libXXXX.1.dylib (compatibility version 1.0.0, current version 1.0.1)

这一行表示你的应用程序找这个动态库是相对路径的,即要求你的这个动态库在/usr/lib目录下或/usr/local/lib目录下。你双击编译出的用用程序提示无法打开,点击报告会显示为找不到库。其实是在/usr/lib目录下或/usr/local/lib目录下 找不到这个库。你手工放置库文件到这个目录即可双击运行。注意:你没有权限把库放到/usr/lib下,因此你放到/usr/local/lib即可。
注意在你动态库的xxx.pro文件中加入如下的配置。否则,双击应用程序的时候会到/usr/lib找,而不是在/usr/local/lib找。

unix {
target.path = /usr/local/lib
INSTALLS += target
}

为了发布出去的应用程序不再在/usr/local/lib目录下找对应的动态库。而是在bundle包(目录)中查找。从而用户复制你的bundle到“应用程序”目录即可直接运行。因此你需要修改应用程序记录动态库的路径。修改方法如下:

install_name_tool -change "libXXXX.1.dylib" "@rpath/xxxx/libXXXX.1.dylib" untitled.app/Contents/MacOS/untitled 

命令表示,把bundle包里面的应用程序untitled储存的此库的路径从”libXXXX.1.dylib”改为”@rpath/xxxx/libXXXX.1.dylib”。

执行完此命令后,找到untitled这个编译好的程序右键“打开包内容/Show Package Contents”,然后跳转到bundle的包内部目录里面,切换到“Contents”目录下的Frameworks目录中,然后创建一个目录“xxxx”(自己起的名字)然后把你制作的动态库或第三方的动态库放到这个目录。保证库的名字和”@rpath/xxxx/libXXXX.1.dylib”写的库的名字对应上。
以上就都做好了,现在用otool工具检测一下应用程序untitled包含库的路径:

otool -L untitled.app/Contents/MacOS/untitled

就会变成以@rpath开头的相对路径了。这次你双击“untitled”程序,程序就不会报错说找不到库了。
然后执行以下命令打包为dmg安装包。

macdeployqt ./build-untitled-Desktop_Qt_5_12_3_clang_64bit-Release/untitled.app -dmg

使用到的工具介绍

  • macdeployqt:qt的提供的工具,可以把应用程序依赖的动态库,查找出来并放到一个文件夹中。
  • macdeployqt -dmg:在macdeployqt基础上,再打包生成dmg安装包
  • otool -L:查看一个动态库或应用程序的依赖
  • install_name_tool -change: 改变应用程序或库的依赖库路径。

引言

为何redis中大量使用的是SDS,而不是传统的C语言字符串表示法存储字符串?到底什么是SDS?为什么要使用SDS?其相对于传统的C语言字符串有何优点?本文会针对以上几点做一个详细的分析,帮助大家以及自己更好的理解redis中的简单动态字符串

介绍SDS之前先简单介绍一下C语言中的传统字符串表示法

C语言字符串(C字符串)

始终以空字符结尾的字符数组,c语言为其封装了大量的函数库操作API

C字符串特点
  • 字符数组存储
  • 以空字符结尾,除了末尾之外,字符串里面不可以包含空字符
  • 由于采用字符数组,导致所存储的字符必须符合某种编码(如ASCII)
  • 获取C字符串长度必须遍历整个字符串,并对遇到的每个字符计数,直到遇到空字符为止,时间复杂度为O(N)

那么基于以上特点,C字符串是否能够满足我们Redis高效,安全的需求呢?显然是不能的,单是一个获取字符串长度就需要遍历整个字符串数组了,必须重新定义一种新的结构以支持Redis中对于频繁高效操作字符串的要求。


SDS 简单动态字符串

基于以上C字符串的缺陷,Redis自己构建了一种名为简单动态字符串的抽象类型,简称SDS。

SDS结构
struct sdshdr {

    // buf数组已经使用字节数量,即SDS字符串长度
    int len;

    // 记录buf数组中未使用字节的数量
    int free;

    // 字节数组,用于保存二进制数据
    char buf[];
}

可以看出,SDS定义的结构中,增加了一个int类型的len属性用于记录SDS所保存的字符串长度,一个free属性用于记录数组中未使用的字节数量。

SDS特点
  • 常数复杂度获取字符串长度

Redis中利用SDS字符串的len属性可以直接获取到所保存的字符串的长
度,直接将获取字符串长度所需的复杂度从C字符串的O(N)降低到了O(1)。


  • 减少修改字符串时导致的内存重新分配次数

基于前面介绍的C字符串的特性,我们知道对于一个包含了N个字符的C字符串来说,其底层实现总是N+1个字符长的数组(额外一个空字符结尾),那么如果这个时候需要对字符串进行修改,程序就需要提前对这个C字符串数组进行一次内存重分配(可能是扩展或者释放),而内存重分配就意味着是一个耗时的操作。

Redis巧妙的使用了SDS避免了C字符串的缺陷,在SDS中,buf数组的长度不一定就是字符串的字符数量加一,buf数组里面可以包含未使用的字节,而这些未使用的字节由free属性记录。

SDS采用了空间预分配策略避免C字符串每一次修改时都需要进行内存重分配的耗时操作,将内存重分配从原来的每修改N次就分配N次降低到了修改N次最多分配N次。

字符串扩展:

首先检查SDS未使用空间即free属性是否够用,如果够用,则会直接使用未使用空间,而无须进行内存重分配

空间预分配

所谓预分配就是额外多分配一部分空间给SDS,并不是仅仅只分配刚好够字符串扩展修改后的空间。

分配策略:

  1. 若SDS修改后,其长度(len的属性)小于1MB,那么程序会分配和修改后的len属性同样大小的未使用空间
  2. 若修改后,其长度大于1MB,那么程序只会分配固定1MB的未使用空间,不会再多分配了,考虑内存的成本因素

字符串缩短:

针对SDS字符串的缩短场景,SDS并不会立即释放多余出来的内存空间,而是将这部分内存空间记录再其free属性中,当SDS字符串进行扩展时,这部分未使用的内存空间就能直接用,而不需要进行内存重分配,这就是SDS的惰性空间释放


  • 杜绝缓冲区溢出

在C字符串的拼接操作过程中,例如某程序员操作S1拼接S2,由于程序员忘记了给需要拼接的字符串S1分配足够的内存空间(到底需要分配多少内存呢?哈哈,当然需要遍历S2的字符数组才能知道S2的长度是多少,因为C字符串不记录自身的长度),那么拼接的时候就会导致缓冲区溢出的可能性。

针对以上情况,SDS的空间分配策略可以完全杜绝这种情况,因为当SDS 的API对SDS进行修改时,API会首先检查SDS的未使用空间是否足够,不够的化会进行内存重分配以扩展空间至足够修改所需的大小,然后再执行实际的修改操作,这样就可以达到杜绝缓冲区溢出的可能。


  • 让Redis保存更多类型的数据成为可能

由于C字符串中保存的字符必须符合某种编码格式(如ASCII),这就限制了C字符串只能保存文本数据,而不能保存箱视频,音频,压缩文件这种的二进制数据。

另一个限制是C字符串中间不能包含空字符,否则中间的空字符会被认为是整个字符串的结尾,而导致后面的部分字符被忽略掉。

SDS的API被设计成了二进制安全的,以处理二进制的方式来处理SDS中存放再buf数组中的数据,原样存取,这就是为什么在SDS的结构中采用的是字节数组,而不是C字符串中的字符数组

这样的二进制安全的SDS,使得Redis不仅可以保存文本,还可以保存任意格式的二进制数据。


  • 兼容部分C字符串API

由于C字符串本身具有大量操作API,SDS如果可以利用一部分C字符串的API那样就不用重复发明轮子了,所以Redis中的SDS遵循C字符串以空字符结尾的惯例,在SDS的API中,总会将SDS保存的数据末尾设置未空字符,在分配buf数组时也总会多分配一个字节来保存这个空字符,这样SDS就可以重用一部分C字符串库的API。


C字符串与SDS对比

对比点C字符串SDS
获取字符串长度时间复杂度O(N)O(1)
API安全性不安全,可能造成缓冲区溢出安全,不会造成溢出
字符串修改N次需要几次内存重分配N次至多N次
能够保存数据类型只能保存文本文本或二进制
对于C语言中字符串API的使用范围所有一部分

总结

Redis中采用SDS替代C语言中传统字符串表示法,提升了获取字符串长度的效率,扩大了能够保存数据的类型范围,以及降低了每次修改字符串时候的内存重分配次数,甚至规避了在操作C字符串中可能出现的缓冲区内存溢出的可能性,从而为Redis中字符串操作的安全,高效提供了保障。

(1)读取 http body 部分

(2)根据 boundary 分析出分隔符特征(这个串是唯一的,不会与body内其他数据冲突)

(3)根据实际分隔符分段获取 body 内容

(4)遍历分段内容

(5)根据 Content-Disposition 特征获取其中值

(6)根据值中 filename 或 name 区分是否是包含二进制流还是表单数据的 k-v

(7)根据 filename 获取原始文件名

(8)从连续 两个 newline 字符串为起始至当前分段完毕,按照二进制流读取上传文件流信息。

完成后即有——

  • 原始文件名信息
  • 原始文件类型信息
  • 全部文件流信息

然后该干嘛干嘛,比如写文件到磁盘等。

具体怎么处理是服务器做的事儿,HTTP协议本身并没有死规定,以下说的只是解决问题的某一种思路。

有一个基本认识是,每一个请求都是一个流。而每一个用于传输文件的HTTP报文,都会有类似于这样的报头:

Content-Type: multipart/form-data; boundary=巴拉巴拉

如果报头定义了这样的东西,就可以判断客户端采用了multipart格式传递信息,同时我们也拿到了boundray。

再考虑文件如何处理。以问题中提到的报文为例,payload(你题干中的报文格式并不对,我根据题目意思做了相应修改)为:

-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="userfile1"; filename="备注说明.txt"
Content-Type: text/plain

1.±ê×¢ÒÔiPhone6s ÆÁÄ»³ß´çΪ±ê×¼£»
2.Èç¹ûÐèÒª²»Í¬³ß´çµÄicon£¬ÔÙ¸øÎÒ˵¡£
-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="hehe"

tewtw
-----------------------------14579331036932498511351460782--

1. 第一次读到定义的边界”—————————–14579331036932498511351460782″

意味着一个字段的开始;

2. 继续读入一行,发现这是个文件;再读入一行,发现定义了Content-Type,也许还会定义charset之类的信息;再读入一行发现是个 CRLF ,意味着后续的内容是文件数据,这时候可以构造一个新的临时文件对象,将后续的数据pipe到这个临时文件对象中。

3. 再一次读到边界”—————————–14579331036932498511351460782″意味着这一个字段结束,这时候可以去关闭刚刚创建的临时文件。

4. 然后开始继续下一字段解析过程。

ps:以上部分只是简单的说了解决思路,并不涉及检查、转换等工作。比如在流的pipe过程中,可能需要根据之前定义的charset进行流的转换,甚至如果发现Content-Type不是自己需要的,就压根不存而是直接pipe到黑洞中去。

编译环境:VS2019 + Win10 + cmake-gui-3.8.0 + cef_binary_3.2623.1401.gb90a3be_windows32

最后一个兼容Windows XP的CEF(2623)的下载地址:https://pan.baidu.com/s/1UoWt8Ffs_YPBCmYlHbipLg

提取码:x7ym

1、解压 cef_binary_3.2623.1401.gb90a3be_windows32 后,目录如下:

2、下载cmake-gui

下载地址:https://pan.baidu.com/s/1KdOaZXWX9gy7yVKVbJdpgA

提取码:ptnu

下载好cmake-gui并安装好之后打开cmake-gui.exe,设置如下:

Where is the source code : cef_binary_3.2623.1401.gb90a3be_windows32解压后的路径

where to build the binaries : cef_binary_3.2623.1401.gb90a3be_windows32解压后的路径

Configure: 选择你电脑上装有的VS的编译器的版本,如果选择了电脑本地并没有的VS编译器版本,会遇到如下情况:

用cmake生成编译工程时候报这样的错误,原因是配置错误导致cmake找不到对应的编译器,于是通过File->Delete cache清理配置,重新通过Configure更换你电脑上装有的VS的编译器的版本即可。

当出现Configuring done的时候点击Generate按钮即可生成对应版本的VS sln解决方案,使用VS打开生成解决方案即可。

VS2015打开cef.sln然后直接编译即可生成libcef_dll_wrapper.lib文件了,如下图项目cefsimple项目和cefclient项目会失败,这个并不影响生成我需要的libcef_dll_wrapper.lib,我就不解决了。

在这里还有一个坑就是这个工具最多只支持到VS2017,由于我的电脑上装了VS2013和VS2019,于是我选择了VS2013的配置并成功编译出了libcef_dll_warpper.lib,但在导入CEF浏览器实际项目调用的时候报了如下错误:error LNK2038: 检测到“_MSC_VER”的不匹配项问题。

_MSC_VER这个相当于做了宏的检测  _MSC_VER 定义编译器的版本。下面是一些编译器版本的_MSC_VER值:
MS VC++ 14.0 _MSC_VER = 1900 vs2015
MS VC++ 12.0 _MSC_VER = 1800 vs2013的编译器他的平台是v120
MS VC++ 11.0 _MSC_VER = 1700 vs2012的编译器他的平台是v110
MS VC++ 10.0 _MSC_VER = 1600 Visual C++ 2010
MS VC++ 9.0 _MSC_VER = 1500 Visual C++ 2008
MS VC++ 8.0 _MSC_VER = 1400 Visual C++ 2005
MS VC++ 7.1 _MSC_VER = 1310
MS VC++ 7.0 _MSC_VER = 1300
MS VC++ 6.0 _MSC_VER = 1200
MS VC++ 5.0 _MSC_VER = 1100

error LNK2038: 检测到“_MSC_VER”的不匹配项: 值“1800”不匹配值“1700”(main.obj 中)
原因:由于你使用了vs2012,工作集选择了更高的1800也就是vs2013的,致使msvc不兼容!
方法:在项目(解决方案资源管理器或者属性管理器里都行)右键属性-配置属性-常规中,平台工具集选用为合适平台即可,比如上面的就是要选择成2012的 v11版本,注意光选了还没有用,还要应用。
注意一个工程里面会有几个解决方案的时候,需要给每个解决方案都更改一遍,最后重新编译即可。

最近在通过OpenHardWareMonitorLib来获得一些CPU和GPU的信息,采用了c++调用c#dll的方法,由于只能传递基本数据类型,所以动态数组考虑到使用String来传递回C++并进行字符串分割,在.net中string是需要用gcnew进行初始化,先来看看gcnew和普通的new的区别:

C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:

1.     gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址. 
2.     gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放.
暂时没有很深入的去理解这些区别。因为需要在c++的控制代码中对c#产生的String^变量进行写出,而默认的文件写出是string类型的,因此需要进行转换。查阅资料发现有人总结了一下较为简单的转换方式:

1:std::string转String^:std::string stdstr=””;
String^ str = marshal_as<String^>(stdstr);

2:String^转std::string:
String^ str= gcnew String();
std::string stdstr = marshal_as(str->ToString());

3:CString转Sting^:
CString cstr=””;
String^ str = marshal_as(cstr.GetBuffer());
cstr.ReleaseBuffer();

4:String^转CString:
String^ str;
CString cstr(str);