6

As a Swift coder I am so confused by the apparent fragmentation in the c side of things. Somehow there is Objective-C, Objective-C++, c, and c++ and they all have there .h, .hpp, .m, .cpp, etc file extensions however I cannot find great documentation of which extension goes to which file type in XCode (although I am fairly sure for c++ it should be .hpp and .cpp).

This confusion has lead me state where I have no real idea of what I am doing when trying to expose a c++ class to my swift code. Let me explain my strategy.

I have a bridging file that was autogenerated by XCode which I have modified

Bridging-Header.h

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#include <stdio.h>
#include <stdlib.h>

int* main1() {
    int *num;

    num = (int *)malloc(1000 * sizeof(int));
    for (int i = 0; i < 10; i++) {
        num[i] = i;
    }
    num[10] = 10;
    free(num);
    return num;
}

If I am interpreting things right XCode considers this to be the header file of Objective-C code. Indeed this function is exposed to swift and works as intended.

I then created another file because if I try to import my c++ code in that file it tries to compile it like objective-c which does not go well. This file I believe would be the implementation of this header and I have been told it would be a safe place to import my c++ code.

There is also Bridging-Header.m

#ifndef Bridging_Header_h
#define Bridging_Header_h
#import "Bridging-Header.h"
#import "rectangle.hpp"

int main2() {
    Rectangle* r = new Rectangle();
    return 2;
}

#endif /* TryPost_Bridging_Header_h */

This code I was expecting XCode to treat like Objective-c++ code thus allowing my to safely import my c++ code additionally I thought it would be automatically linked to its corresponding header since if I tried to import it from my other file it would cause an import cycle leading to tons of issues.

However those assumptions must be wrong as this code doesnt appear to even be compiled. Swift does not see the function main2 does not show up in swift.

There is also Rectangle.hpp

#ifndef rectangle_hpp
#define rectangle_hpp

#include <stdio.h>

int main3() {
    return 3;
}
class Rectangle {
    int main();
};

#endif /* fancy_hpp */

I believe XCode should interpret this as C++ code. There is also

Rectangle.cpp

#include "rectangle.hpp"

int Rectangle::main() {
    std::cout << "Hello world!";
    return 99;
}

int main4() {
    return 99;
}

Neither of these seem to be getting compiled either.

I am very confused because I don't know how to even begin dealing with imports between all of these languages nor what I should name the files if I want the compiler to automatically treat them like a certain language. How can I get my Rectangle class to be exposed to swift and maybe even print out "Hello World" when called (even though I am doubtful that printing from c++ is possible.

To be honest I dont really care about the main2 function it is just there for testing purposes and because I think I need that file to get my c++ in. If it is not included in the final project I would not mind. main1, main2, main4 and Rectangle are all important however.

