0

I'm quite new to C++/CMake and was trying to create a CMake file that would compile a GRPC project. I can generate the client (only want client) files fine using this CMake, but I'm trying to create another file (base.cpp) that will import a class (ManagerClient) from one of these files. I decided to create a header file for this file I want called manager_client.h.

I wasn't too sure where in the CMake file to compile base.cpp so added it as one the target directories and assumed the header file I made would get automatically linked. After running make, I am getting this error:

Undefined symbols for architecture x86_64:
  "ManagerClient::ManagerClient(std::__1::shared_ptr<grpc::Channel>)", referenced from:
      ReadUntilClient::ReadUntilClient() in base.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [base] Error 1
make[1]: *** [CMakeFiles/base.dir/all] Error 2
make: *** [all] Error 2

I found this question, but didn't really explain how I could resolve my issue. The strange thing is prior to making base.cpp, I was able to run base.cpp logic inside manager_client.cpp's main function without any issue, so I'm suspecting either I'm compiling it wrong via CMake or my header file is wrong. Any ideas where it's coming from?

Here is the code:

manager_client.cpp

#include <grpcpp/grpcpp.h>
#include <grpc/grpc.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <string>
#include <memory>

#include "minknow_api/manager.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using grpc::ClientReader;
using minknow_api::manager::ManagerService;
using minknow_api::manager::FlowCellPositionsRequest;
using minknow_api::manager::FlowCellPositionsResponse;


class ManagerClient {
  public:
    ManagerClient(std::shared_ptr<Channel> channel) : stub_(ManagerService::NewStub(channel)) {}

  std::unique_ptr<ClientReader<FlowCellPositionsResponse> > flow_cell_positions(FlowCellPositionsRequest request) {
      ClientContext context;
      return stub_->flow_cell_positions(&context, request);
  }
  private:
    std::unique_ptr<ManagerService::Stub> stub_;
};

int main(int argc, char* argv[]) {
  return 0;
}

manager_client.h

#pragma once

#include <grpcpp/grpcpp.h>
#include <grpc/grpc.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <string>
#include <memory>

#include "minknow_api/manager.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using grpc::ClientReader;
using minknow_api::manager::ManagerService;
using minknow_api::manager::FlowCellPositionsRequest;
using minknow_api::manager::FlowCellPositionsResponse;

class ManagerClient {
  public:
    ManagerClient(std::shared_ptr<Channel> channel);
    std::unique_ptr<ClientReader<FlowCellPositionsResponse> > flow_cell_positions(FlowCellPositionsRequest request);
  private:
    std::unique_ptr<ManagerService::Stub> stub_;
};

base.cpp

#include <grpcpp/grpcpp.h>
#include <grpc/grpc.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <string>
#include <thread>
#include <memory>
#include <iostream>

#include "manager_client.h"
#include "minknow_api/manager.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using grpc::ClientReader;
using minknow_api::manager::FlowCellPositionsRequest;
using minknow_api::manager::FlowCellPositionsResponse;

class ReadUntilClient {
  public:
    ManagerClient manager;
    ReadUntilClient() : manager(grpc::CreateChannel("127.0.0.1:9501",
                          grpc::InsecureChannelCredentials())) {}
};

int main(int argc, char* argv[]) {
  ReadUntilClient client;
  return 0;
}

CMakeLists.txt

// Above this I compile the proto files & I only included the code for relevant files
add_library(rg_grpc_proto
  ${man_grpc_srcs}
  ${man_grpc_hdrs}
  ${man_proto_srcs}
  ${man_proto_hdrs})
target_link_libraries(rg_grpc_proto
  ${_REFLECTION}
  ${_GRPC_GRPCPP}
  ${_PROTOBUF_LIBPROTOBUF})

foreach(_target
  manager_client
  base)
  add_executable(${_target}
    "${_target}.cpp")
  target_link_libraries(${_target}
    rg_grpc_proto
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF})
endforeach()
Brian Z.
  • 13
  • 2
  • 1
    you have an ODR violation, the declarations of `ManagerClient` in `manager_client.cpp` and `manager_client.h` are different, `manager_client.cpp` should include `manager_client.h` and not declare a new copy of `ManagerClient`, just define the constructor and method – Alan Birtles Jan 12 '22 at 12:17

1 Answers1

3

You seem to misunderstand how projects with multiple source files work.

In a project with multiple source files, the source files are typically built one by one into object files, and then the object files are linked (together with any libraries) into the executable program file.

Using CMake this is done by adding one executable target (with add_executable) that lists all the source files to be used for the program. Like:

add_executable(program base.cpp manager_client.cpp)

That will cause the build to create the executable program file program from the two source files base.cpp and manager_client.cpp (using the intermediate object file and linker steps outlined above).


As mentioned this change means you should not re-define the ManagerClient class in the source file, but instead #include the header file.

You also should not have a main function in each source file, only in a single source file should it be defined.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621