Если вы разрабатываете приложения для Mac и iOS на Objective-C или Swift, это не означает, что вы можете использовать только эти языки программирования. Очень часто возникает ситуация, когда вам нужно подключить к проекту модули на C++ или C. Xcode и LLVM компилятор позволяют это делать. Далее я расскажу как вызвать С+ метод из Objective-C, предать в него параметры, и из C++ метода вызвать Objective-C и вернуть в него результаты.
Итак!
Как подружить Objective-C и C++ в Xcode?
Предположим, что нам необходимо написать приложение, которое ищет файлы, имея шаблон и путь для поиска. Основное приложение реализовано на Objective-C, а модель поиска реализована на C++. Приступим.
Создаем новый проект.
Создадим класс SearchManager, который будет представлять собой Синглтон и отвечать за взаимодействие с моделью поиска.
SearchManager.h
1 2 3 4 5 6 7 | #import <Foundation/Foundation.h> @interface SearchManager : NSObject + (SearchManager *)sharedManager; @end |
SearchManager.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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
1 2 3 4 5 6 7 8 | #ifndef cppsearch_hpp #define cppsearch_hpp #include <stdio.h> void performCppSearch(const char* searchTemplate, const char* searchPath); #endif /* cppsearch_hpp */ |
cppsearch.cpp
1 2 3 4 5 6 | #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
1 2 3 4 5 6 7 8 9 | #import <Foundation/Foundation.h> @interface SearchManager : NSObject + (SearchManager *)sharedManager; - (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath; @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #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 |
В принципе первая часть задачи выполнена. Запускаем и… Получаем ошибку компиляции. Дело в том, что нам необходимо переименовать 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
1 2 3 4 5 6 | #ifndef SearchManager_C_Interface_h #define SearchManager_C_Interface_h void fileFound (char *file, char *path); #endif /* SearchManager_C_Interface_h */ |
SearchManager.mm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #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
1 2 3 4 5 6 7 8 9 10 | #import <Foundation/Foundation.h> @interface SearchManager : NSObject + (SearchManager *)sharedManager; - (void)performSearchWithTemplate:(NSString *)searchFile withPath:(NSString *)searchPath; - (void)fileFoundObjCWithFile:(NSString *)fileName andPath:(NSString *)path; @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #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 |
Для наглядности вся схема выглядит так: