By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
443,359 Members | 1,495 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 443,359 IT Pros & Developers. It's quick & easy.

Calling C++ class method from C

P: 24
Hello guys,

Ok here is the issue/roadblock. I am trying to call a method that is a member of a c++ class. I know I need to have a wrapper function to give C a pointer to the member function. I have created a shared header file <header.h> and 2 source files. <cSource.c> and <cppSource.cpp>. Then of course the main file written in c. <cMain.c>

If anyone could help me get this small example working so that I can experiment with the actual problem I'm trying to overcome it would be absolutely terrific.

Here is the code I have:
(the commands to compile and errors located at the end of post)

header.h
Expand|Select|Wrap|Line Numbers
  1. /*Start CPP header information */
  2. #ifndef HEADER_H
  3. #define HEADER_H
  4.  
  5.     #ifdef __cplusplus
  6.         class cClass
  7.         {
  8.             public:
  9.  
  10.                 void sayHello();
  11.                 cClass (){};
  12.         };
  13.         extern "C" cClass* cSayHello(cClass* cclass);
  14.     #else
  15.         /*End CPP header information */
  16.         ///////////////////////////////////
  17.         /*Start C Header Information */
  18.         typedef struct cClass {}cClass;
  19.     #endif    
  20.  
  21.     #ifdef __cplusplus
  22.         extern "C" 
  23.         {
  24.     #endif
  25.  
  26.  
  27.     void cSpeak(struct cClass* cclass);
  28.  
  29.     #ifdef __cplusplus
  30.         }
  31.     #endif
  32.     /* End C Header Information */    
  33.  
  34.  
  35. #endif //For Header.h define check.
  36.  
  37.  
cSource.c
Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2. #include "header.h"
  3.  
  4.  
  5. void cSpeak(cClass* cclass)
  6. {
  7.     cSayHello(cclass);
  8. }
  9.  
  10.  
cppSource.cpp
Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2. #include "header.h"
  3.  
  4. void cClass::sayHello() //This is the function we are trying to run from C
  5. {
  6.     printf ("This is how you say hello, using C that calls a C++ function");
  7. }
  8.  
  9. extern "C" cClass* cSayHello(cClass* cclass) //This returns a pointer for the C function!
  10. {
  11.     cclass->sayHello();
  12.     return cclass;
  13. }
  14.  
  15.  
cMain.c
Expand|Select|Wrap|Line Numbers
  1. //Includes
  2. #include <stdio.h>
  3. #include "header.h"
  4.  
  5. int main()
  6. {
  7.     cClass cclass;
  8.     printf ("void cSpeak(cClass* cclass);");
  9.     cSpeak(&cclass);
  10.     return 0;
  11. }
  12.  
  13.  

$>gcc -c cMain.c -o main.o
$>gcc -c cSource.c -o csource.o
$>g++ -c cppSource.cpp -o cppsource.o
$>gcc -o main.o csource.o cppsource.o //This gives the following error.

csource.o: In function `cSpeak':cSource.c:(.text+0xd): undefined reference to `cSayHello'
collect2: ld returned 1 exit status

Thanks for all the help in advance!
Mar 13 '09 #1
Share this Question
Share on Google+
13 Replies


weaknessforcats
Expert Mod 5K+
P: 9,197
First, cClass is a C++ class and there are no classes in C so you can't create a cClass object in main().

You can call any C++ functions from C but you will have to be sure all of the C++ functions are in C++ source files and compiled as C++.

Any C++ functions called from C have to be extern "C" to surn off the C++ mangler. Therefore, you lose function oveloading, too.

So, to call cClass::sayHello from C you would in the C main():

1) call cSpeak(). No arguments.
This would be an extern "C" C++ function.
2) cSpeak would create a cClass object on the heap and call sayHello on that object.
3) cSpeak would delete the cClass object and return
Mar 13 '09 #2

P: 24
@weaknessforcats
Oh i guess I should have mentioned about the wrapper thing I was looking to do. From my internet journey I have discovered a way to do it, by creating a wrapper function that returns a pointer to the class method. Here is the link i found. Its about the third sentence down "Naturally...." I somewhat understand what is going on, however I'm confused about where certain pieces go.

http://www.research.att.com/~bs/bs_faq2.html#callCpp
Mar 13 '09 #3

weaknessforcats
Expert Mod 5K+
P: 9,197
You want to be careful here.

This is from your referenced article:
extern "C" double call_C_f(C* p, int i) // wrapper function
{
return p->f(i);
}
This function takes a C* and the member function is called using that pointer.
This is exactly what I said to do in my earlier post except without the C* argument:

