Search code examples
c++xcodec++11openframeworksi386

Symbol(s) not found for architecture i386 despite compiling universal and i386 versions of library


I have been developing a dynamic library in C++, which compiles and runs when tested in an xcode command line interface project and a gtest project.

However, when I try to link it in an openframeworks project the linker complains about not being able to find i386 symbols for some of the functions of one of my classes. This is particularly strange as it can see the symbols for 16 out of the 21 functions, constructors & destructor... I know this because I can call all 16 without any linker issues and get the expected results.

Here's what Xcode spits out:

Undefined symbols for architecture i386:
  "Cicada::Message::load(std::string const&)", referenced from:
      ofApp::setup() in ofApp.o
  "Cicada::Message::save(std::string const&)", referenced from:
      ofApp::setup() in ofApp.o
  "Cicada::Message::setContent(std::string)", referenced from:
      ofApp::setup() in ofApp.o
  "Cicada::Message::parse(std::string, bool)", referenced from:
      ofApp::setup() in ofApp.o
  "Cicada::Message::Message(std::string, Cicada::CALL_TO_ACTION, bool)", referenced from:
  ofApp::setup() in ofApp.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)

As a sanity check, I called lipo on my library:

> lipo -info bin/libcicada.0.0.1.dylib
> Architectures in the fat file: bin/libcicada.0.0.1.dylib are: x86_64 i386

Then I called nm to see if I could "find" the symbols it has been struggling with:

> nm -gj -arch i386 bin/libcicada.0.0.1.dylib
...
__ZN6Cicada7Message10setContentENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message11generateUIDEv
__ZN6Cicada7Message13header_lengthE
__ZN6Cicada7Message13hexifyContentEv
__ZN6Cicada7Message14generateHeaderEv
__ZN6Cicada7Message15dehexifyContentEv
__ZN6Cicada7Message4loadERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message4saveERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message5parseENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEb
__ZN6Cicada7Message5printEv
__ZN6Cicada7Message6setCTAENS_14CALL_TO_ACTIONE
__ZN6Cicada7Message8toStringEv
__ZN6Cicada7MessageC1ENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_14CALL_TO_ACTIONEb
__ZN6Cicada7MessageC1Ev
__ZN6Cicada7MessageC2ENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_14CALL_TO_ACTIONEb
__ZN6Cicada7MessageC2Ev
__ZN6Cicada7MessageD0Ev
__ZN6Cicada7MessageD1Ev
__ZN6Cicada7MessageD2Ev
...

The symbols that the linker can't see are:

__ZN6Cicada7Message10setContentENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message4loadERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message4saveERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE
__ZN6Cicada7Message5parseENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEb
__ZN6Cicada7MessageC1ENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_14CALL_TO_ACTIONEb

i.e. the main constructor, setContent, parse, save and load.

I know that 64bit functionality was only coming to openframeworks in version 9, hence the compilation for universal binary. I even tried compiling exclusively for i386, but no dice...

I've tried various architectures and SDKs for the openframeworks project but anything other than the default $(NATIVE_ARCH) prevents openframeworks itself from compiling. The library search path points to the correct locations and the I'm linking to the correct library in build phases, which is evident in that it happily compiles and runs when calls to the offending functions are commented out.

I have a nagging feeling that this is a C++11 issue, as it is used throughout my library and if my memory serves me correctly, openframeworks has problems with C++11. If anyone thinks it could be something else i'd appreciate the input!!!

Here's a copy of the problematic class...

//
//  Message.h
//  Cicada
//
//  Created by Pierre Chanquion on 06/09/2014.
//

#include "Definitions.h"
#include "Serialisable.h"

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••
// • Message: Class for  parsing, loading, saving  
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••
namespace Cicada {
    class Message : public Serialisable {
        private:
            // ----------------------------------------------->
            // content : Data (i.e. the message)
            // header  : Header
            // cta     : Call to Action
            // ishex   : Flag denoting content format
            std::string content, header;
            static const size_t header_length;
            size_t uid;
            CTA    cta;
            bool   ishex;
            ECL    ecl;
            // ----------------------------------------------->
            // Generates a header string
            void generateHeader();

            // ----------------------------------------------->
            // Generates a header string
            void generateUID();

        public:
            // ----------------------------------------------->
            // Constructors
            //  @con : content
            //  @cta : cta
            Message(std::string con, CTA cta, bool ishex = false);
            Message();

            // ----------------------------------------------->
            // Destructor
            ~Message();

            // ----------------------------------------------->
            // Print message
            void print();

            // ----------------------------------------------->
            // Stringify message
            std::string toString();

