How to Bridge C Code to Create Swift Barcode Reader on Mac

Have you implemented any barcode reader software in Swift on Mac OS X? Since Dynamsoft released the 1D/2D barcode SDK for Mac, I was wondering how I can bridge the C dylib with Swift. Why Swift, not Objective-C? Because Apple is encouraging developers to migrate to the new programming language, it is important to master the new tool if you want to create more excellent apps for Mac and iOS in the future. Swift is totally new to me, and thus I spent two days googling relevant resources and finally figured out the solution.

Prerequisites

Using Swift with C

It is recommended to read the e-book – Using Swift with Cocoa and Objective-C – published by Apple in iBooks. To quickly get familiar with how to make Swift work with C language types and features, we can pick the chapter Interacting with C APIs. You may use the following primitive type mapping and pointer mapping when interoperating Swift and C.

primitive type mapping

pointer mapping

Implementing 1D/2D Barcode Reader with Swift and C on Mac

Open Xcode, and then press Command+Shift+N to create a new project. I will demo both command line tool and Cocoa application.

create swift project

After initializing the new project, we need to import all relevant header files and libraries to the project. The quickest way is to use your mouse to drag header files and libraries to your project. Xcode will automatically add them to project configuration files.

Press Command+N to add a C file, which will be used to invoke Dynamsoft Barcode dylib.

create c for swift project

Once it’s done, you will see an alert dialog as follows:

objective-c bridging header

If press Yes, Xcode will automatically generate an Objective-C bridging header file. I had created native_lib.c and native_lib.h, so I opened the bridging header file and add the following line:

#import "native_lib.h"

Referring to the online sample of Dynamsoft Barcode SDK, we can make a few changes:

#include "native_lib.h"

int dbr_release_memory(pBarcodeResultArray paryResult)
{
    DBR_FreeBarcodeResults(&paryResult);
    printf("Game Over\n");
    return 0;
}

pBarcodeResultArray dbr_decodeBarcodeFile(char* pszImageFile)
{
    // Parse command
    __int64 llFormat = (OneD |QR_CODE);

    int iMaxCount = 0x7FFFFFFF;
    int iIndex = 0;
    ReaderOptions ro = {0};
    pBarcodeResultArray paryResult = NULL;
    int iRet = -1;
    char * pszTemp = NULL;
    char * pszTemp1 = NULL;
    struct timeval begin, end;

    if (NULL == pszImageFile)
    {
        printf("The syntax of the command is incorrect.\n");
        return NULL;
    }

    // Set license
    DBR_InitLicense("A825E753D10C6CAC7C661140EC5ABEC3");

    // Read barcode
    gettimeofday(&begin, NULL);
    ro.llBarcodeFormat = llFormat;
    ro.iMaxBarcodesNumPerPage = iMaxCount;
    iRet = DBR_DecodeFile(pszImageFile, &ro, &paryResult);
    gettimeofday(&end, NULL);

    // Output barcode result
    pszTemp = (char*)malloc(4096);
    if (iRet != DBR_OK)
    {
        sprintf(pszTemp, "Failed to read barcode: %s\r\n", DBR_GetErrorString(iRet));
        printf("%s", pszTemp);
        free(pszTemp);
        return NULL;
    }

    if (paryResult->iBarcodeCount == 0)
    {
        sprintf(pszTemp, "No barcode found. Total time spent: %.3f seconds.\r\n",
                ((float)((end.tv_sec * 1000 * 1000 +  end.tv_usec) - (begin.tv_sec * 1000 * 1000 + begin.tv_usec))/(1000 * 1000)));
        printf("%s", pszTemp);
        DBR_FreeBarcodeResults(&paryResult);
        return 0;
    }

    sprintf(pszTemp, "Total barcode(s) found: %d. Total time spent: %.3f seconds\r\n\r\n", paryResult->iBarcodeCount,
            ((float)((end.tv_sec * 1000 * 1000 +  end.tv_usec) - (begin.tv_sec * 1000 * 1000 + begin.tv_usec))/(1000 * 1000)));
    printf("%s", pszTemp);

    return paryResult;
}

As for the corresponding header file, add the following code:

#ifndef __DBRConsole__native_lib__
#define __DBRConsole__native_lib__

#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "If_DBR.h"

pBarcodeResultArray dbr_decodeBarcodeFile(char* fileName);
int dbr_release_memory(pBarcodeResultArray paryResult);
const char * GetFormatStr(__int64 format);

