Можно ли отделить основную функцию и классы C++ от процедур Objective-C и / или C при компиляции и компоновке?
У меня есть небольшое приложение C++, которое я импортировал классы Objective-C. Он работает как файлы Objective-C++,.mm, но любой файл C++, который содержит заголовок, который может в конечном итоге содержать заголовок Objective-C, должен быть переименован в расширение.mm для соответствующих драйверов GCC.
Есть ли способ написать либо чисто C++-оболочку для классов Objective-C, либо я могу как-то отделить объекты Objective-C и просто связать их отдельно? Может быть, даже если бы классы Objective-C стали маленькой библиотекой, я мог бы статически пересвязать ссылки во время компиляции?
Проблема заключается в том, что этот код является кроссплатформенным, и его сложнее компилировать в системах, которые обычно не используют Objective-C (т.е. не Mac). Несмотря на то, что команды препроцессора ограничивают любую реализацию кода Objective C в Windows или Linux, исходный код все еще имеет расширения.mm, и GCC все еще обрабатывает код как Objective C++.
2 ответа
Обычно вы просто заключаете классы Objective-C в классы C++, например, используя непрозрачные указатели и перенаправляя вызовы методов C++ в методы Objective-C.
Таким образом, ваши переносимые исходники C++ никогда не должны видеть какие-либо Objective-C, и в идеале вам нужно только поменять файлы реализации для оболочек на разных платформах.
Пример:
// c++ header:
class Wrapper {
struct Opaque;
Opaque* opaque;
// ...
public:
void f();
};
// Objective-C++ source on Mac:
struct Wrapper::Opaque {
id contained;
// ...
};
void Wrapper::f() {
[opaque->contained f];
}
// ...
Да, это легко возможно в обоих направлениях, если знать несколько хитростей:
1) Тип "id" фактически определен в простом C-заголовке. Таким образом, вы можете сделать следующее:
В вашем заголовке:
#include <objc/objc.h>
class MyWindow
{
public:
MyWindow();
~MyWindow();
protected:
id mCocoaWindow;
};
В вашей реализации (.mm):
#include "MyWindow.h"
#include <Cocoa/Cocoa.h>
MyWindow::MyWindow()
{
mCocoaWindow = [[NSWindow alloc] init];
}
MyWindow::~MyWindow()
{
[mCocoaWindow release];
mCocoaWindow = nil;
}
2) Существуют две константы препроцессора, которые можно использовать для исключения кода, специфичного для C++/ObjC, когда исходный файл включает их один из двух, но не ObjC++:
#if __OBJC__
// ObjC code goes here.
#endif /* __OBJC__*/
#if __cplusplus
// C++ code goes here.
#endif
Только будьте осторожны, вы не можете просто добавлять / удалять ivars или виртуальные методы с помощью #ifdef, это создаст два класса с разным расположением памяти и приведет к аварийному завершению вашего приложения очень странными способами.
3) Вы можете использовать указатель на структуру без объявления ее содержимого:
В вашем заголовке:
@interface MyCppObjectWrapper : NSObject
{
struct MyCppObjectWrapperIVars *ivars; // This is straight ObjC, no ++.
}
@end
В вашем файле реализации (.mm):
struct MyCppObjectWrapperIVars
{
std::string myCppString1;
std::string myCppString2;
std::string myCppString3;
};
@implementation MyCppObjectWrapper
-(id) init
{
if(( self = [super init] ))
{
ivars = new MyCppObjectWrapperIVars;
}
return self;
}
-(void) dealloc
{
delete ivars;
ivars = NULL;
[super dealloc];
}
@end
Это сделает ваш заголовок простым стандартным C или ObjC, в то время как ваш файл реализации получает конструкторы / деструкторы всех ivars, вызываемых без необходимости создавать / удалять каждый из них как объект в куче.
Это все только сторона Mac, но это может означать, что вы можете не допускать, чтобы ObjC содержал ваши заголовки, или, по крайней мере, заставить его компилироваться при использовании из кроссплатформенных клиентских файлов реализации Mac вашего уровня переносимости C++.