            // ----------------------------------------------->
            // Parse message
            //  @m   : stringified message to parse
            //  @hex : flag denoting payload format
            RC parse(std::string m, bool hex);

            // ----------------------------------------------->
            // Serialisation functions
            //  @fp : filepath
            virtual RC save(const std::string &fp);
            virtual RC load(const std::string &fp);

            // ----------------------------------------------->
            // Conversions between char and ascii no
            // representations
            void hexifyContent();
            void dehexifyContent();

            // ----------------------------------------------->
            // Accessors
            void setContent(std::string con);
            void setCTA(CTA cta);
            void setECL(ECL ecl) { this->ecl = ecl;}

            inline void setHex(bool b){ ishex = b;}
            inline std::string getContent() { return content; }
            inline std::string getHeader() { return header; }
            inline CTA    getCTA() { return cta; }
            inline size_t getUID() { return uid; }
            inline bool   isHex() { return ishex; }
    };
}

//
//  Message.cpp
//  Cicada
//
//  Created by Pierre Chanquion on 06/09/2014.
//

#include "Message.h"
#include "easylogging++.h"
#include "Utilities.h"
#include <iomanip>
#include <iostream>
#include <string>
using namespace std;
using namespace Cicada;
using namespace Cicada::Utilities;

const size_t Cicada::Message::header_length = 10;
// -----------------------------------------------
// Constructors
Cicada::Message::Message(string con, CTA cta, bool ishex){
    this->content = con;
    this->cta     = cta;
    this->ishex   = ishex;

    generateHeader();
    generateUID();
}
Cicada::Message::Message(){
    cta   = CTA::UNKNOWN;
    ishex = false;
}
Cicada::Message::~Message(){}

// -----------------------------------------------
// Print message to console
void Cicada::Message::print(){
    cout << "====> CICADA MESSAGE" << endl;
    cout << "\t===> CTA     : "   << cta             << endl;
    cout << "\t===> CONTENT : "   << content        << endl;
    cout << "\t===> SIZE    : "   << content.length() << " Bytes" <<endl;
}

// -----------------------------------------------
// Generate a header string for this message
void Cicada::Message::generateHeader(){
    ostringstream ss;
    ss << "SU"<< setfill('0') << setw(2) << cta << "N" << setfill('0') << setw(4) << content.length() << "T";
    header = ss.str();
}

// -----------------------------------------------
// Generate UID
void Cicada::Message::generateUID(){

    string s = toString();
    uid      = 0;
    for (char &c : s)
        uid = uid * 101 + c;
}

// -----------------------------------------------
// Parse message
RC Cicada::Message::parse(string m, bool hex){

    LOG(INFO) << "Parsing Message...";

    ostringstream ss;
    string        h, c;
    size_t        s;
    short         _0 = 48, _9 = 57;
    CTA           _cta;
    RC            r = RC::SUCCESS;

    // Compartor: check whether value is within a particular range
    auto in_range   = [] (int n, int mn, int mx) { return (n >= mn && n <= 57); };

    // Generate error codes
    auto error_code = [] (RC _r, RC _c) { return static_cast<RC>(_r == RC::SUCCESS ? _c : _r | _c); };

    // Check Header Length
    LOG(INFO) << "Check Header Length.";
    auto v = split(m, "T");

    if(v.size() == 1 || v[0].length() < header_length-1){
        LOG(ERROR) << "Corrupt Header: LENGTH INVALID!";
        r = error_code(r, RC::ERR_CORRUPT_HEADER);
        if(ecl == B_STRICT_PARSE) return r;
    }
    ss << v[0] << "T";
    h  =  ss.str(); // Header
    c  = v[1];      // Content

    LOG(INFO) << "Check Payload Length.";
    if((hex && (c.length()-1) % 2 != 0) || c.length() == 0){
        LOG(ERROR) << "Corrupt Payload: LENGTH INVALID!";
        r = error_code(r, RC::ERR_CORRUPT_PAYLOAD);
        if(ecl == B_STRICT_PARSE) return r;
    }
    LOG(INFO) << "Dehex String.";
    // Dehex string
    if(hex) c = hexStringToString(split(c, "Y")[0]);

    // Check CTA Chunk
    LOG(INFO) << "Check CTA Chunk.";
    string t = h.substr(1,3);
    if(t[0] != 'U' || !in_range(t[1], _0, _9) || !in_range(t[2], _0, _9)){
        LOG(ERROR) << "Corrupt Header: CTA CHUNK INVALID!";
        r = error_code(r, RC::ERR_CORRUPT_HEADER);
        if(ecl == B_STRICT_PARSE) return r;
    }
    _cta = (CTA) atoi(t.substr(1).c_str());

    // Check Length Chunk
    LOG(INFO) << "Check Length Chunk.";
    t = h.substr(4, 5);
    if(t[0] != 'N' || !in_range(t[1], _0, _9) || !in_range(t[2], _0, _9) || !in_range(t[3], _0, _9) || !in_range(t[4], _0, _9)){
        LOG(ERROR) << "Corrupt Header: SIZE CHUNK INVALID!";
        r = error_code(r, RC::ERR_CORRUPT_HEADER);
        if(ecl == B_STRICT_PARSE) return r;
    }
    s = atoi(t.substr(1).c_str());

    LOG(INFO) << "Check Content Chunk.";
    // Check content chunk
    if(s != c.length()){
        LOG(ERROR) << "Corrupt Payload: LENGTH DOES NOT MATCH HEADER SIZE CHUNK VALUE!";
        r = error_code(r, RC::ERR_CORRUPT_PAYLOAD);
        if(ecl == B_STRICT_PARSE) return r;
    }

    // Set content and cta.
    content = c;
    cta     = _cta;

    // Generate header string
    generateHeader();

    LOG(INFO) << "Message Parsed => content="<<content<<" cta="<<cta;

    return r;
}

