1、C++的特点
1.1 C++ 跨平台性
c++是编译出来的二进制文件在Android、IOS、甚至在WP上都可以正常运行。还有很多其他的跨平台语言,比如之前曾经流行过的H5、ReactNative,还有现下最是火热的 Flutter,他们都属于解释型跨平台,约定好一种脚本语言,底层辅助以各平台的基础实现。比如ReactNative的开发中使用JavaScriptCore,单本质上还是在运行时解释js然后再执行native,真正参与编译的是 语法解释器+NA底层代码而不是js,他们或多或少都是通过c++实现的。
1.2 ObjectiveC 与 C++ 向下兼容C
ObjectiveC 与 C++ 都向下兼容C ,是他们的共同点和纽带。
OC中的对象虽然有ARC辅助内存管理,但本质上还是一个void *,同理C++也一样是void *,OC之所以调用函数叫做发送消息(sendMsg),是因为封装了层独有的runtime机制(这机制还是C的)
,但归根结底每个函数实体依然是一个IMP
,依然是一个函数指针
,这一点和C++也一样,所以他们之间的衔接才会如此通畅。
2、ObjectiveC 与 C++ 混编
混编过程中主要存在以下三种文件:
• 纯C++类:只需要分别创建对应的 .h 和 .cpp 文件,直接导入工程并配置到对应TARGETS 的Compile Sources 下 即可。如果需要使用C++标准库,还需要导入libc++库。即创建下图2中的2 和 3。如下图所示:
• 混编文件 .mm 文件:如果你想创建一个能即识别C++又识别OC的对象,只需要照常创建一个.h 文件和.m文件,然后将.m文件重命名成.mm文件,就是告诉编译器,这个文件可以进行混编 — ObjectiveC++。即创建下图中的1,然后修改文件名后缀。
• 纯OC类: .m文件和对应的头文件,即创建下图中的1。
3、OC&C++混编示例
如果想要在c++ 文件中直接使用 OC对象,则需要 此 c++ 文件后缀改为.mm, 然后在此文件中使用OC 对象,不要在对应的.h 文件中使用,.h 文件中还是 c++的数据结构。这里1以 OC环境下调用C++代码为例进行说明。
1)创建纯 c++类文件 CppFile.h 和 CppFile.cpp,其中 .h 文件如下所示
#pragma once
#include <string>
//using std::string;
class CppFile {
public:
CppFile(const std::string name);
~CppFile();
public:
void DoSomething(const std::string& str);
};
2)创建 混编文件 OCCFile.h 和 OCCFile.m, 然后将OCCFile.m 文件后缀修改为 .mm, 其中 头文件如下所示:
#pragma once
#import <Foundation/Foundation.h>
#include "CppFile.h"
@interface OCCFile : NSObject
{
CppFile* mCppFile;
}
- (void)doSomethingWithString:(NSString*)str;
@end
3)在demo工程的ViewController.m 文件中引用我们创建混编文件 OCCFile.h
#import "ViewController.h"
#import "OCCFile.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
OCCFile* occFile = [OCCFile new];
[occFile doSomethingWithString:@"How do you do!"];
}
@end
此时编译以下,我们要养成好习惯边写边编译,这样子可以最快的发现问题,我们看看现在会有什么问题 🤔!
出错了🤔!是的,你没有看错,build 失败了,说好的C++和OC 完美结合呢?
C++和OC是完美结合的,我们说的是.m 文件修改为 .mm 文件之后,能够让xcode编译器知道这是一个混编文件,因为xcoce 可以识别.mm 的混编语法。但是xcode不识别 .h文件中的混编语法,也不识别.hh 语法为混编。也就是对于xcode 来说:
• 如果.h 文件中都是 C++ 接口(比如CppFile.h) , 或者都为 OC (比如 ViewController.h 文件)都是没有问题;
• 如果.h 文件是混编的,当然 引入 纯c++的头文件也是不可以,死心眼的 xcode 就是认识 。
那怎么处理呢?有下面两个办法:
• 将文件或者方法移到xcode 编译器识别的混编的 .mm 文件里;
#pragma once
-------CppFile.h
#import <Foundation/Foundation.h>
//#include "CppFile.h"
@interface OCCFile : NSObject
{
// CppFile* mCppFile;
}
- (void)doSomethingWithString:(NSString*)str;
@end
-------CppFile.cpp
#import "OCCFile.h"
#include "CppFile.h"//移动到这里了了...
@interface OCCFile ()
{
CppFile* mCppFile;//移动到这里了了...
}
@end
@implementation OCCFile
- (id)init
{
if (self == [super init])
{
printf("=======My name is OCCFile.\n ");
}
return self;
}
- (void)doSomethingWithString:(NSString*)str
{
}
@end
• 使用 通过 OC对象id
和 c语言的 void *
😅,然后在 .mm 中进行类型转换;
#pragma once
#import <Foundation/Foundation.h>
//#include "CppFile.h"
@interface OCCFile : NSObject
{
// CppFile* mCppFile;
id mCppFile;//可以修改成这样子,😄
}
- (void)doSomethingWithString:(NSString*)str;
@end
void*
是指针的最原本形态,利用它我们随意的进行C++和OC的混编,但是因为OC 对象 存在内存管理,但是 C++ 是没有的,所以在ARC下需要进行下特殊处理。
• __bridge: ARC下OC对象和Core Foundation对象之间的桥梁,转化时只涉及对象类型
不涉及对象所有权的转化;
• __bridge_retained: 将内存所有权同时归原对象和变换后的对象持有,并对变换后的对象做一次reatain
。
• __bridge_transfer:将内存所有权彻底移交变换后的对象持有,retain变换后的对象,release变换前的对象
。
思路:
首先,在全局区域声明了一个全局的函数指针 OCInterfaceCFunction,用来处理所有跨C++回调OC事件。
#pragma once
typedef void(*OCInterfaceCFunction)(void*, void*);
然后,在创建纯OC类(OCInterfaceCC.m)文件中也实现了这一个函数指针MyObjectDoSomethingWith,这个OCInterfaceCFunction实体就是我们的接口通道了。
----- 纯OC类
#import "OCInterfaceC.h"
#include "OCInterfaceCFile.h"
void MyObjectDoSomethingWith(void* obj, void* param)
{
[(__bridge id)obj dosomthing:param];
}
@implementation OCInterfaceC
- (id)init
{
self = [super init];
if (self)
{
_call = MyObjectDoSomethingWith;
}
return self;
}
- (int)dosomthing:(void *)param
{
printf("hei, there is OC .....\n");
return 0;
}
@end
------ 纯c++类
#include "InterfaceCC.h"
#include <stdio.h>
InterfaceCC::InterfaceCC(void* ocObj, OCInterfaceCFunction interfaceFunction)
{
mOCObj = ocObj;
mInterfaceFunction = interfaceFunction;
}
InterfaceCC::~InterfaceCC()
{
}
void InterfaceCC::doSomthings()
{
printf("there is c++.\n");
mInterfaceFunction(mOCObj,NULL);
}
最后,当初始化纯c++类( InterfaceCC)的时候,将以创建好的OCInterfaceCC和这个OCInterfaceCFunction一并传入,当Cpp需要调用Oc的时候,直接使用OCInterfaceCC与OCInterfaceCFunction即可。
OCInterfaceC* OCtoC = [[OCInterfaceC alloc] init];
void* CCPointer = (__bridge void*)OCtoC;
InterfaceCC* cc = new InterfaceCC(CCPointer, OCtoC.call);
cc->doSomthings();
这样做的好处是可以隔离两种语言避免开发上的混乱和困难。比如C++做三方库在一个以OC为主的环境中进行使用,OC需要任意调用C++的各种接口和对象,但是不希望三方库直接引用oc头文件,希望三方库解耦,只通过固定回调或者协议来通信。示通过创建中间文件专门处理交互,可以降低模块间的耦合度, 否则 处理维护起来会非常困难;
完整Demo地址:https://github.com/starmier/OCFlutterCDemo
4、总结
.h 需要保持各自的数据结构,保证纯正的C++ 或 OC;
将需要混编引用的.m 或者 .cpp文件的后缀修改为 .mm ,告诉编译器 这个文件可以进行混编 --- ObjectiveC++;
Objective-C 和 C++ 都是向下兼容 C的,可以通过灵活的使用
void *
指针 作为纽带在两者之间传递对象;
Objective-c 可以通过工程的Build Settings/Apple Clang - Language - Objective-C/ Objective-C Automatic Reference Counting 来配置模式内存管理为 ARC(特定文件配置标示:-objc-arc) 或则 MRC(特定文件配置标示:-fno-objc-arc),但是 C++ 的内存管理是谁创建谁释放的原则;故在一个项目如果要使用多种语言,尽量的将两种语言分开封装,各自内部维护内存;