升级Mac系统到10.15.1之后,发现有一处代码编译不过。发现是使用了objc_sendMsg,但是在2019.10左右,Mac OS X系统中把这个方法原型修改了。原本是带参数的,现在直接改为void objc_sendMsg(void)了。但是,仍然可以传参数。具体参考 :
objc_msgSend’s New Prototype 这篇文章介绍了为什么能改变函数原型,这里面提到函数调用的实现细节,以及ABI方面的东西。
ABI在理解的时候可以借助我们熟悉的API。
API是源码级别的约定,比如参数,返回值,以及一些属性,比如是否可以继承。这个是由编译器定义约束的。
而ABI是二进制文件之间的约定。定义了一种机制,定义了数据类型,长度,对齐方式,函数调用转化,调用时参数如何在调用者和被调用者之间传递,返回值如何传回给调用者,库文件如何实现,程序如何被加载进内存,多进程如何共存。所以ABI由链接器约束。一般标准是由CPU厂商定义,比如Intel(卷3 Intel® 64 and IA-32 Architectures Software Developer Manuals)、AMD(System V Application Binary Interface AMD64 Architecture Processor Supplement,居然不在AMD官网上维护,而是放在了GitHub上,不解,参考这个问题)。
但是C/C++标准中都没有定义ABI,参考Quora上一个问题 Why is C++ ABI not as good as C ABI? Dan Allen的一个回答,注意别被标题误导,C/C++都没有定义ABI。之前在研究如何让自定义C++类型成为atomic的时候,发现为了满足这一条件,C++做了很多看起来很别扭的约定,而且还在不同版本的标准中变化,比如:一个C++类是否是standard layout,是否是trivial class,是否是POD(而且还不同于C中的POD,真让人头晕)。估计(猜测,不想深究了)跟ABI也有一定的关系。
不过,也有厂商公布了自己的ABI标准。比如libstdc++库定义了C++ Standard Library ABI,虽然版本老一些了。
Casting Objective-C Message Sends,这篇文章介绍了如何修改较好。
objctive-c run-time库可以在这里看objc4,有各种版本。
#if defined(Q_OS_OSX)
#include <objc/objc.h>
#include <objc/message.h>
// ref: https://www.mikeash.com/pyblog/objc_msgsends-new-prototype.html
bool dockClickHandler(id self,SEL _cmd,...);
void setupDockClickHandler() {
Class cls = objc_getClass("NSApplication");
objc_object* (*OBJC_CALL)(id, SEL) = (objc_object* (*)(id, SEL)) objc_msgSend;
objc_object* appInst = OBJC_CALL((objc_object*)cls, sel_registerName("sharedApplication"));
if(appInst != NULL) {
objc_object* delegate = OBJC_CALL(appInst, sel_registerName("delegate"));
Class delClass = (Class)OBJC_CALL(delegate, sel_registerName("class"));
SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
if (class_getInstanceMethod(delClass, shouldHandle)) {
if (class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:")) {
// qDebug() << "Registered dock click handler (replaced original method)";
} else {
//qWarning() << "Failed to replace method for dock click handler";
}
}
else {
if (class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:")) {
//qDebug() << "Registered dock click handler";
} else {
//qWarning() << "Failed to register dock click handler";
}
}
}
}
bool dockClickHandler(id self,SEL _cmd,...) {
if (IM::getInstance()) {
IM::getInstance()->showMainForm();
}
return false;
}
#endif