Objective-C и С++ в Xcode

Если вы разрабатываете приложения для Mac и iOS на Objective-C или Swift, это не означает, что вы можете использовать только эти языки программирования. Очень часто возникает ситуация, когда вам нужно подключить к проекту модули на C++ или C. Xcode и LLVM компилятор позволяют это делать. Далее я расскажу как вызвать  С+ метод из Objective-C, предать в него параметры, и из C++ метода вызвать Objective-C и вернуть в него результаты.

Итак!

Как подружить Objective-C и C++ в Xcode?

Предположим, что нам необходимо написать приложение, которое ищет файлы, имея шаблон и путь для поиска. Основное приложение реализовано на Objective-C, а модель поиска реализована на C++. Приступим.

Создаем новый проект.

Objective-C и С++ в Xcode

Создадим класс SearchManager, который будет представлять собой Синглтон и отвечать за взаимодействие с моделью поиска.

SearchManager.h

#import <Foundation/Foundation.h>

@interface SearchManager : NSObject

+ (SearchManager *)sharedManager;

@end

SearchManager.m

#import "SearchManager.h"

@implementation SearchManager

+ (SearchManager *)sharedManager
{
    static SearchManager * _sharedManager = nil;
    static dispatch_once_t oncePredicate;
    
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[SearchManager alloc] init];
    });
    return _sharedManager;
}

@end

Мы реализовали классический синглтон.

Создаем модель нашего поиска на C++. File->New File->C++ File. Вводим название файла cppsearch и отмечаем галочку Also create a header file

Objective-C и С++ в Xcode

Реализуем наш поиск в виде метода void performCppSearch(const char* searchTemplate, const char* searchPath);

cppsearch.hpp

#ifndef cppsearch_hpp
#define cppsearch_hpp

#include <stdio.h>

void performCppSearch(const char* searchTemplate, const char* searchPath);

#endif /* cppsearch_hpp */

cppsearch.cpp

#include "cppsearch.hpp"

void performCppSearch(const char* searchTemplate, const char* searchPath)
{
    printf("perform search in cpp for %s in %s", searchTemplate, searchPath);
}

Отлично. Теперь нам необходимо реализовать вызов С++ метода performCppSearch из нашего Objective-C менеджера поиска. Теперь внимание. Первое , что мы делаем, — подключаем заголовочный C++ файл директивой #include(!) Не #import, а #include, — иначе компилятор выдаст ошибку «Undefined symbols for architecture x86_64:» И реализуем вызов поиска через метод

— (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath

В итоге класс нашего Менеджера поиска приобретает следующий вид:

SearchManager.h

#import <Foundation/Foundation.h>

@interface SearchManager : NSObject

+ (SearchManager *)sharedManager;

- (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath;

@end

SearchManager.m

#import "SearchManager.h"
#include "cppsearch.hpp"

@implementation SearchManager

+ (SearchManager *)sharedManager
{
    static SearchManager * _sharedManager = nil;
    static dispatch_once_t oncePredicate;
    
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[SearchManager alloc] init];
    });
    return _sharedManager;
}

- (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath
{
    const char* file = [searchFile cStringUsingEncoding:NSUTF8StringEncoding];
    const char* path = [searchPath cStringUsingEncoding:NSUTF8StringEncoding];
    
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue1, ^{
        performCppSearch(file, path);
    });
}

@end

В методе performSearchWithTemplate:withPath: мы приводим шаблон поиска и путь из NSString* к char* и передаем их в C++ метод, который вызываем отдельным потоком.

В принципе первая часть задачи выполнена. Запускаем и… Получаем ошибку компиляции. Дело в том, что нам необходимо переименовать SearchManager.m в SearchManager.mm. Расширение .mm говорит компилятору, что этот файл должен быть скомпилирован как C++ для того, чтобы иметь доступ к другому C++ файлу. Переименовываем, запускаем и вуаля.  С++ метод вызовет printf и мы увидим в консоли результат выполнения поиска «perform search in cpp for TEMPLATE in PATH».

Отлично. Теперь, нам надо передать результаты поиска из C++ назад в Objective-C. Как это сделать?