// -----------------------------------------------
// Concatenates header and content
string Cicada::Message::toString(){
    ostringstream ss;
    ss << header << content;
    return ss.str();
}

// -----------------------------------------------
// Saves Message to file
RC Cicada::Message::save(const string &fp){

    ofstream outfile;
    ostringstream ss;

    // Append file type to fp
    ss << fp << ".bin";

    // Open File
    outfile.open(ss.str().c_str(), ios::binary);
    if (!outfile.is_open()) {
        LOG(ERROR) << "Unable to open file at filepath: " << ss.str();
        return RC::ERR_OPENING_FILE;
    }

    // Write to File
    outfile << "CTA: " << cta     << "\n";
    outfile << "CONTENT: " << content << "\n";

    outfile.close();

    return RC::SUCCESS;
}

// -----------------------------------------------
// Loads Message from file
RC Cicada::Message::load(const string &fp){

    ifstream  infile;
    ostringstream ss;

    // Append file type to fp
    ss << fp << ".bin";

    // Open File
    infile.open(ss.str().c_str(), ios::binary);
    if (!infile.is_open()) {
        LOG(ERROR) << "Unable to open file at filepath: " << ss.str().c_str();
        return RC::ERR_OPENING_FILE;
    }

    // Read From File
    while (infile.good()) {
        string row;
        if (getline(infile, row)) {
            auto s = Utilities::split(row, ": ");
            if(s.at(0) == "CTA")
                cta = (CTA) atoi(s.at(1).c_str());
            else if(s.at(0) == "CONTENT")
                content = s.at(1);
        }
    }

    return RC::SUCCESS;
}

// -----------------------------------------------
// Convert content to hex version
void Cicada::Message::hexifyContent(){
    ostringstream c;
    c << Utilities::stringToHexString(content) << "Y";
    content = c.str();
    ishex   = true;
}

// -----------------------------------------------
// Convert hex content to original version
void Cicada::Message::dehexifyContent(){
    content = Utilities::hexStringToString(split(content, "Y")[0]);
    ishex   = false;
}

// -----------------------------------------------
// Set content and generate header/UID
void Cicada::Message::setContent(string con) {
    content = con;
    generateHeader();
    generateUID();
}

// -----------------------------------------------
// Set content and generate header/UID
void Cicada::Message::setCTA(CTA cta) {
    this->cta = cta;
    generateHeader();
    generateUID();
}

Edit: Here's ofApp:setup ...

void ofApp::setup(){

    try {
        buffer = new float[ws]();
        inwavebuf = new float[ws]();
    } catch (bad_alloc e) {
        cout << "Unable to allocate memory for " << e.what() << endl;
        exit();
    }

    message.hexifyContent();
    message.dehexifyContent();
    message.getContent();
    message.getCTA();
    message.getHeader();
    message.generateUID();
    message.generateHeader();
    message.getUID();
    message.isHex();
    message.load("");
    message.save("");
    message.toString();
    message.setHex(true);
    message.setECL(B_STRICT_PARSE);
    message.print();
    message.setContent("");
    message.setCTA(UNKNOWN);
    message.parse("010003414243", true);
    message = Message("", UNKNOWN);

    Message *msg = new Message("", UNKNOWN);

    ofBackground(0);
    setupGui();

}

Solution

  • Turns out Openframeworks doesn't like C++11 binaries... I fixed this by not using open frameworks for the application in question.