今天看啥  ›  专栏  ›  starmier

ObjectiveC混编 c++

starmier  · 简书  ·  · 2019-05-25 22:57

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。如下图所示:


图1:配置示例.png

• 混编文件 .mm 文件:如果你想创建一个能即识别C++又识别OC的对象,只需要照常创建一个.h 文件和.m文件,然后将.m文件重命名成.mm文件,就是告诉编译器,这个文件可以进行混编 — ObjectiveC++。即创建下图中的1,然后修改文件名后缀。
  • 纯OC类: .m文件和对应的头文件,即创建下图中的1。


图2: 文件类型示例.png

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 完美结合呢?

Build.png

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++ 的内存管理是谁创建谁释放的原则;故在一个项目如果要使用多种语言,尽量的将两种语言分开封装,各自内部维护内存;




原文地址:访问原文地址
快照地址: 访问文章快照