2

There are many tutorials to make this on web but none are clear and objective, because thats I'm prefer show my case.

I'm developing a iOS app that use a C API to connect to a service web and it have callback functions, my task is simple, I must get events generateds in C code on Swift code. To this I tried some ways, the current way that I'm trying is the follow.

Scenario

I've three files: wrapper.cpp, Bridging-Header.h and ViewController.swift

On Briding-Header.h I declared a function's pointer.

Bridging-Header.h

void callback_t(void(*f)(unsigned char*));

On wrapper.cpp I wrote my code to connect to web and use callback functions. So this is the callback method to get state connection when is disconnected.

wrapper.cpp

void callback_web_disconnected(unsigned char *c) {
    printf("Disconnected %s",c);
    // Here I want invoke a method to Swift code
    callback_t(r); // But this not works
}

It's not compiles, error message: Use of undeclared identifier 'callback_t'.

If I try write: void callback_frame(unsigned char*);, linker command failed with exit code 1 error occurs.

ViewController.swift

func callback_t(_ f: ((UnsafeMutablePointer<UInt8>?) -> Void)!) {
        print("ARRIVED ON VIEWCONTROLLER!")
        // Here I want get the error code (unsigned char*) passed as parameter
    }

I can't import Bridging-Header.h file on wrapper.cpp because cause many conflicts.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Augusto
  • 3,825
  • 9
  • 45
  • 93

1 Answers1

4

First, callback_t is not an identifier. I don't see it typedef anywhere.. Second, you need some way of telling C++ that the callback is your swift function. To do that, I pass it as a parameter to the C++ function similar to how we do it in Objective-C and Swift.. Otherwise you need to store the callback in a global variable somewhere and have C++ access it.

Using the first method of passing the callback as a parameter:

First in the C++ header (Foo.h) I did (Do NOT remove the ifdef stuff.. the compiler uses C linkage when importing to Swift but when compiling the C++ side, it'll be mangled so to make it use C linkage, we extern "C" the code):

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

    typedef void(*callback_t)(const char *);
    void callback_web_disconnected(callback_t);

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* Foo_hpp */

Then in the implementation file (Foo.cpp) I did:

#include "Foo.h"
#include <thread>
#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

    void callback_web_disconnected(callback_t callback)
    {
        std::thread t = std::thread([callback] {
            std::this_thread::sleep_for(std::chrono::seconds(2));

            if (callback)
            {
                callback("Hello World");
            }
        });

        t.detach();
    }

#ifdef __cplusplus
} // extern "C"
#endif

Then in ViewController.swift I did:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        callback_web_disconnected({
            if let ptr = $0 {
                let str = String(cString: ptr)
                print(str)
            }
        })
    }
}

It works fine. The Swift code gets called from C++ after 2 seconds has passed.


Using the second method of storing the callback in a global variable (which I despise but let's not get into that)..

In Foo.h I did:

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

    typedef void(*callback_t)(const char *);
    callback_t globalCallback; //Declare a global variable..

    void callback_web_disconnected();

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* Foo_hpp */

In Foo.cpp I did:

#include "Foo.h"
#include <thread>
#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

    void callback_web_disconnected()
    {
        std::thread t = std::thread([] {
            std::this_thread::sleep_for(std::chrono::seconds(2));

            if (globalCallback)
            {
                globalCallback("Hello World");
            }
        });

        t.detach();
    }

#ifdef __cplusplus
} // extern "C"
#endif

In ViewController.swift, I did:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        //Set the globalCallback variable to some block or function we want to be called..

        globalCallback = {
            if let ptr = $0 {
                let str = String(cString: ptr)
                print(str)
            }
        }

        //Trigger our test.. I guess your C++ code will be doing this anyway and it'll call the globalCallback.. but for the sake of this example, I've done it here..
        callback_web_disconnected()
    }
}
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • On the both options, when I try write the closure in .swift file the follow error occurs: `Cannot assign value of type '() -> ()' to type 'callback_t?' (aka 'Optional<@convention(c) (Optional>) -> ()>')` – Augusto Dec 07 '18 at 00:17
  • `callback_t` has an `UnsafePointer` argument.. but you're assigning a closure that has no arguments.. perhaps try: `let meh = callback_t = { (UnsafePointer) in }` and then assign `meh` or pass `meh` as the parameter. – Brandon Dec 07 '18 at 06:36