J.Doe
  • 1,502
  • 13
  • 47
  • 1
    You are using `Objective-C` file names for `C++` — they should be `.mm` – l'L'l Feb 25 '18 at 09:09
  • So Objective-C uses `.h` and `.m` and c++ uses `.mm`? I am confused because `.cpp` and `.hpp` was what XCode gave me when I asked it to make a c++ file. What are those two extensions for? – J.Doe Feb 25 '18 at 09:11
  • Yes, .mm is for Objective-c++. Xcode will give .cpp for *c++* because it’s not the same as *Objective-c++* – Sami Kuhmonen Feb 25 '18 at 09:14
  • @Cristik commenting out main1 and main3 does not fix the issue. Was that what you were referring to? – J.Doe Feb 25 '18 at 09:16
  • Could someone please just answer with a list of all the c based languages supported by XCode and what their file extensions should be for both headers and implementations? – J.Doe Feb 25 '18 at 09:18
  • @Cristik what definition did I put in a .h file other than main1 and main3? – J.Doe Feb 25 '18 at 09:19
  • *"when trying to expose a c++ class to my swift code"* – Swift **cannot** import C++/Objective-C++ code, only C/Objective-C. – Martin R Feb 25 '18 at 09:22
  • @MartinRI did not know that. So I am going to have to have a layer of objective c++ between my swift code and my c? If I redid the return statement of main1 to `return num + main2() + main3() + main4();` could I get that to work? Right now the only file that seems to get compiled is the bridging header.h file and I dont know why. – J.Doe Feb 25 '18 at 09:33
  • Possible duplicate of [Interacting with C++ classes from Swift](https://stackoverflow.com/q/35229149/1187415) – Martin R Feb 25 '18 at 09:35
  • XCode supports full C and C++. However when mixing languages with Objective-C which is required for bridging, or Objective-C++ there are come constraints about what you can put in the headers. Maybe this answer to a more specific question could help to understand the interoperability requirements between .h .hpp .m and .mm: https://stackoverflow.com/a/46389811/3723423 – Christophe Feb 25 '18 at 09:40

1 Answers1

12

You cannot directly use c/c++ code in swift because the type systems are different. Objective c/c++ is mixed meaning it understand both c/c++ types and swift types. To consume say Rectangle c++ class, you would wrap this in a objective c/c++ class and import the objective c/c++ header in <Project>-Bridging-Header.hpp file to expose it to Swift.

Swift by default treats all files with extension -

*.m - as objective c files
*.mm - as objective c++ files
*.hpp - as (objective) c/c++ header files
*.cpp - as c++ files
*.c - as c files

Now coming to your example, If i understand correctly you want to use c++ class (say Rectangle) in Swift. Here's the layered view of the solution.

+----------------------------------+
|  Rectangle (c++)                 |
+----------------------------------+
|  RectangleWrapper (objc++)       |
+----------------------------------+
|  Bridging header                 |
+----------------------------------+
|  Swift                           |
+----------------------------------+

Native class

// Rectangle.h
class Rectangle {
  public:
   void draw();
}

// Rectangle.cpp
void Rectangle::draw() {
  cout << "draw rectangle" << endl;
}

objc++ Wrapper

// RectangleWrapper.hpp
@interface RectangleWrapper : NSObject {
@private
  void* rect;
}
- (RectangleWrapper*) init;
- (void) draw;
@end

// RectangleWrapper.mm
#import "Rectangle.h"
@implementation RectangleWrapper

- (RectangleWrapper*) init {
  rect = (void*)new Rectangle;  // create c++ class instance
  return self;           // return objc++ instance
}

- (void) draw {
  Rectangle* r = (Rectangle*)rect;
  r->draw();  // call c++ method
}

@end

Bridging-Header

Expose your objective c++ class to swift world by simply importing the objc++ header file into Bridging-Header.hpp.

// Bridging-Header.hpp
#import "RectangleWrapper.hpp"

Swift

Now simply use the objective c++ RectangleWrapper class as any other swift class.

// use-rectangle.swift
let rect = RectangleWrapper()
rect.draw()

code

See complete code on github

Sunil
  • 335
  • 4
  • 11
  • Thankyou for the clear explanation! I am starting to wrap my head around this. Unfortunately this is not quite working and I am not sure why. To start off when I ask Xcode to generate a bridging header it creates a file with the `.h` header. If I change it to `.hpp` then edit Target->Build Settings->Swift Compiler - General ->Objective-C Bridging Header and just change if from `.h` to `.hpp` it causes this error: – J.Doe Feb 25 '18 at 18:47
  • `:0: error: PCH file '/Users/Me/Library/Developer/Xcode/DerivedData/CLanguages2-cxigszddgyrbtgfxzvliivntiolq/Build/Intermediates.noindex/PrecompiledHeaders/CLanguages2-Bridging-Header-swift_2L50VG7OFFTWE-clang_53GTILGSD4G9.pch' not found: module file not found :0: error: clang importer creation failed` – J.Doe Feb 25 '18 at 18:48
  • Im not sure why the compiler would be particular about this but it is. It then goes downhill from there. I import `RectangleWrapper.hpp` from my bridging header `.h` file which imports my `Rectangle.h` file then if I build it causes an error in the c++ because class needs to be Class (so it is not compiling it as c++ code). Another point of friction is that when I asked Xcode to create c++ code it by default did the header as `.hpp` so I renamed it to `.h` notably keeping it the way it was didn’t help either. – J.Doe Feb 25 '18 at 18:48
  • alright, change Rectangle* rect to void* rect in the header file. In the implementation file typecast rect to Rectangle* before calling draw. Let me know if that still throws errors – Sunil Feb 25 '18 at 18:55
  • Clever fix but now there is this error `Undefined symbols for architecture arm64: "_OBJC_CLASS_$_RectangleWrapper", referenced from: objc-class-ref in GameScene.o ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)` – J.Doe Feb 25 '18 at 19:18
  • Also what is linking the two `RectangleWrapper` files together? – J.Doe Feb 25 '18 at 19:19
  • Ok so I think the solution was to add `#import "RectangleWrapper.hpp"` to the `.mm` and then add the `.mm` to Target->Build Phasees->Compile Sources – J.Doe Feb 25 '18 at 19:34
  • I don't know where you are going wrong. But take a look at https://github.com/Sunhick/SwiftInterop – Sunil Feb 25 '18 at 19:43
  • Thankyou so much! So if I understand right the `RectangleWrapper.hpp` file is compiled as Objective-C++ right? Is there a reason it might not accept c structs? I added a c file and defined a struct and then imported that file in the hpp and tried to add the following method `- (void) injest:(DData)d;` but it says `Expected a type` – J.Doe Feb 25 '18 at 19:48
  • Yes. That's why i put ```RectangleWrapper.hpp``` file under objc++ wrapper. Objc++ is a superset of c. so you should be able to import c structs. – Sunil Feb 25 '18 at 19:56
  • So wait the hpp and mm files are being interpreted as different languages? – J.Doe Feb 25 '18 at 20:33
  • Yes, they are seen as objective c++ files. – Sunil Feb 25 '18 at 20:50
  • Why couldnt the header import c++ then? – J.Doe Feb 25 '18 at 20:52
  • ```RectangleWrapper.hpp``` can import ```Rectangle.hpp``` header file. But that would make ```RectangleWrapper``` non swift compatible meaning you won't be able to import it in ```Bridging-Header``` as swift cannot understand ```Rectangle.hpp```. So when you are importing headers into bridging header you have to be cautious to import only the header files that use purely objective c++ types and not raw c/c++ types. Swift can only understand objective c++ types. That's my understanding. – Sunil Feb 25 '18 at 20:56
  • So are header files like not technically written in any language then? Is the point of all of this that the type of the header files doesn't matter so long as it doesn't use or import incompatible code? It is the `.m` or the `.mm` or `.hpp` or `.cpp` or `.c` files that are truly specific to one language but they may go with any header type? – J.Doe Feb 26 '18 at 00:02
  • Also do you know how expensive the whole `Rectangle* rect = reinterpret_cast(ptr);` cast is? If the Rectangle class eventually contains like 40-50 floats would it still be reasonable to do this 60 times a second? – J.Doe Feb 26 '18 at 00:12
  • As far as i know there's no performance overhead of ```reinterpret_cast()``` it's just a compiler directive to treat bytes as if it were a different data type. Read more on [cppreference](http://en.cppreference.com/w/cpp/language/reinterpret_cast). The ones that you import into bridging header are written in objective c without any direct references to c/c++ types. That said you may have to dive deep into the overlapping features of objective c/c++ and c/c++. – Sunil Feb 26 '18 at 02:20