Если вы разрабатываете приложения для Mac и iOS на Objective-C или Swift, это не означает, что вы можете использовать только эти языки программирования. Очень часто возникает ситуация, когда вам нужно подключить к проекту модули на C++ или C. Xcode и LLVM компилятор позволяют это делать. Далее я расскажу как вызвать С+ метод из Objective-C, предать в него параметры, и из C++ метода вызвать Objective-C и вернуть в него результаты.
Итак!
Как подружить Objective-C и C++ в Xcode?
Предположим, что нам необходимо написать приложение, которое ищет файлы, имея шаблон и путь для поиска. Основное приложение реализовано на Objective-C, а модель поиска реализована на C++. Приступим.
Создаем новый проект.
Создадим класс 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
Реализуем наш поиск в виде метода 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 — это С интерфейс
в котором объявляем метод 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. Желаю удачи!
Для наглядности вся схема выглядит так: