This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Undefined symbol XXX using a C++ function

Admittedly I am new to C++ and am much more comfortable in ANSI C, so this might be a simple problem:

I have some C++ files for my application that are running on top of the CubeMX HAL from ST. I am running bare metal.

My project all compiles cleanly with no warnings or errors. When it tries to link I get an undefined symbol error. My suspicions are it has something to do with name mangling from C++, but I'm not certain. I am using the extern "C" {} around the c++ header files to accommodate the mixed c/c++

Below is the definition of the functions (overloaded), the complete include file, and a snippet from the usage that is throwing the linkage error.

linking...
DiscBoard-Interm\DiscBoard-Interm.axf: Error: L6218E: Undefined symbol GPIO::RWGpio(GPIO::Pin_e) (referred from max31856.o).
DiscBoard-Interm\DiscBoard-Interm.axf: Error: L6218E: Undefined symbol GPIO::RWGpio(GPIO::Pin_e, GPIO::AbstractState_e) (referred from max31856.o).
Not enough information to list image symbols.
Finished: 1 information, 0 warning and 2 error messages.


/**
   @brief Read/Write GPIO

   This function will take the parameters and determine if the request is a read or write request.
   The index will be the particular IO to use, and the data passed is the set value.  If there is no
   set value passed in, the function will return the existing value.  If a value is passed, it will set
   the value and return.

   Global variables
      none

*/
GPIO::AbstractState_e RWPGpio(GPIO::Pin_e PinIdx)
{
   GPIO_PinState State;
   GPIO::AbstractState_e retval;

   if (GpioCfgTable[PinIdx].Idx == PinIdx)  //make sure table is consistent with request
   {
      State = HAL_GPIO_ReadPin(GpioCfgTable[PinIdx].Port,GpioCfgTable[PinIdx].Pin);
      if (GpioCfgTable[PinIdx].ActiveState == GPIO_PIN_SET)
      {
         //normal positive polarity so do nothing
         retval = (GPIO::AbstractState_e)State;
      }
      else
      {
         //we need to invert on/off
         retval = (GPIO::AbstractState_e)((State + 1)>>1);
      }
   }
   else
   {
      //inconsistent def, throw an error and do nothing
   }
   return retval;
}

/**
   @brief Read/Write GPIO

   This function will take the parameters and determine if the request is a read or write request.
   The index will be the particular IO to use, and the data passed is the set value.  If there is no
   set value passed in, the function will return the existing value.  If a value is passed, it will set
   the value and return.

   Global variables
      none

*/
void RWGpio(GPIO::Pin_e PinIdx, GPIO::AbstractState_e State)
{
   GPIO_PinState PinState;

   if (GpioCfgTable[PinIdx].ActiveState == GPIO_PIN_SET)
   {
      //normal positive polarity so do nothing
      PinState = (GPIO_PinState)State;
   }
   else
   {
      //we need to invert on/off
       PinState = (GPIO_PinState)((State + 1)>>1);
   }

   if (GpioCfgTable[PinIdx].Idx == PinIdx)  //make sure table is consistent with request
   {
      HAL_GPIO_WritePin(GpioCfgTable[PinIdx].Port,GpioCfgTable[PinIdx].Pin,PinState);

   }
   else
   {
      //inconsistent def, throw an error and do nothing
   }
   //return state;
}
#ifndef GPIO_H
#define GPIO_H


