is there an example of protobuf with text output? - c++

I want to use protobuf and to create the serialization output file in text format for testing and for a replacement of json. I can't figure out how to write it on my own and am looking for examples.
Here is the one on binary output :
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n');
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// Add an address.
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
Can I just do some minor changes in this one to output in text format or something else needs to be done? Please either suggest the changes required or any link where code exists (in any language).

The debug string output is guaranteed to be valid text-serialized format, but does not care about whether the protocol message is actually valid:
std::string s = msg.DebugString(); // or ShortDebugString
If you want to validate, use TextFormat::PrintToString:
#include <google/protobuf/text_format.h>
if (std::string s; google::protobuf::TextFormat::PrintToString(msg, &s)) {
std::cout << "Your message: " << s;
} else {
std::cerr << "Message not valid (partial content: "
<< msg.ShortDebugString() << ")\n";
}
Tools for JSON interop are available in json_util.h.

This code will serialise protobuf messages to JSON and deserialise JSON to protobuf messages.
This is lifted straight out of production code (which I own and hereby grant you licence to use, but please credit me).
This is linked against protobuf 3.
Header:
struct pretty_json_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.add_whitespace = true;
}
};
static constexpr pretty_json_type pretty_json{};
struct compact_json_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.add_whitespace = false;
}
};
static constexpr compact_json_type compact_json{};
struct include_defaults_type {
void operator()(google::protobuf::util::JsonOptions& opts) const {
opts.always_print_primitive_fields = true;
}
};
static constexpr include_defaults_type include_defaults{};
template<class...Options>
auto json_options(Options&&...options)
{
google::protobuf::util::JsonOptions opts;
using expand = int [];
void(expand{
0,
((options(opts)),0)...
});
return opts;
}
std::string as_json(const google::protobuf::Message& msg,
google::protobuf::util::JsonOptions opts = json_options(pretty_json,
include_defaults));
std::string as_json(const google::protobuf::Message* msg,
google::protobuf::util::JsonOptions opts = json_options(pretty_json,
include_defaults));
google::protobuf::Message& from_json(google::protobuf::Message& msg,
const char* first,
std::size_t size);
inline
decltype(auto) from_json(google::protobuf::Message& msg,
const std::string& json)
{
return from_json(msg, json.data(), json.length());
}
Implementation
std::string as_json(const google::protobuf::Message& msg,
google::protobuf::util::JsonOptions opts)
{
namespace pb = google::protobuf;
namespace pbu = google::protobuf::util;
auto buffer = msg.SerializeAsString();
std::string result;
pb::io::ArrayInputStream zistream(buffer.data(), buffer.size());
auto resolver = std::unique_ptr<pbu::TypeResolver> {
pbu::NewTypeResolverForDescriptorPool("",
pb::DescriptorPool::generated_pool())
};
auto status = google::protobuf::util::BinaryToJsonString(resolver.get(),
"/" + msg.GetDescriptor()->full_name(),
buffer,
std::addressof(result),
opts);
if (!status.ok())
{
std::ostringstream ss;
ss << status;
throw std::runtime_error(ss.str());
}
return result;
}
std::string as_json(const google::protobuf::Message* msg,
google::protobuf::util::JsonOptions opts)
{
return as_json(*msg, opts);
}
google::protobuf::Message& from_json(google::protobuf::Message& msg,
const char* first,
std::size_t size)
{
namespace pb = google::protobuf;
namespace pbu = google::protobuf::util;
auto resolver = std::unique_ptr<pbu::TypeResolver> {
pbu::NewTypeResolverForDescriptorPool("", pb::DescriptorPool::generated_pool())
};
auto zistream = std::make_unique<pb::io::ArrayInputStream>(first,
size);
auto binary_buffer = std::string {};
binary_buffer.reserve(size);
auto zostream = std::make_unique<pb::io::StringOutputStream>(std::addressof(binary_buffer));
auto status = pbu::JsonToBinaryStream(resolver.get(),
"/" + msg.GetDescriptor()->full_name(),
zistream.get(), zostream.get());
zistream.reset();
zostream.reset();
if (msg.ParseFromString(binary_buffer))
{
return msg;
}
throw std::runtime_error("invalid message");
}