Now C::f() can be used like this:
/* C code: */

double call_C_f(struct C* p, int i);

void ccc(struct C* p, int i)
{
double d = call_C_f(p,i);
/* ... */
}
Here an assumption is made: Namely the struct C has the same address as a class C. It's based on the fact that classes in C++ are really implemented as structs. Personally, I would remove the assumption by using a struct C* in the C function and on the C++ side using a struct C rather than a class C.

If you specify public/private/protected for every struct member, then a struct and a class are identical.

I so not know if this assumption is part of the C++ standard or whether it is compiler-dependent. Every time I have done this the class and the struct work the same in C++.

But were I coding this, ccc() would look like this:
Expand|Select|Wrap|Line Numbers
  1. void ccc(int arg)
  2. {
  3.      wrapperfunction(arg);   //a C++ function
  4. }
  5.  
where
Expand|Select|Wrap|Line Numbers
  1. void wrapperfunction(int arg)
  2. {
  3.     C* ptr = new C;
  4.          ptr->f(i);
  5.     delete C;
  6. }
  7.  
There's no need to pass a C* around onb the C side. You just stay in C++ until you can delete the C object.

That is, your C main() itself is just a wrapper for C++ and you should put all of your code in C++. Here's the C main():
Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     cplusplusmain();
  4. }
  5.  
where cplusplusmain() wraps the entire C++ application:
Expand|Select|Wrap|Line Numbers
  1. extern "C" int cplusplusmain()
  2. {
  3.     the ActualApplication();
  4. }
  5.  
theActualApplication is your C++ main().
Mar 13 '09 #4

P: 24
This is exactly what I said to do in my earlier post except without the C* argument:
My apologies, I must have overlooked that small detail. I am having more trouble with the layout of where things go than with the theory behind it all. This is the most in depth I've ever gone with C or C++ for that matter.

If I have a common header, I get very confused where to add the extern "C" and where not to.

Lets see if I can sum this up properly:

