Home > Net >  How do I compile a CPP file with a C file?
How do I compile a CPP file with a C file?

Time:03-04

I am trying to implement my pthread_create function. After searching online I found few examples but I could not compile them and run the code. I have these 2 files, first one is pthread.c

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>


int (*original_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL;

void load_original_pthread_create() {
    void *handle = dlopen("libpthread-2.15.so", RTLD_LAZY);
    char *err = dlerror();
    if (err) {
        printf("%s\n", err);
    }
    original_pthread_create = dlsym(handle, "pthread_create");
    err = dlerror();
    if (err) {
        printf("%s\n", err);
    }
}
int my_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
    if (original_pthread_create == NULL) {
        load_original_pthread_create();
    }
    printf("I am creating thread from my pthread_create\n");
    return original_pthread_create(thread, attr, start_routine, arg);
}

I compiled this using the below command and got a shared object named libpthread.so

gcc pthread.c -o libmypthread.so -shared -fpic -ldl

Now the second file, main.cpp

#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <ostream>
#include <iostream>
#include <thread>
#include <chrono>
#include <pthread.h>
#include <signal.h>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <execinfo.h>
#include <sstream>
#include <dlfcn.h>
#include <unistd.h>

void *dummyThread(void *t)
{
    // just spin/sleep until done
    //std::cout<<"This thread ID is "<<std::this_thread::get_id()<<std::endl;
    //long thID = (long) id;
    printf("thread Id is  pthread id is %lu\n", pthread_self());
    while(!done)
    {
        sleep(10);
    }
}

int main(int argc, char*argv[])
{
    printf("Hello World!\n");
    pthread_t ptid1, ptid2;
 
    int ret1 = my_pthread_create(&ptid1, NULL, dummyThread, NULL);
    int ret2 = my_pthread_create(&ptid2, NULL, dummyThread, NULL);
    
    pthread_exit(NULL);
    printf ("Goodbye Cruel World!\n");
}

To compile this above code, I use a Makefile

CC=gcc
CXX=g  
RM=rm -f
CPPFLAGS=-g 
CXXFLAGS=-std=c  17
LDFLAGS=-g -rdynamic
LDLIBS=-lpthread -ldl

SRCS=main.cpp
OBJS=$(subst .cpp,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

when I run make, I get this following error:

undefined reference to my_pthread_create(unsigned long*, pthread_attr_t const*, void* (*)(void*), void*)' /usr/bin/ld: /home/hgovindh/perf/main.cpp:: undefined reference to my_pthread_create(unsigned long*, pthread_attr_t const*, void* ()(void), void*)' collect2: error: ld returned 1 exit status make: *** [Makefile:16: tool] Error 1

Now, how do I run this main.cpp so that it calls the my_pthread_create defined in the pthread.c file?

CodePudding user response:

Are you familiar with C 's name mangling? In C, if you have a function named foo -- regardless of the arguments it receives -- the symbol in the .o file is called foo. In C , the name is mangled to include type information. This is how you can engage in method name overloading based on different arguments.

In your include file, you need to use

 extern "C" {
 ....
 }

This tells the C compiler that the symbols inside those braces are C symbols, and don't name-mangle them.

It will actually look something like this:

#ifdef __cplusplus
extern "C" {
#endif

With similar ifdef around the closing brace.

You should wrap your entire C .h file in this type of guard so it can be included in your C files. The other choice is to guard it when you include it, but it's better if you do the guard in the include file itself.

So you'll ultimately have something like this:

#ifdef __cplusplus
extern "C" {
#endif

void load_original_pthread_create();
int my_pthread_create(pthread_t *, const pthread_attr_t *, void *(*) (void *), void *);

#ifdef __cplusplus
}
#endif

CodePudding user response:

You should be able do create different compilation rules depending on the file extension. Note that my preferred approach of using cmake is presented at the end of the answer.

CC=gcc
CXX=g  
RM=rm -f
CPPFLAGS=-g 
CXXFLAGS=-std=c  17
CFLAGS=-g
LDFLAGS=-g -rdynamic
LDLIBS=-lpthread -ldl

SRCS=main.cpp pthread.c
OBJS=$(addsuffix .o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

%.cpp.o: %.cpp
    $(CXX) -c -o $@ $(CPPFLAGS) $(CXXFLAGS) $<

%.c.o: %.c
    $(CC) -c -o $@ $(CFLAGS) $<

this appends the .o to the file name including extension to be able to tell .c files appart from .c files in the rules for the .o file.

Alternative: Let CMake generate the makefiles

CMake can take care of generating the project files for you. It will automatically choose some reasonable defaults and allows you to change the build system you're using without much effort e.g. to ninja instead of unix makefiles.

CMakeLists.txt

(Place in the directory containing the source files)

cmake_minimum_required(VERSION 3.16)
# compile features for c  17 only documented in v 3.16, but may be available earlier

project(Tool)

add_executable(tool
    main.cpp
    pthread.c
)

# make sure at least C  17 is used
target_compile_features(tool PRIVATE cxx_std_17)

set_target_properties(tool PROPERTIES
    CXX_EXTENSIONS Off # no -std=gnu-...
    # CXX_STANDARD 17 # require an exact standard (remove target_compile_feature, if commented in)
)

target_link_libraries(tool PRIVATE pthread ${CMAKE_DL_LIBS})

Building the project on linux should use gcc and g as default, unless you modified the environment variables to point to other compilers, in addition to using the generator "Unix Makefiles" by default.

Choose the name of a directory where CMake is free to modify/remove/overwrite any files without messing with your project files. I use build_release and build_debug below. From the directory containing the CMakeLists.txt file, execute the following commands to create makefile projects for the release and debug version respectively. This step is necessary only once.

cmake -D CMAKE_BUILD_TYPE=Debug -S . -B build_debug
cmake -D CMAKE_BUILD_TYPE=Release -S . -B build_release

Now the equivalent of running make all would be

cmake --build build_debug
cmake --build build_release

(alternatively you could change the working directory to build_debug or build_release and run make all, but the cmake --build ... version works with any Generator type even without changing the working directory.)

  • Related