To convert a message to JSON in three lines of code, do this -
#include <google/protobuf/util/json_util.h>
static std::string ProtoToJson(const google::protobuf::Message& proto)
{
std::string json;
google::protobuf::util::MessageToJsonString(proto, &json);
return json;
}

Related

Windows service cannot read file with owner other than LOCAL_SERVICE

I have a Windows service written in C++ with the Win32 API. Before entering in "service" mode, the C++ program tries to read a configuration file specified as an absolute path. But the program cannot read it and exits. Some debugging leaves me to suspect that this is because of file ownership.
Question is, how can I modify the file ownership (preferably with a power-shell script) , so that the file can be read?
Here are the relevant parts
main program (exits, file cannot be read)
int main()
{
std::string cfg_file_name = config::get_config(config::comm_config_file);
if (cfg.read(cfg_file_name) < 0)
{
events::start_log(cfg.log_path, cfg.log_spdlog_level);
SPDLOG_CRITICAL("Cannot read: " + cfg_file_name);
return 1;
}
function get_config() uses some Win32 API calls to get the executable path (where the file is located) and concatenates it with the file name, to get an absolute path
std::string config::get_config(const std::string& config_name)
{
#ifdef _MSC_VER
std::string s = config::get_executable_path();
//this is done before log starts; it will be written to C:\Windows\System32 as a first log/debugging tool
std::ofstream ofs("comm.txt");
ofs << "GetModuleFileName: " << s << std::endl;
TCHAR buf[MAX_PATH];
GetCurrentDirectory(MAX_PATH, buf);
ofs << "GetCurrentDirectory: " << buf << std::endl;
//change the current directory of the process to be the executable path
if (SetCurrentDirectory(s.c_str()) == 0)
{
ofs << "SetCurrentDirectory" << std::endl;
}
GetCurrentDirectory(MAX_PATH, buf);
ofs << "GetCurrentDirectory: " << buf << std::endl;
s += config_name;
ofs.close();
return s;
#else
return config_name;
#endif
}
service is created by a power-shell script
sc.exe create _comm_ftp_server binPath= "$install_dir\ftp_server.exe" start= auto obj= "NT AUTHORITY\LocalService" password= " "
to debug it, I wrote a simple test service that writes a file and reads that same file, with no problem (so, a file can be read)
int main(int argc, char* argv[])
{
std::string path = config::get_executable_path();
cfg.log_path = path;
events::start_log(cfg.log_path, "trace", true);
//A service process has a SERVICE_TABLE_ENTRY structure for each service that it can start.
//The structure specifies the service name and a pointer to the service main function for that service.
//The main function of a service program calls the StartServiceCtrlDispatcher
//function to connect to the service control manager (SCM)
SERVICE_TABLE_ENTRY service_table[] =
{
{ (LPSTR)service_name, (LPSERVICE_MAIN_FUNCTION)service_main },
{ NULL, NULL }
};
if (StartServiceCtrlDispatcher(service_table))
{
return 0;
}
else
{
return 1;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_main
/////////////////////////////////////////////////////////////////////////////////////////////////////
void WINAPI service_main(DWORD argc, LPTSTR* argv)
{
service_handle = RegisterServiceCtrlHandler(service_name, service_handler);
if (service_handle == NULL)
{
return;
}
service_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (service_stop_event == NULL)
{
return;
}
report_status(SERVICE_START_PENDING);
report_status(SERVICE_RUNNING);
SPDLOG_INFO("service running..." + std::to_string(current_state));
HANDLE thread_service = 0;
thread_service = CreateThread(NULL, 0, service_thread, NULL, 0, NULL);
WaitForSingleObject(thread_service, INFINITE);
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service shutdown requested
/////////////////////////////////////////////////////////////////////////////////////////////////////
CloseHandle(thread_service);
report_status(SERVICE_STOP_PENDING);
SPDLOG_INFO("service stop pending..." + std::to_string(current_state));
CloseHandle(service_stop_event);
report_status(SERVICE_STOPPED);
SPDLOG_INFO("service stopped..." + std::to_string(current_state));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_thread
/////////////////////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI service_thread(LPVOID lpParam)
{
std::string path = cfg.log_path;
SPDLOG_INFO("service started in..." + cfg.log_path);
path += "\\test.txt";
size_t i = 0;
while (WaitForSingleObject(service_stop_event, 0) != WAIT_OBJECT_0)
{
write_txt_file(path, "writing...#" + std::to_string(i));
i++;
Sleep(10000);
read_txt_file(path);
}
return ERROR_SUCCESS;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//write_txt_file
/////////////////////////////////////////////////////////////////////////////////////////////////////
void write_txt_file(const std::string& file_name, const std::string& input)
{
FILE* f = fopen(file_name.c_str(), "a+");
fprintf(f, "%s\n", input.c_str());
fclose(f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//read_txt_file
/////////////////////////////////////////////////////////////////////////////////////////////////////
void read_txt_file(const std::string& file_name)
{
std::ifstream ifs;
ifs.open(file_name);
if (!ifs.is_open())
{
SPDLOG_ERROR("Cannot open: " + file_name);
return;
}
std::string line;
while (std::getline(ifs, line))
{
SPDLOG_INFO("Line: " + line);
}
ifs.close();
}
Examining the file written by the test service in Windows explorer (Properties->Details) reveals a file owner as LOCAL_SERVICE
The file that must be read has owner "Administrators"
This leaves me to suspect that this is the problem. How can the file ownership be changed, or is there a way to create the service with privileges that can read any file ?
reference for SC.EXE Create
https://learn.microsoft.com/en-US/windows-server/administration/windows-commands/sc-create
To read the file, std::ifstream is used (default read only)
int config::config_t::read(const std::string& fname)
{
try
{
std::ifstream ifs(fname);
ifs >> configuration_json;
ifs.close();
from_json(configuration_json, *this);
}
catch (const std::exception& e)
{
SPDLOG_ERROR(e.what());
return -1;
}
return 0;
}
The read error was because the library JSON for modern C++
https://github.com/nlohmann/json
detects an error reading the last entry of this file because of the comma ","
{
"archive_path":"D:\\archive",
"test_comm_input_path":"D:\\test_comm_input_path",
}
in the reading function
int config::config_t::read(const std::string& fname)
{
//this is done before log starts; it will be written to the executable path as a first log/debugging tool in service mode
std::ofstream ofs("comm.txt");
try
{
std::ifstream ifs;
std::ios_base::iostate mask = ifs.exceptions() | std::ios::failbit;
ifs.exceptions(mask);
ifs.open(fname);
if (!ifs.is_open())
{
ofs << "open fail: " << fname << std::endl;
ofs.close();
return -1;
}
else
{
ofs << "open: " << fname << std::endl;
}
ifs >> configuration_json;
ifs.close();
from_json(configuration_json, *this);
ofs << "read: " << fname << std::endl;
ofs.close();
}
catch (const std::exception& e)
{
ofs << "json read: " << e.what() << std::endl;
ofs.close();
return -1;
}
return 0;
}
where
void from_json(const nlohmann::json& j, config::config_t& c)
{
if (j.contains("archive_path"))
{
j.at("archive_path").get_to(c.archive_path);
}
}

file reading in debian;

I need to read config file, containing key value pairs.
File contains several strings like this:
key1=value1
key2=value2
keyN=valueN
***//terminate
class CFG
{
public:
static void Init(char* path_);
static string Param(string name_);
static string PrintAll();
private:
static void GetPathEXE(string path_);
static void ParseConfigFile();
static map<string, string> mapData;
};
void CFG::ParseConfigFile()
{
ifstream file;
file.open(Param("HomePath") + Param("Slash") + "ConfigMS.cfg");
if (file.is_open())
{
string file_line;
cmatch results;
regex reg3("(.*)=(.*)", std::regex_constants::ECMAScript);
std::cout<<"Parsing cfg file"<<endl;
while (getline(file, file_line) && (file_line.substr(0, 3) != "***"))
{
std::cout<<file_line<<endl;
std::regex_match(file_line.c_str(), results, reg3);
mapData.insert(std::pair<string,string>(results.str(1),results.str(2) ));
mapData[string(results.str(1))] =string( results.str(2));
std::cout<<"key: "<<results.str(1)<<" val: "<<results.str(2)<<endl;
}
//mapData["OuterIP"] = "10.77.1.68";
std::cout<<"Config loaded\n";
for (auto it : mapData)
{
std::cout<<it.first<<"="<<it.second<<endl;
}
if (Param("OuterIP") == "") mapData["Error"] = "IP for receiving messages not set in *.cfg file.\n";
//else if (data["sipName"] == "") error = "sipName for receiving messages not set in *.cfg file.\n";
}
else { mapData["Error"] = "Could not open *.cfg file. Check its existance or name. Name Must \"be ConfigMS.cfg\".\n"; }
if (Param("RTPPort") == Param("MGCPPort"))
{
mapData["Error"] = "RTP port is same as MGCP port. Please change one of them.\n";
}
if (Param("Error") != "")
{
cout << "\n" + Param("Error");
//system("pause");
exit(-1);
}
}
string CFG::Param(string name_)
{
return mapData[name_];
}
If I rely on file input i have [OuterIP,10.77.1.68] key value pair in my map, but function CFG::Param("OuterIP") returns empty string; And I have no idea how to work around this problem;
I've compiled you code with small modifications and it works.
rafix07 told us the program exits if there is no MGCPort and RTPPort values, and he is right, but you told explicitly that the function returns an empty string. Are you sure there isn't another empty OuterIP parameter in the file?
BTW my modifications are removing the path, reading just the file and commenting out the exit(-1) thing. (As well as adding a main function.)
#include <regex>
#include <map>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
class CFG
{
public:
static void Init(char* path_);
static string Param(string name_);
static void ParseConfigFile();
private:
static void GetPathEXE(string path_);
static map<string, string> mapData;
};
void CFG::ParseConfigFile()
{
ifstream file;
file.open("ConfigMS.cfg");
if (file.is_open())
{
string file_line;
cmatch results;
regex reg3("(.*)=(.*)", std::regex_constants::ECMAScript);
std::cout<<"Parsing cfg file"<<endl;
while (getline(file, file_line) && (file_line.substr(0, 3) != "***"))
{
std::cout<<file_line<<endl;
std::regex_match(file_line.c_str(), results, reg3);
mapData.insert(std::pair<string,string>(results.str(1),results.str(2) ));
mapData[string(results.str(1))] =string( results.str(2));
std::cout<<"key: "<<results.str(1)<<" val: "<<results.str(2)<<endl;
}
//mapData["OuterIP"] = "10.77.1.68";
std::cout<<"Config loaded\n";
for (auto it : mapData)
{
std::cout<<it.first<<"="<<it.second<<endl;
}
if (Param("OuterIP") == "") mapData["Error"] = "IP for receiving messages not set in *.cfg file.\n";
//else if (data["sipName"] == "") error = "sipName for receiving messages not set in *.cfg file.\n";
}
else { mapData["Error"] = "Could not open *.cfg file. Check its existance or name. Name Must \"be ConfigMS.cfg\".\n"; }
if (Param("RTPPort") == Param("MGCPPort"))
{
mapData["Error"] = "RTP port is same as MGCP port. Please change one of them.\n";
}
if (Param("Error") != "")
{
cout << "\n" + Param("Error");
//system("pause");
//exit(-1);
}
}
string CFG::Param(string name_)
{
return mapData[name_];
}
map<string, string> CFG::mapData;
int main ()
{
CFG::ParseConfigFile ();
cout << "Param: " << CFG::Param("OuterIP") << std::endl;
return 0;
}
This is the program as I've compiled with:
g++ main.cc -o regex -std=c++11
This is the configuration file:
OuterIP=10.0.0.1
And these are the results:
Parsing cfg file
key: val:
key: val:
OuterIP=10.0.0.1
key: OuterIP val: 10.0.0.1
Config loaded
=
OuterIP=10.0.0.1
RTP port is same as MGCP port. Please change one of them.
Param: 10.0.0.1
As you can see, the last line is the cout in main.
So your code works. You have to remove the exit(-1) from the class. It doesn't have sense anyway.

finding a string in a file using fstream in c++

This is my code
/*
Asks the user for their ID, depending on the ID depends on the results. It either goes to maintanance
or it asks the user to return DVD's or check DVD's out and changes the stock of the DVD's.
Cody Close
*/
#include <iostream>
#include <fstream>
#include <conio.h>
#include <sstream>
#include <string>
using namespace std;
void custID();
void sales();
void returns();
void discounts();
void maint();
void createAcc(string* filename, string* newID);
bool checkID(string* filename, string* search);
int main()
{
//Declares all the variables for the program
int mainID= 99959, menuChoice;
bool close = false;
bool done = false;
string vidId;
//Declares and input file and opens a file
fstream inFile;
inFile.open("dayin00.dat");
do{
do{
cout << "accountID: " << endl;
cin >> mainID;
stringstream out;
out << mainID;
mainid = out.str();
checkID("IDlist.txt", mainid);
}while(mainid.length() < 5 || mainid.length() > 9);
if(mainID!= 99959)
{
do
{
cout << "MENU:" << endl;
cout << "(1)Purchase\n(2)Return\n(3)Exit" << endl;
cin >> menuChoice;
switch(menuChoice)
{
case 1:
case 2:
case 3:
done = true;
}
}while(done == false);
}else{
maint();
}
close = true;
}while(close == false);
return 0;
}
void maint()
{
int maintChoice;
cout << "\n(1)Summary\n(2)Withdrawl\n(3)Close Down\n(4)Back to >main\n(0)Help" << endl;
cin >> maintChoice;
switch (maintChoice)
{
case 1:
case 2:
case 3:
case 4:
default:
cout << "1 for summary, 2 for withdrawl, 3 to close down, 4 to >go back to main" << endl;
}
}
void createAcc(string* filename, string* newID)
{
fstream newFile;
newFile.open(filename);
newFile << newID;
}
void checkID(string* filename, string* ID)
{
fstream infile;
infile.open("IDlist.txt");
string word;
infile >> word;
while (!infile.eof()){
if(word == ID)
{
cout << "ID FOUND!" << endl;
}else{
createAcc(infile, ID);
}
}
}
The text file only contains the ID 99959. How do I check if the ID the user types in already exists in the text file and if it doesn't, then it goes to createAcc(),setting up a new account using the ID that the user has entered.
The code opens file with users ID in read mode, reads it line by line and tries to finde ID. If ID not found in file, it opens file in write mode and add user ID in file.
#include <iostream>
#include <fstream>
#include <stdexcept>
void createAcc(const std::string& filename, const std::string& id)
{
std::ofstream os(filename);
if (os)
os << id;
else
throw std::runtime_error("Open file error: " + filename);
}
bool isStringContainsID(const std::string& line, const std::string& id)
{
if (line.find(id) == std::string::npos)
return false;
else
return true;
}
bool isFileContainsID(const std::string& filename, const std::string& id)
{
std::ifstream is(filename);
if (!is)
throw std::runtime_error("Open file error: " + filename);
std::string line;
while (is)
{
std::getline(is, line);
if (isStringContainsID(line, id))
return true;
}
return false;
}
int main() {
std::string id("99959");
std::string file_name("IDlist.txt");
if (isFileContainsID(file_name, id))
std::cout << "ID FOUND!" << std::endl;
else
createAcc(file_name, id);
return 0;
}
Note that all users ID should have the same length in string representation, otherwise the code can find shorter ID in file that contains larger ID with shorter ID as sub-string.

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();
}
Turns out Openframeworks doesn't like C++11 binaries... I fixed this by not using open frameworks for the application in question.

Trying to derive from wfilebuf (filebuf) for logging

I'm basically trying to derive from wfilebuf so I can both output to a file and intercept the output to print it to the console/debug window as well as illustrated here:
http://savingyoutime.wordpress.com/2009/04/21/ and/or here: http://savingyoutime.wordpress.com/2009/04/22/40/
(ancient supporting ideas here: http://www.horstmann.com/cpp/streams.txt)
I've almost got it, but I can't seem to be able to both write to the underlying file AND peek at the input.
I overrode the sync() function similar to the second example but it seems that pbase() and pptr() are always NULL unless I set a buffer with setp(...), but this seems to break the file output. The file is always empty!
My crude attempt at this is below:
class LoggerBuffer : public wfilebuf {
// Functions
public:
LoggerBuffer();
~LoggerBuffer();
void open(const wchar_t loggerFile[]);
void close();
int sync();
int_type overflow(int_type c = EOF);
void setState(int newState);
// Variables
private:
int currentState;
static const int BUFFER_SIZE = 10;
wchar_t buffer[BUFFER_SIZE];
};
class LoggerStream : public wostream {
// Functions
public:
LoggerStream();
~LoggerStream();
void open(const wchar_t loggerFile[] = 0);
void close();
void setState(int newState);
};
LoggerBuffer::LoggerBuffer() {
wfilebuf::open("NUL", wios::out); currentState = 1;
}
LoggerBuffer::~LoggerBuffer() {
wcout << "Destruction of LoggerBuffer" << endl;
}
void LoggerBuffer::open(const wchar_t loggerFile[]) {
wcout << "LoggerBuffer Opening " << loggerFile << endl;
close();
wfilebuf* temp = wfilebuf::open(loggerFile, wios::out); //ios::out | ios::app | ios::trunc
setp (buffer, buffer+(BUFFER_SIZE-1));
}
void LoggerBuffer::close() {
wfilebuf::close();
}
int LoggerBuffer::sync() {
wcout << " Syncing ";
int out_waiting = pptr() - pbase();
wcout << out_waiting << " characters!";
wcout << endl;
wcout << "pptr(): " << (unsigned int)pptr() << endl;
return wfilebuf::sync();
}
LoggerBuffer::int_type LoggerBuffer::overflow(int_type c) {
wcout << "overflow! (" << (wchar_t)c << ")" << endl;
if (c == EOF)
return EOF;
if (sync() == EOF)
return EOF;
return wfilebuf::overflow(c);
}
void LoggerBuffer::setState(int newState) {
wcout << "New buffer state = " << newState << endl;
currentState = newState;
}
LoggerStream::LoggerStream() : wostream(new LoggerBuffer), wios(0) {
}
LoggerStream::~LoggerStream() {
delete rdbuf();
}
void LoggerStream::open(const wchar_t loggerFile[]) {
wcout << "LoggerStream Opening " << loggerFile << endl;
((LoggerBuffer*)rdbuf())->open(loggerFile);
}
void LoggerStream::close() {
((LoggerBuffer*)rdbuf())->close();
}
void LoggerStream::setState(int newState) {
wcout << "New stream state = " << newState << endl;
((LoggerBuffer*)rdbuf())->setState(newState);
}
Full disclosure: I asked a question regarding something similar earlier: Simple wostream logging class (with custom stream manipulators)
I think I have solved that problem though.
Any help is greatly appreciated! Thanks!
I'd use a filtering streambuf, that does no buffering of its own, instead passing data through to a real streambuf (i.e., one that does real buffering) for each of the destinations. This should simplify your code quite a bit and let you concentrate on the parts you really care about.