If I am careful I can use a c++ class and a C struct (only if it's assumed the address are the same and the class is designed to mimic a struct), or I have a c++ struct and a c struct.

Then in cMain I call a function that is an extern "C" cppMain(), which then does all the work such as creating a class accessing its members and doing what needs to be done.

Where i get confused is what files go where in this case. If i have cMain.c, csource.c, cppsource.cpp, and header.h

Sorry for being so clumsy at this, but I've been trying to get my head around this (syntactically) most of this week, and I've confused myself pretty bad. Sadly it wouldn't be so bad if the examples from the sites I've been looking at would show how to link all these files together, and where the corresponding functions go.

The only two sites I have are :
http://www.parashift.com/c++-faq-lit....html#faq-32.8

and the one above.
Do you know of any other good resources for examples or information on what I'm trying to do?

I appreciate all your help weaknessforcats
Mar 13 '09 #5

weaknessforcats
Expert Mod 5K+
P: 9,197
Do not use a common header. C ande C++ are different languages and you will have problems on the C side tripping over C++ syntax and troubles on the C++ side because C syntax can work differently (or be an outright error) on the C++ side.

Instead, have a C header for the C side and a C++ header for the C++ side.

The extern "C" goes into the C++ header since that is C++-only syntax.

Then your C code is in a .c file and that code includes your C header.

Your C++ code is in a .cpp file and that code includes your C++ header.

You compile each source file and then link the object files together to create your final executable.

Usually, this consists of having the .c and the .cpp files members of the same project. Then when you build, the .c files are compiled as C code and the .cpp files are compiled as C++ code. The link step takes care of linking the object files together. (this is how Visual Studio.NET does it).

Lastly, remember, that at the object level everything is binary and all notion od C or C++ is lost. All you need to do is get your source code to compile using the correct language.
Mar 14 '09 #6

P: 24
Thanks so much weaknessforcats. I'm going to try this code and I'll let you know how everything works out and if i need more help. I'll post the results (the files and possibly a make file) so that anyone who has this question can have it answered with a good example.
Mar 16 '09 #7

P: 24
I believe it worked. Can you check this to make sure it is indeed C calling C++? It would be greatly appreciated.

cMain.c
Expand|Select|Wrap|Line Numbers
  1. /* This is the C main file.  This should also be compiled with a c compiler */
  2. #include <stdio.h>         // This is included because printf is used.
  3. #include "cheader.h"     // We are calling functions from the C source here. so we
  4.                         //need the header for it.
  5.  
  6. int main()
  7. {
  8.     printf ("About to run the c function.\n");
  9.     cSpeak(); // Call the C function to start everything.
  10.     return 0;
  11. }
  12.  
  13.  

cheader.h
Expand|Select|Wrap|Line Numbers
  1. // This is the header file for all the C functions.
  2. // 
  3.  
  4. //Function to initiate the C++ function.
  5.  
  6. void cSpeak();
  7.  
  8.  

cppheader.h
Expand|Select|Wrap|Line Numbers
  1. //C++ Header, this is where all the C++ functions are prototyped. 
  2. //
  3.  
  4. // C++ class which holds the member function sayHello();
  5.  
  6. class myTest
  7. {
  8.     public:
  9.         void sayHello();
  10.         myTest(){};
  11. };
  12.  
  13. //Purpose of this function is to call sayHello
  14. //This function is also, C compatible. (because we used extern "C")
  15. extern "C" void sayHelloWrapper();
  16.  
  17.  
cppSource.cpp
Expand|Select|Wrap|Line Numbers
  1. /* This is the c++ source file.*/
  2. #include "cppheader.h" // Include the cppheader.h file
  3. #include <stdio.h> // We need to include this in order to use printf.
  4.  
  5. //This is the function we are trying to run from C (it needs a wrapper)
  6. void myTest::sayHello() 
  7. {
  8.     printf ("This is how you say hello, using C that calls a C++ function\n");
  9. }
  10.  
  11. //This function is meant to call the class function. This is the wrapper.
  12. extern "C" void sayHelloWrapper() 
  13. {
  14.     printf("inside wrapper about to run sayHello\n");
  15.     myTest* speak; // Create instance of class type 
  16.     speak->sayHello(); // call sayHello()
  17.  
  18. }
  19.  
  20. /*  This file needs to be compiled by the c++ compiler.  ie: g++ */
  21.  
cSource.c
Expand|Select|Wrap|Line Numbers
  1. /* This file is small for readibility. */
  2. #include "cheader.h" //Include the cheader.h file.  It contains the cSpeak() protoype
  3. #include <stdio.h> // We have to include this to use printf.
  4.  
  5. // C function designed to call a C++ function.
  6. void cSpeak()
  7. {
  8.     printf("Inside cspeak.. running wrapper.\n");
  9.     sayHelloWrapper();
  10. }
  11.  
  12. /* This file needs to be compiled by the C compiler. ie gcc -c */
  13.  
Makefile
Expand|Select|Wrap|Line Numbers
  1. #This simple make file must be executed from within the directory that contains
  2. #the source files, header files, and main files.
  3.  
  4. #linking of all the main .o files
  5. run : cMain.o cppSource.o cSource.o
  6.     g++ -o run cMain.o cppSource.o cSource.o
  7.  
  8. # c files need to be compiles with gcc
  9. cMain.o : cMain.c
  10.     gcc -c cMain.c
  11.  
  12. cSource.o : cSource.c
  13.     gcc -c cSource.c
  14.  
  15. # cpp files need to be compiled with g++
  16. cppSource.o : cppSource.cpp
  17.     g++ -c cppSource.cpp
  18. # this allows you to remove all the .o files from the directory in which you compiled
  19. # simply issue the command <make clean> and it will perform this action.    
  20. clean:
  21.     rm -f *.o
  22.  
Hopefully this helps everyone. Thanks again weaknessforcats, i appreciate it very much.

Plop all the files in a directory, and then run the make file. That should do it.
Mar 16 '09 #8

P: 24
OK, so i got the variable passing down. But I decided that I need to be able to pass multiple objects back and forth, so I decided to use a structure.

In my CHeader file I have this:
Expand|Select|Wrap|Line Numbers
  1. typedef struct returnData
  2. {    
  3.  /*    This is the structure that gets changed for passing
  4. *    data back and forth from C and C++
  5.  */ 
  6.     char* txtResult;
  7.     int intResult;
  8.     int intMultiResult[50];
  9.     int resultToGet;
  10. };
  11. struct returnData* cData;
  12.  
  13. void getMachineInfoC();
  14.  
  15.  
in my CppHeader.h file i have this:
Expand|Select|Wrap|Line Numbers
  1. typedef struct returnData
  2. {    
  3. /*    This is the structure that gets changed for passing
  4. *    data back and forth from C and C++
  5.  */ 
  6.     char* txtResult;
  7.     int intResult;
  8.     int intMultiResult[50];
  9.     int resultToGet;
  10. };
  11. struct returnData* cppData;
  12.  
  13. extern "C" void getMachineInfoCpp(returnData* cppData, char* machineName);
  14.  
  15.  
Now, what i want to do is essentially pass the address from the C function to the C++ function. Do what I gotta do in C++ then output all the stuff using C


C Function Description
Expand|Select|Wrap|Line Numbers
  1. void getMachineInfoC()
  2. {
  3.  
  4.        char machineName[50];
  5.     printf ("Enter the name of the machine: ");
  6.     fgets(machineName, 50, stdin);
  7.     rmNewline(machineName, 50);
  8.     (*cData).intResult = 10;   //This causes a segfault 
  9.  
  10.     getMachineInfoCpp(&cData, &machineName[0]);
  11.  
  12.     //These print statements don't work either
  13.     printf ("Error Status: %d\n", cData->intMultiResult[0]);
  14.     printf ("Machine ID:   %d\n", cData->intMultiResult[1]);
  15.     printf ("MiltID:       %d\n", cData->intMultiResult[2]);
  16. }
  17.  
  18.  
CPP Function Description:
Expand|Select|Wrap|Line Numbers
  1. extern "C" void getMachineInfoCpp(returnData* cppData , char* machineName)
  2. {
  3.  
  4.     string query;
  5.     string query1 = "select * from sp_getmachineid('";
  6.     string query2 = (string)machineName;
  7.     string query3 = "');";
  8.     query = query1 + query2 + query3;
  9.     cout << query << endl;
  10.     startHandling(1);
  11.  
  12.     initConnection();
  13.  
  14.     icConnection.dbStatement->Execute(query);
  15.  
  16.     while (icConnection.dbStatement->Fetch())
  17.     {
  18.         (icConnection.dbStatement)->Get(1, cppData->intMultiResult[0]);
  19.         (icConnection.dbStatement)->Get(2, cppData->intMultiResult[1]);
  20.         (icConnection.dbStatement)->Get(3, cppData->intMultiResult[2]);
  21.     }
  22.     startHandling(0);
  23.     printf ("intResult %d", cppData->intResult);
  24.     printf("qResult 1: %d\n 2: %d\n 3: %d\n", 
  25.         cppData->intMultiResult[0],
  26.         cppData->intMultiResult[1], 
  27.         cppData->intMultiResult[2]);
  28. }
  29.  
Thank you all for your help. I know I'm probably just doing my pointer stuff incorrectly.
Apr 2 '09 #9

weaknessforcats
Expert Mod 5K+
P: 9,197
This code:

Expand|Select|Wrap|Line Numbers
  1.  (*cData).intResult = 10;   //This causes a segfault  
should be written as:

Expand|Select|Wrap|Line Numbers
  1. cData->intResult = 10;   
That's a matter of style but is not your problem.

Your problem is that cData is a returnData* that does not point to a returnData variable. That is, it an uninitialized pointer.
Apr 2 '09 #10

P: 24
Ahh, got it. THX!!!

But with pointers I have another concept. I tried a bunch of diferent methods but nothing i seem to try works to convert the value of qresult to qresultptr

Expand|Select|Wrap|Line Numbers
  1. char qResult[25];
  2. char* qResultptr;
  3.  
  4. //get a value for qResult[]
  5. //lets say qResult="superman"
  6.  
  7. qResultptr=qResult; // this doesn't work.  and strcpy segfaults.
  8.  
Apr 2 '09 #11

weaknessforcats
Expert Mod 5K+
P: 9,197
It works for me:
Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3. char qResult[25]; 
  4. char* qResultptr; 
  5.  
  6. //get a value for qResult[] 
  7. //lets say qResult="superman" 
  8.  
  9. qResultptr=qResult; // this doesn't work.  and strcpy segfaults
  10. strcpy(qResultptr, "superman");
  11. cout << qResultptr << '\n'
  12.          << qResult << endl;
  13. }
