0

I am now writing wrappers for C++ functions, such that they can be used from C code.

The idea is to compile the cpp files using g++ and the c files using gcc, then link them together (!), but exposing ONLY those functions that are needed, to the C programs, by making them available in a header file 'test.h' (or maybe test.hpp?), like so:

(Note how I do not expose function 'vector Tokenize(const string& str,const string& delimiters)')

test.h:

/* Header can be read by both C+ and C compilers, just the way we want! */
#ifndef TEST_H
#define TEST_H

#ifdef __cplusplus
extern "C" {
#endif

#if defined(__STDC__) || defined(__cplusplus)
 extern int TokenizeC(const char* text, const char* delim, char ***output);   /* ANSI C prototypes */
 extern void reclaim2D(char ***store, unsigned int itemCount);
#endif

#ifdef __cplusplus
}
#endif

#endif /* TEST_H */

test.cpp:

#include <string>
#include <iostream>
#include <vector>

#include <assert.h>

#include "test.h"

using namespace std;

vector<string> Tokenize(const string& str,const string& delimiters)
{
 vector<string> tokens;

 string::size_type delimPos = 0, tokenPos = 0, pos = 0;

 if(str.length() < 1)  return tokens;

 while(1)
 {
   delimPos = str.find_first_of(delimiters, pos);
   tokenPos = str.find_first_not_of(delimiters, pos);

   if(string::npos != delimPos)
   {
     if(string::npos != tokenPos)
     {
       if(tokenPos < delimPos) tokens.push_back(str.substr(pos,delimPos-pos));
       else tokens.push_back("");
     }
     else tokens.push_back("");

     pos = delimPos + 1;
   }
   else
   {
     if(string::npos != tokenPos) tokens.push_back(str.substr(pos));
     else tokens.push_back("");
     break;
   }
 }

 return tokens;
}

int TokenizeC(const char* text, const char* delim, char ***output)
{
    if((*output) != NULL) return -1; /* I will allocate my own storage, and no one tells me how much. Free using reclaim2D */

    vector<string> s = Tokenize(text, delim);

    // There will always be a trailing element, that will be blank as we keep a trailing delimiter (correcting this issue would not be worth the time, so this is a quick workaround)
    assert(s.back().length() == 0); // This will be nop'ed in release build
    s.pop_back();

    (*output) = (char **)malloc(s.size() * sizeof(char *));

    for(vector <string>::size_type x = 0; x < s.size(); x++)
    {
        (*output)[x] = strdup(s[x].c_str());

        if(NULL == (*output)[x])
        {
            // Woops! Undo all
            // TODO : HOW to test this scenario?

            for(--x; x >= 0; --x)
            {
                free((*output)[x]);
                (*output)[x] = NULL;
            }

            return -2; 
        }
    }

    return x; /* Return the number of tokens if sucessful */
}

void reclaim2D(char ***store, unsigned int itemCount)
{
    for (int x = 0; itemCount < itemCount; ++x)
    {
        free((*store)[x]);
        (*store)[x] = NULL;
    }

    free((*store));
    (*store) = NULL;
}

poc.c:

#include <stdio.h>
#include "test.h"

int main()
{
    const char *text = "-2--4--6-7-8-9-10-11-", *delim = "-";

    char **output = NULL;

    int c = TokenizeC(text, delim, &output);

    printf("[*]%d\n", c);

    for (int x = 0; x < c; ++x)
    {
        printf("[*]%s\n", output[x]);
    }

    reclaim2D(&output, c);

    return 0;
}

Do you notice something wrong?

For starters, when I ran this program, I got "Unsatisfied code symbol '__gxx_personality_v0'"

Thankfully, there is something here : What is __gxx_personality_v0 for?

Once I ran g++ with options " -fno-exceptions -fno-rtti", the output now fails with "Unsatisfied data symbol '_ZNSs4_Rep20_S_empty_rep_storageE'"

Ofcourse, the two environments (the one to compile - HP-UX B.11.23 ia64 and the one to run the binary on - HP-UX B.11.31 ia64) have different library versions (but same architecture), and this should not be a reason for the errors.

I would also like to test out the case marked by "// TODO : HOW to test this scenario?", but that can wait for now.

Any pointers?

Community
  • 1
  • 1
