Qt与Objective-C混合编程

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

1 comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注