这个话题并不是一个新的话题,网上有前辈做过详细的描述(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;
}