Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
97 views
in Technique[技术] by (71.8m points)

ios - How can i update swift variable when c++ callback function triggered

I would like to notify swift code whenever something changes or event triggered in my .cpp functions.

I've following structure in my app.

(.cpp)[.hpp] ->(.mm)[.h] -> .swift

I can handle changeThisString from swift code via :

let swiftString = CPPWrapper().MyMethodWrapper()

this is okay with button click / viewDidLoad but i would like to update this value whenever i set it up from c++.

If C++ passes new string to swift, it shouldn't wait the button click it should work like listener.

I'll be really glad for any kind of help, thank you.

Example:

my.cpp:

std::string changeThisString = "";

...

virtual void myCallBack(MyCallBackParam &paramter){
    changeThisString = "I WANT TO SEE THIS MESSAGE ON MY APP!"
}

std::string MyClass::MyMethod() {
    return changeThisString;
}

my.hpp:

#include <string>

class MyClass{
public:
    std::string MyMethod();
};

wrapper.mm

#import "wrapper.h"
#import "my.hpp"

    @implementation CPPWrapper
    
    MyClass myClass;
    
    - (NSString*) MyMethodWrapper {
        NSString* result = [NSString stringWithUTF8String:myClass.MyMethod().c_str()];
        return result;
    }
    @end

Wrapper.h

#import <Foundation/Foundation.h>

@interface CPPWrapper : NSObject
-(NSString*) MyMethodWrapper;
@end

.swift

let swiftString = CPPWrapper().MyMethodWrapper()
question from:https://stackoverflow.com/questions/65680353/how-can-i-update-swift-variable-when-c-callback-function-triggered

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is an example of C callback that triggers a Combine notification.

( Moved on GitHub right here : https://github.com/moosefactory/C-callback-to-Swift )

This example changes a value of a C String in a C Library and displays it in a field in a SwiftUI view.

enter image description here

You don't need to go through Objective-C.

  • The first part is the C Library ( Few changes to make it work with C++ )

  • The second part is the C<->Swift class

I think it's nice to have an object that makes the bridge between your C code and your swift application, to remove esoteric syntax from the app code. In this example, the file MyCLibraryInterfacedoes the job.

This class is an observable object that will publish the value change using combine, so it goes a bit beyond the question - You can stop there and do what you want once you are in the callback block. Note that we can't catch the swift context in c calls ( no calls to self or variables declared on the heap )

  • The third part is a simple SwiftUI app that receive change and update interface

C Library

  • MyCLibrary.h
#ifndef MyCLibrary_h
#define MyCLibrary_h

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


/// The callback to call when the string is changed
typedef void callback_t(const char* string);

void setCallBack(callback_t _callback);

/// A function that will change the string
void setString(const char* string);

void startTimer(void);

void cancelTimer(void);

#endif /* MyCLibrary_h */
  • MyCLibrary.c
#include "MyCLibrary.h"

const char* myString;

dispatch_queue_t queue;
dispatch_source_t timer;
bool running;

callback_t* callback;

void setCallBack(callback_t _callback) {
    callback = _callback;
}

void setString(const char* string) {
    myString = string;
    callback(myString);
}

/// A function that will start a timer that changes string

int ticks = 0;

void idle(dispatch_source_t timer)
{
    ticks++;
    char ticksStr[32];
    sprintf(ticksStr, "Time : %ds", ticks);
    setString(ticksStr);
}

void startTimer() {
    if (running) { cancelTimer(); sleep(1); }
    
    queue = dispatch_queue_create("timerQueue", 0);

    // Create dispatch timer source
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    dispatch_source_set_event_handler(timer, ^{idle(timer);});

    dispatch_source_set_cancel_handler(timer, ^{
        dispatch_release(timer);
        dispatch_release(queue);
    });

    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);

    // Set timer
    dispatch_source_set_timer(timer, start, NSEC_PER_SEC, 0);

    ticks = 0;
    running = true;
    dispatch_resume(timer);
}

void cancelTimer() {
    running = false;
    dispatch_source_cancel(timer);
    char ticksStr[32];
    sprintf(ticksStr, "Canceled after %ds", ticks);
    setString(ticksStr);
}

C<>Swift Part

  • MyApp-Bridging-Header.h
#import "MyCLibrary.h"
  • MyCLibraryInterface.swift
import Foundation

class MyCLibraryInterface: ObservableObject {
    @Published var string: String = "This is a string"
    
    static let shared = MyCLibraryInterface()
    
    init() {
        setCallBack { stringPtr in
            let newString = CFStringCreateWithCString(kCFAllocatorDefault, stringPtr, kCFStringEncodingASCII) ?? "" as CFString
            DispatchQueue.main.async {
                MyCLibraryInterface.shared.string = newString as String
            }
        }
    }
    
    func setLibString(string: String) {
        string.withCString { stringPointer in
            setString(stringPointer)
        }
    }
    
    func startLibTimer() {
        startTimer()
    }

    func cancelLibTimer() {
        cancelTimer()
    }
}

SwiftUI Sample

This sample app present the intial string and a button. On click or tap, the setString function is called in the CLibrary, the swift callback is called and the view is updated following the ObservableObject modification

import SwiftUI

struct ContentView: View {
    @ObservedObject var myCLibInterface: MyCLibraryInterface = MyCLibraryInterface.shared
    
    var body: some View {
        VStack {
            Text(myCLibInterface.string).frame(width:150).padding()
            Button("Reset") {
                myCLibInterface.setLibString(string: "C Timer Example")
            }.padding()
            Button("Start Timer") {
                myCLibInterface.startLibTimer()
            }.padding()
            Button("Cancel") {
                myCLibInterface.cancelLibTimer()
            }.padding()
        }.padding(20)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...