Of course, this can't work:
Expand|Select|Wrap|Line Numbers
  1. qResult="superman" 
because the name of an array is the address of element 0 and this code attempts to change the address of qResult[0] (a local variable) and this you cannot do.
Apr 3 '09 #12

P: 24
Ahh, you know I'm beginning to get the hang of all this complex (complex to me anyway) pointer logic. Thank you so much weaknessforcats.

I've got another question yet again. A tad more complex for me this time probably not for you.

OK so i call getDBVersionCpp from C and give it the pointer to an array of structures. Everything works fabulous. Here's the catch though. Once the aryResults is returned by the getDBVersionCpp, and I go to do the printf's, I get the correct value of gobble gobble from myResults[1].textResults[1] however, instead of getting 5.8.10 for the db version i get a strange symbol.

Everything on the CPP side prints out just as it should. The comments will show what it prints out.

Can anyone explain to me why this is happening?

cheader.h
Expand|Select|Wrap|Line Numbers
  1. typedef struct
  2. {
  3.     char* textResults[1024];
  4.     int intResults[1024];
  5.     int exitStatus;
  6. }results;
  7. results cResults[10];
  8. results* ptrResults;
  9. void rmNewline(char*, int);    // Function to remove the newline from the user's input.
  10. void getDBVersionC();        // Function to show the database version.
  11.  