Для нашего SearchManager создаем еще один заголовочный класс — SearchManager-C-Interface.h — это С интерфейс

Objective-C и С++ в Xcode

в котором объявляем метод fileFound, который будет объявлен в Objective-C классе SearchManger и будет вызываться из C++

SearchManager-C-Interface.h

#ifndef SearchManager_C_Interface_h
#define SearchManager_C_Interface_h

void fileFound (char *file, char *path);

#endif /* SearchManager_C_Interface_h */

SearchManager.mm

#import "SearchManager.h"
#include "cppsearch.hpp"

@implementation SearchManager

+ (SearchManager *)sharedManager
{
    static SearchManager * _sharedManager = nil;
    static dispatch_once_t oncePredicate;
    
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[SearchManager alloc] init];
    });
    return _sharedManager;
}

- (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath
{
    const char* file = [searchFile cStringUsingEncoding:NSUTF8StringEncoding];
    const char* path = [searchPath cStringUsingEncoding:NSUTF8StringEncoding];
    
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue1, ^{
        performCppSearch(file, path);
    });
}

void fileFound (char *file, char *path)
{
    printf("file found with name %s and path %s", file, path);
}

@end

И немного изменим наш С++ метод  performCppSearch

cppsearch.cpp

#include "cppsearch.hpp"
#include "SearchManager-C-Interface.h"

void performCppSearch(const char* searchTemplate, const char* searchPath)
{
    printf("perform search in cpp for %s in %s\n", searchTemplate, searchPath);
    
    char name[] = "FoundFileName";
    char path[] = "FoundFilePath";
    
    char* pname = &name[0];
    char* ppath = &path[0];
    
    fileFound(pname, ppath);
}

Запускаем и видим как наш Objective-C SearchManager успешно вызывает C++ performSearch, а в ответ C++ performSearch успешно вызывает Objective-C fileFound. В консоли мы увидим:

«perform search in cpp for TEMPLATE in PATH
file found with name FoundFileName and path FoundFilePath»

Отлично осталось одно но. Дело в том, что метод результатов fileFound — это С метод и нам из него желательно нужно вернуться с результатами в Objective-C. Проблема в том, что в методах C у нас нет указателя на self по умолчанию, как у нас есть в Objective-C методах, поэтому мы не можем из C метода сделать что-то вроде

[self fileFoundObjCWithFile:fileStr andPath:pathStr]; 

Вместо этого мы вызываем

[[SearchManager sharedManager] fileFoundObjCWithFile:fileStr andPath:pathStr];

Итоговый SearchManager.h

#import <Foundation/Foundation.h>

@interface SearchManager : NSObject

+ (SearchManager *)sharedManager;

- (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath;
- (void)fileFoundObjCWithFile:(NSString *)fileName andPath:(NSString *)path;

@end

Итоговый SearchManager.mm

#import "SearchManager.h"
#include "cppsearch.hpp"

@implementation SearchManager

+ (SearchManager *)sharedManager
{
    static SearchManager * _sharedManager = nil;
    static dispatch_once_t oncePredicate;
    
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[SearchManager alloc] init];
    });
    return _sharedManager;
}

- (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath
{
    const char* file = [searchFile cStringUsingEncoding:NSUTF8StringEncoding];
    const char* path = [searchPath cStringUsingEncoding:NSUTF8StringEncoding];
    
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue1, ^{
        performCppSearch(file, path);
    });
}

void fileFound (char *file, char *path)
{
    NSString *fileStr = [NSString stringWithUTF8String:file];
    NSString *pathStr = [NSString stringWithUTF8String:path];
    [[SearchManager sharedManager] fileFoundObjCWithFile:fileStr andPath:pathStr];
}

- (void)fileFoundObjCWithFile:(NSString *)fileName andPath:(NSString *)path
{
    NSLog(@"%@", [NSString stringWithFormat:@"file found with name %@ and path %@", path, fileName]);

}

@end

Вот и все! Данный рабочий проект вы можете посмотреть на GitHub. Желаю удачи!

github

Для наглядности вся схема выглядит так:

owerview-m

overview-h