1

I'm a recent Swift learner and now I'm trying to use following package: https://github.com/aaronSig/Hunspell-iOS

The package itself is written using C++ and has some headers for Objective-C. I've added *-Bridging-Header.h file and have been able to use the package inside Swift code, but now I don't understand how to use it properly:

enter image description here

The comments for Objective-C headers for the library say following:

/* suggest(suggestions, word) - search suggestions
 * input: pointer to an array of strings pointer and the (bad) word
 *   array of strings pointer (here *slst) may not be initialized
 * output: number of suggestions in string array, and suggestions in
 *   a newly allocated array of strings (*slts will be NULL when number
 *   of suggestion equals 0.)
 */
LIBHUNSPELL_DLL_EXPORTED int Hunspell_suggest(Hunhandle *pHunspell, char*** slst, const char * word);

But I don't know how to get correct pointer now. I've tried to utilize answers from here: How to pass an array of Swift strings to a C function taking a char ** parameter

But this is where I got so far

naffiq
  • 1,030
  • 1
  • 9
  • 19
  • 1
    On success, `Hunspell_suggest` is going to write the location of a _new_ array into the address pointed to by `slst` — which means that you will need to pass in the address of an empty `char **` variable, and not an existing one like you have. To be honest, this C-ism is (rightfully) a pain to represent in Swift; rather than using this function directly from Swift, you're likely going to be a bit better off writing an Objective-C wrapper for the function, which you can then use from Swift. Is this acceptable for your use-case? If so, I can submit an answer. – Itai Ferber Feb 19 '23 at 20:03
  • Hi! Did you do anything additional to use the Hunspell lib? I'm trying to import it into my Swift project, but it doesn't compile if I try to use it; it throws a weird linker error (`ld: symbol(s) not found for architecture arm64`). I only added `#import "HunspellBridge.h"` to the bridging header; maybe I need anything else? – f-person Apr 13 '23 at 14:27

2 Answers2

1

The problem here is that the function parameters expect a pointer type, not an array, and you have to provide arguments correspondingly. Assuming you have a function declared like this:

#ifdef __cplusplus
extern "C" {
#endif
int foo(char*** inoutStrArr, const char* prop);
#ifdef __cplusplus
}
#endif

And defined something like this:

int foo(char*** inoutStrArr, const char* prop) {
    *inoutStrArr = new char*[] {
        "one", "two", "three", "four"
    };
    return 4;
}

It wants you to allocate a single pointer of type char*** only as an argument:

// A `char***` pointer
let inoutPtr = UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?>.allocate(capacity: 1)

// result contains number of elements
let result = Int(foo(inoutPtr, "test"))

// checks whether the function assigned anything to the passed in pointer
guard let array = inoutPtr.pointee else {
    print("No result")
    return
}
// Array of utf8 strings
let cStrings = (0 ..< result).compactMap { array[$0] }
// Array of swift strings
let strings = cStrings.map { String.init(utf8String: $0) }
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
1

I managed to get it working by using the SpellChecker class directly. My bridging header only imports "SpellChecker.h" currently:

// ProjectName-Bridging-Header.h:
#import "SpellChecker.h"

I then created a simple wrapper to make it easier to consume the lib:

class SpellCheckerWrapper {
    /// The instance of the Objective-C `SpellChecker`
    private let spellChecker: SpellChecker

    /// Used to avoid possible exceptions during `getSuggestions`.
    private var isLoaded = false
    
    init() {
        spellChecker = SpellChecker()
        // I set the language directly in the constructor but feel free
        // but feel free to make `updateLanguage` public and use it directly.
        updateLanguage("hy_AM")
        isLoaded = true
    }
    
    public func getSuggestions(for word: String) -> [String] {
        if !isLoaded {
            return []
        }
        
        let suggestions = spellChecker.getSuggestionsForWord(word)
        // Remove the log if you don't need it :)
        NSLog("[SpellCheckerWrapper] suggestions: \(suggestions ?? [])")
        return suggestions as? [String] ?? []
    }

    public func isSpeltCorrectly(_ word: String) -> Bool {
        return spellChecker.isSpeltCorrectly(word)
    }
    
    private func updateLanguage(_ language: String) {
        spellChecker.updateLanguage(language)
    }
}

I also had to update the createCharFromCFStringRef function in SpellChecker.mm because it crashed otherwise. I don't remember the exact issue, but here is the updated code in case it throws exceptions for you too:

static char *createCharFromCFStringRef(CFStringRef cfstr)
{
    if (cfstr == NULL) {
        return NULL;
    }
    
    CFIndex length = CFStringGetLength(cfstr);
    CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
    char *buffer = (char *)malloc(maxSize);
    if (CFStringGetCString(cfstr, buffer, maxSize, kCFStringEncodingUTF8)) {
        return buffer;
    }
    return NULL;
}

Hope this helps!

Edit: my full implementation can be found in https://github.com/f-person/hayatar/blob/ee8daae5e221dcf192ac2b7d572c80947453e1bb/Armenian/AutocompleteProvider.swift

f-person
  • 331
  • 5
  • 16