PoorLuzer
  • 24,466
  • 7
  • 31
  • 35

3 Answers3

3

The easiest way to avoid undefined symbols while linking is to link with g++ (not gcc). You can still compile your .c file with gcc, though.

Also please use system at a time. The link error may go away if you run all your gcc and g++ commands on the same system (no matter the old or the new one).

pts
  • 80,836
  • 20
  • 110
  • 183
  • This is what I did: g++ -c -fPIC -Wall -Wuninitialized -fno-exceptions -fno-rtti -O -D__hpuxita -mlp64 -I$(INCLUDEPATH) $< ; gcc -c -fPIC -Wall -Wstrict-prototypes -Wuninitialized -O -D__hpuxita -mlp64 -I$(INCLUDEPATH) $< ; gcc -AA -Aa -mlp64 -shared -L$(LIBPATH) $(OBJS) -o $(APP) – PoorLuzer May 16 '09 at 21:54
  • +1, the problem is the last line there, you should link with g++ not gcc which will include all the neccessary c++ stuff by default. – Evan Teran May 16 '09 at 21:58
  • Evan, yes, but then would not the resulting binary be a C++ binary with mangled names etc? I would want the resulting binary to be a C one. I want to use C++ only in specific instances where using the STL would be extremely beneficial, but the resulting binary needs to be C compatible. – PoorLuzer May 16 '09 at 22:06
  • The message 'Unable to find library 'libstdc++.so' is stopping all the fun now - maybe if I can locate the lib myself, the executable will now finally run? – PoorLuzer May 16 '09 at 23:05
  • Funny thing is even if I set SHLIB_PATH, LD_LIBRARY_PATH_64, LD_LIBRARY_PATH and, hell, even PATH to point to the .so file, I STILL get the error message! – PoorLuzer May 17 '09 at 04:53
2

To call a C++ function from C, you can't have mangled names. Remove the conditional test for __cplusplus where you do the extern "C". Even though your functions will be compiled by a C++ compiler, using extern "C" will cause it to avoid name-mangling.

Here is an example:

The C file.

/* a.c */
#include "test.h"

void call_cpp(void)
{
  cpp_func();
}

int main(void)
{
  call_cpp();
  return 0;
}

The header file.

/* test.h */
#ifndef TEST_H
#define TEST_H
extern "C" void cpp_func(void);
#endif

The CPP file.

// test.cpp
#include <iostream>
#include "test.h"

extern "C" void cpp_func(void)
{
  std::cout << "cpp_func" << std::endl;
}

The compiler command line.

g++ a.c test.cpp
Dingo
  • 3,305
  • 18
  • 14
  • and I should use gcc instead of g++ to link - unlike what Evan told above? – PoorLuzer May 16 '09 at 22:49
  • removing the conditional test to get no name mangling seems to mess with the prototype declaration in the header file because gcc barks at me "warning: implicit declaration of function 'TokenizeC'" – PoorLuzer May 16 '09 at 22:55
  • The conditional works only for C++, where it SHOULD work... I believe that is not the issue. – PoorLuzer May 16 '09 at 22:56
  • Why do I need to have the extern at both places - the source and the header? Is not having it in the header enough? – PoorLuzer May 17 '09 at 04:52
  • The declaration and definition must match. That's the reason you got the: "warning: implicit declaration of function 'TokenizeC'". – Dingo May 17 '09 at 13:24
1

Is there a reason why you haven't considered modifying Swig to do just this? I seem to remember that there is a developmental branch of Swig to do just this...

Arafangion
  • 11,517
  • 1
  • 40
  • 72
  • I was, when I asked the question, under the belief that using Swig to interface C++ code with C be too much of a workaround. I would be interested in looking at the path though - if you had some links. – PoorLuzer Nov 26 '10 at 20:07
  • 1
    It's hard to find... I think most people just end up using C++ for the glue code, however I have managed to find the following link, and I think that is the same link that I remembered in the past, although I suspect that there has been little interest: http://swig.svn.sourceforge.net/viewvc/swig/branches/gsoc2008-maciekd/Doc/Manual/C.html – Arafangion Nov 28 '10 at 03:36
  • Yes, I use C/C++ in production code, and I prefer rewriting code to have least dependencies in such cases. +1 for the link though. – PoorLuzer Dec 02 '10 at 08:02