#ifdef __cplusplus extern "C" { #endif
#include "stm32f4xx_hal.h" // Keil::Device:STM32Cube HAL:Common #include <stdint.h> #include "stm32f4xx_hal_gpio.h" //GPIO defs
struct GPIOConfig { uint8_t Idx; GPIO_TypeDef* Port; uint16_t Pin; GPIO_PinState ActiveState; };
class GPIO {
public: enum AbstractState_e { OFF=0, ON }; enum Pin_e { UC_KICK_WDOG = 0, CONTACT_QUALITY_MON, CH4_TEMP_FAULT, CH4_RF_ENABLE, CH4_CAL_RELAY, FAN_TEMP_ALERT, CH4_RETURN_RELAY, UC_WKUP, CH4_RF_VOLTAGE_MON, CH4_RF_CURRENT_MON, CH3_RF_VOLTAGE_MON, CH3_RF_CURRENT_MON, CH2_RF_V_MON, CH2_RF_I_MON, CH1_RF_V_MON, CH1_RF_I_MON, CH4_TEMP_DATA_READY, CH4_DAC_SPI3_CS, CH4_TEMP_SPI6_CS, CH3_TEMP_FAULT, CH3_RF_ENABLE, CH3_CAL_RELAY, CH3_RETURN_RELAY, CH3_TEMP_DATA_READY, CH3_DAC_SPI3_CS, CH3_TEMP_SPI6_CS, CH2_TEMP_FAULT, CH2_RF_ENABLE, CH2_CAL_RELAY, CH2_RETURN_RELAY, CH2_TEMP_DATA_READY, CH2_DAC_SPI3_CS, CH2_TEMP_SPI6_CS, CH1_RF_ENABLE, CH1_TEMP_FAULT, CH1_CAL_RELAY, CH1_RETURN_RELAY, CH1_TEMP_DATA_READY, CH1_DAC_SPI3_CS, CH1_TEMP_SPI6_CS, STIM_NEG, STIM_POS, STIM_SELECT, DEBUG_OUTPUT_PC7, DEBUG_OUTPUT_PC8, DEBUG_INPUT_PC9, FAN_FLT_FULL_SPEED, OSC_460KHZ_ENABLE, FAN_OVERTEMP, H_LOCKOUT_RF_MONITOR, L_STANDBY_ACTIVE, L_UC_MASTER_DISABLE_RF, L_WDOG_TIMER_EXPIRED, UC_WDOG_REG_DAT, UC_WDOG_REG_CLK, LED3_PE0, LED4_PE1, MAX_PIN_CNT };
private: //TODO Come back and figure out how to make this a private const table //and where does it live in memory //static const GPIO_Config GpioCfgTable[];
public: GPIO_PinState RWGpio(Pin_e PinIdx); void RWGpio(Pin_e PinIdx,AbstractState_e State); }; //end class
#ifdef __cplusplus } #endif
#endif //GPIO_H


usage:

Thermocouple::TempTenths Thermocouple::ReadThermocouple (uint8_t Tidx)
{
uint8_t spiCommand[3];
uint8_t spiResp[3];
//HAL_StatusTypeDef retStatus;
GPIO_PinState State;
GPIO rwGPIO;
TempTenths temperature;

      State = rwGPIO.RWGpio(rwGPIO.CH2_TEMP_DATA_READY);

      if (State == GPIO_PIN_RESET)
      {
         //Read the temperature
         spiCommand[0] = 0x0c;

         rwGPIO.RWGpio(rwGPIO.CH2_DAC_SPI3_CS,rwGPIO.ON);
         //Transmitting 2 more bytes than configured.  Doesn't really matter, but will be garbage.
         HAL_SPI_TransmitReceive(&hspi6, (uint8_t*)&spiCommand,(uint8_t*)&spiResp,sizeof(spiCommand),1000);
         rwGPIO.RWGpio(rwGPIO.CH2_DAC_SPI3_CS,rwGPIO.OFF);

      }
      //TODO check this response for accuracy
      temperature = (spiResp[0]<<8) & spiResp[1];
      return temperature;
}

Parents
  • I am using the extern "C" {} around the c++ header files to accommodate the mixed c/c++

    Putting extern "C" {} around a class type definition does not do what you think it does.

    *) It cannot make a C compiler recognize "class" as a reserved word, so putting #ifndef __cplusplus around this particular extern "C" {} is ultimately misleading. You would have to use "struct" instead, and even then you still would get to put functions into it, when this header is included into a C translation unit.

    *) It does not move a C++ class's ordinary member functions into the normal, unmangeld world of "C" linkage.

    *) It cannot make C code accept function argument overloading.

    *) It does not make a plain global function like

    GPIO::AbstractState_e RWPGpio(GPIO::Pin_e PinIdx)
    {
    /*...*/
    }
    


    an actual class member function which can be called using the

    variable.method(argument);
    

    idiom.

    To paraphrase a certain Mr. Kenobi: "These aren't the tools you're looking for."

Reply
  • I am using the extern "C" {} around the c++ header files to accommodate the mixed c/c++

    Putting extern "C" {} around a class type definition does not do what you think it does.

    *) It cannot make a C compiler recognize "class" as a reserved word, so putting #ifndef __cplusplus around this particular extern "C" {} is ultimately misleading. You would have to use "struct" instead, and even then you still would get to put functions into it, when this header is included into a C translation unit.

    *) It does not move a C++ class's ordinary member functions into the normal, unmangeld world of "C" linkage.

    *) It cannot make C code accept function argument overloading.

    *) It does not make a plain global function like

    GPIO::AbstractState_e RWPGpio(GPIO::Pin_e PinIdx)
    {
    /*...*/
    }
    


    an actual class member function which can be called using the

    variable.method(argument);
    

    idiom.

    To paraphrase a certain Mr. Kenobi: "These aren't the tools you're looking for."

Children