#endif /* defined(__DBRConsole__native_lib__) */

Command Line Tool in Swift

When writing Swift code, I need to convert Swift String to Char* in order to pass image path.

var file: String = "/Applications/Dynamsoft/Barcode Reader 3.0 Trial/Images/AllSupportedBarcodeTypes.tif" // barcode file
//let namePtr = strdup(filePath.bridgeToObjectiveC().UTF8String)

var filePtr = strdup(file.cStringUsingEncoding(NSUTF8StringEncoding)!)
var fileName: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(filePtr)

I found someone answered how to convert NSString to Char* with the commented line on StackOverflow. The method bridgeToObjectiveC was supported in earlier Xcode 6.x but has been removed in Xcode 6.4.

Get the barcode results:

var result : pBarcodeResultArray = dbr_decodeBarcodeFile(fileName)

free(filePtr)

println("Total barcode: \(String(result.move().iBarcodeCount))\n.......")

var count = result.move().iBarcodeCount
var pBarcodeResult: pBarcodeResult = nil
var barcodeIndex = 1

// print barcode recognition results
for i in 0..<Int(count) {
    pBarcodeResult = result.move().ppBarcodes.advancedBy(i).move()
    println("Barcode: \(barcodeIndex++)")
    println("Page: \(String(pBarcodeResult.move().iPageNum))")
    var lFormat: __int64 = pBarcodeResult.move().llFormat
    var format = String.fromCString(GetFormatStr(lFormat))
    println("Type: \(format!)")
    println("Value: \(String.fromCString(pBarcodeResult.move().pBarcodeData)!)")
    println(".......")

}

// free C memory
dbr_release_memory(result)

barcode console output

Cocoa Application in Swift

Create buttons and text fields in AppDelegate.swift:

@IBOutlet weak var window: NSWindow!
@IBOutlet weak var btLoad: NSButton!
@IBOutlet weak var btRead: NSButton!
@IBOutlet weak var text: NSTextField!
@IBOutlet weak var filePath: NSTextField!

Create a function to receive click event:

@IBAction func onClick(sender: NSButton) {
        var title = sender.title
        switch(title) {
        case "Load Barcode File":
            dispatch_async(dispatch_get_main_queue()) {
                self.openPanel()
            }
            break
        case "Read Barcode":

            if self.filePath.stringValue == "" {
                text.stringValue = "Please add image path!"
                return
            }

            println("default:" + self.filePath.stringValue)
            var dbr = DBR()
            text.stringValue = dbr.decodeFile(self.filePath.stringValue)!
            break
        default:
            break
        }

    }

In interface builder, connect outlets to UI elements.

app delegate

Using NSOpenPanel to load files:

func openPanel() {
        var openPanel = NSOpenPanel()
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = true
        openPanel.beginWithCompletionHandler { (result) -> Void in
            if result == NSFileHandlingPanelOKButton {
                if let path = openPanel.URL?.path {
                    self.filePath.stringValue = path
                }
            }
        }

    }

Create DBR.swift for reading barcode images:

import Foundation

class DBR {
    func decodeFile(file: String)->String? {

        var filePtr = strdup(file.cStringUsingEncoding(NSUTF8StringEncoding)!)
        var fileName: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(filePtr)
        var result : pBarcodeResultArray = dbr_decodeBarcodeFile(fileName)

        free(filePtr)

        println("Total barcode: \(String(result.move().iBarcodeCount))\n.......")

        var count = result.move().iBarcodeCount
        var barcodeIndex = 1
        var results: String = ""
        // print barcode recognition results
        for i in 0..<Int(count) {
            var pBarcodeResult = result.move().ppBarcodes.advancedBy(i).move()

            results += "Barcode: \(barcodeIndex++)\n"
            results += "Page: \(String(pBarcodeResult.move().iPageNum))\n"

            var lFormat: __int64 = pBarcodeResult.move().llFormat
            var format = String.fromCString(GetFormatStr(lFormat))

            results += "Type: \(format!)\n"
            results += "Value: \(String.fromCString(pBarcodeResult.move().pBarcodeData)!)\n"
            results += ".......\n"

        }

        // free C memory
        dbr_release_memory(result)

        return results
    }
}

Run the 1D/2D barcode reader with GUI:

swift barcode reader

Source Code

https://github.com/yushulx/swift-barcode-reader