1

So I have looked here and here and at a few other links mentioned in the first question and I have the following code already:

The .cpp file:

#include "arp_piping.h"

#include <string>
#include <iostream>
#include <stdio.h>

std::string exec(char* cmd, FILE* pipe) {
    pipe = _popen(cmd, "r");
    if (!pipe) return "ERROR";
    char buffer[128];
    std::string result = "";
    while(!feof(pipe)) {
       if(fgets(buffer, 128, pipe) != NULL)
              result += buffer;
    }
    _pclose(pipe);
    return result;
}

The header/linker file:

#ifndef ARP_PIPING_H
#define ARP_PIPING_H
#endif

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

my function goes here something like 
EXTERNC .....exec(char* cmd, FILE* pipe) ????

#undef EXTERNC

My question is what goes in the bit above as I am unsure what to be typing. I am trying to call the function in the .cpp file from my C main function int main(int argc, char** argv) {}

Community
  • 1
  • 1
cxzp
  • 652
  • 2
  • 14
  • 28
  • @MarkGarcia have re-edited the question&title for that matter see mainly the end of the question ... c main `int main(int argc, char** argv) {}` – cxzp Jul 19 '13 at 07:09
  • 1
    Don't you should return a C-string instead of a `std::string`? – nouney Jul 19 '13 at 07:17
  • You can't. The result type of exec is not existing in C, you perhaps need to write a c-compatible wrapper in the c++-part. – urzeit Jul 19 '13 at 07:17
  • Type of return value should not C++ class. I suggest you to add a function whose return type is POD type. – kamae Jul 19 '13 at 07:19
  • @urzeit could you possibly post an answer and explain how – cxzp Jul 19 '13 at 07:20
  • @cxzp: see Kelm's answer. If you can alter your function definition in c++, you should return a c compatible type directly. If not, write a wrapper that converts the return value to a c compatible type. – urzeit Jul 19 '13 at 07:23

2 Answers2

4

To call C++ functions from C you need to do two things. 1) Let the C++ code know it's going to be used by C so that it can generate C-friendly symbols. 2) Hide any functionality that C can't understand.

The first part is easily achieved by simply defining the functions as you would in C (I.E. don't use any C++ only features like namespaces) and then wrapping them in an extern "C" block if C++ is defined. You basically want your header file to contain C-only code, and then just open the extern block at the top, and close it at the bottom of the file (my example will make this more clear).

The second part is a little trickier, but not too difficult. In your case, your function returns a std::string which is a C++ only class. It can not be used in C and therefore either needs to be replaced with something that can be used in C, or it needs to be hidden behind something that C can use. For the sake of argument let's assume you can't replace std::string with say, char*. In this case you need to hide std::string from the C-facing code. The common way of doing this is to use an opaque pointer.

Basically, the C-facing code deals only with a pointer to something. That something it neither knows about, nor cares about. The C++ code is free to use a std::string internally, but must make sure to hide it before interfacing with the C API. In my example, you can see I've provided an opaque pointer to a struct I've called cppstring.

In the source file, cppstring is just a struct that holds a std::string. I've changed your example code to use the new cppstring struct. One important thing to note is that because the C code can only deal with a pointer to a cppstring, we need to create it on the heap in our C++ code and return the pointer to it. This means that we must provide the C users some way of freeing it when they're done, which I've also provided in the example.

Using this technique you can wrap the entirety of std::string behind a C API, allowing C users to use all of the functionality that std::string provides. I've provided an example of wrapping std::string::substr to show you how.

N.B. I haven't compiled nor tested this code and for the sake of simplicity I haven't included any of the relevant header files, etc. Nevertheless, it should be enough to get you started.

// C header 

#ifdef __cplusplus
extern "C" {
#endif

typedef struct cppstring *cppstring_p;

cppstring_p exec(char *cmd, FILE *pipe);
void free_cppstring(cppstring_p cppstr);

/* example of wrapping std::string::substr for C users */
cppstring_p substr(cppstring_p str, int pos, int count);

#ifdef __cplusplus
}
#endif



// CPP source

struct cppstring {
    std::string data;

    cppstring(void) {}
    cppstring(std::string const& s) : data(s) {}
};


cppstring_p exec(char *cmd, FILE *pipe) {
    pipe = _popen(cmd, "r");
    if (!pipe) return "ERROR";
    char buffer[128];
    auto result = new cppstring;        
    while(!feof(pipe)) {
       if(fgets(buffer, 128, pipe) != NULL)
              result->data += buffer;
    }
    _pclose(pipe);
    return result;
}


void free_cppstring(cppstring_p cppstr) {
    delete cppstr;
    cppstr = nullptr;
}


cppstring_p substr(cppstring_p str, int pos, int count) {
    assert(str);
    return new cppstring(str->data.substr(pos, count));
}
scl
  • 452
  • 4
  • 2
3

You need to declare the function as extern "C" in the cpp file:

extern "C" char *exec(char* cmd, FILE* pipe) {
   ...
}

In the header/linker file you need to declare it's prototype with the keyword "extern", like so:

extern char *exec(char* cmd, FILE* pipe);

Also, are you sure you want to return a c++'s std::string to your C code?

Kelm
  • 967
  • 5
  • 14
  • well i want the output of the command "arp -a" which i am obtained by using the cplusplus function _popen - i am getting an error on return types because of the std::String what would you suggest? – cxzp Jul 19 '13 at 07:20
  • I modified my answer. To return a C-style string instead of std::string, simply do `return myString.c_str();`. – Kelm Jul 19 '13 at 07:22
  • 2
    that will break horribly. it'll return a pointer to a local variable – Tom Tanner Jul 19 '13 at 07:33
  • Which would remain unnoticed if used immediately and become a hard to find bug later, you are right. You can malloc a buffer or use a static one. – Kelm Jul 19 '13 at 07:34
  • @Kelm hi i have got the code compiling so thankyou for the suggestions but it wont compile when i add the cpp header to the file which contains the c main it gives me a bunch of errors see [here](http://fixee.org/paste/kexdlvd/) i think the problem is to do with the fact that the cpp code uses the header ... how can i stop these errors? – cxzp Jul 19 '13 at 07:51