cSource.c
Expand|Select|Wrap|Line Numbers
  1. void getDBVersionC()
  2. {
  3.     ptrResults = cResults;
  4.     results* myResults;
  5.     myResults = getDBVersionCpp(ptrResults);
  6.     printf("\n");
  7.     printf("Database Version: %s\n", myResults[1].textResults[2]); 
  8.            //prints ----- Database Version: <some symbol>   (wtf?)
  9.     printf("What is gibberish: %s\n", myResults[1].textResults[1]);
  10.            //prints ----- What is gibberish:  gobble gobble
  11.     getchar();
  12. }
  13.  
cppheader.h
Expand|Select|Wrap|Line Numbers
  1. #include "cheader.h"
  2. extern "C"
  3. {
  4.     results* getDBVersionCpp(results* aryResults);
  5. }
  6.  
  7.  
cppSource.cpp
Expand|Select|Wrap|Line Numbers
  1. extern "C"
  2. {
  3.     results* getDBVersionCpp(results* aryResults)
  4.     {    /* Function for getting the version of the database */
  5.         char qResult[25];
  6.         char* ptrTextResult;
  7.         char* gobble = "gobblegobble";
  8.         std::string query = "select cast(versionstring as varchar(25)) from version;";
  9.  
  10.         //startHandling(1);
  11.         initConnection();
  12.         icConnection.dbStatement->Execute(query);
  13.  
  14.         while (icConnection.dbStatement->Fetch())
  15.         {
  16.             (icConnection.dbStatement)->Get(1, qResult);
  17.         }
  18.         //startHandling(0);
  19.         cout << "*****************************************************"<<endl;
  20.         cout << "qResult: " << qResult << endl;   
  21.                        //prints ----- qResult: 5.8.10 (works great)
  22.         ptrTextResult = qResult;
  23.         cout << "ptrResult: " << ptrTextResult << endl;
  24.                        //prints ------ ptrResult: 5.8.10 (works great)
  25.         cout << "*****************************************************"<<endl;
  26.         aryResults[1].textResults[1] = gobble;
  27.         aryResults[1].textResults[2] = ptrTextResult;
  28.         cout << "CPP: cResults[1].textResults[2]: " << aryResults[1].textResults[2] << endl;
  29.                    // prints CPP   cResults[1].textResults[2]: 5.8.10 (works) 
  30.         cout << "CPP: cResults[1].textResults[1]: " << aryResults[1].textResults[1] << endl;
  31.                   // prints CPP: cResults[1].textResults[1]: gobble gobble (works)
  32.         cout << "*****************************************************"<<endl;
  33.         closeConnection();
  34.  
  35.         return (aryResults);
  36.     }
  37. }// End Extern "C"
  38.  
Apr 8 '09 #13

weaknessforcats
Expert Mod 5K+
P: 9,197
typedef struct
{
char* textResults[1024];
int intResults[1024];
int exitStatus;
}results;
results cResults[10];
results* ptrResults;
This code says results is a type and that type is another name for that struct.

That's OK.

Next you create an array of results banking on that typedef.

That's OK.

Next you create a results* banking on that typedef.

That's OK.

Now to include this header again on the C++ side
thereby creating ANOTHER cResults array and ptrResults pointer.

That's NOT OK.

You never put code in a header file that allocates memory. When you do you get new variables every time you include that header.

So, remove
Expand|Select|Wrap|Line Numbers
  1. results cResults[10]; 
  2. results* ptrResults;  
from your header file and make them variables in your C main().

Change your function prototypes to have a result* argument. You call the function using the name of the array, cResults.

You don't need ptrResults.

Your function does not need to return a results* since you now pass it in.

Your C main() should look like:
Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3. results cResults[10]; 
  4. getDBVersionC(cResults);
Apr 9 '09 #14

Post your reply

Sign in to post your reply or Sign up for a free account.