Deriving from TGNumberEntry gives compiler errors unresolved symbol

I am working on a code base using Root 5.28, building with VS2008. I am trying to inherit from some GUI controls in the application to extend their functionalities. I have started with TGNumberEntry and TGNumberEntryField, changing them so that the TGNumberEntry or TGNumberEntryField owns a unique ID which can later be queried for. I have also overridden TGNumberEntryField::SetNumber() so that after calling the parent class’s version, I can do some other work. There is more functionality to be added but this is the only functionality I added so far, and it is shown in the example code below. I have included the header file in the cpp file for the MyMainWindow class, and added member pointer variables, both of type TGNumberEntryField. The project compiles without a problem, but I get the following linker errors:

error LNK2001: unresolved external symbol "public: virtual void __thiscall MyNumberEntryField::ShowMembers(class TMemberInspector &)" (?ShowMembers@MyNumberEntryField@@UAEXAAVTMemberInspector@@@Z)
error LNK2001: unresolved external symbol "public: virtual void __thiscall MyNumberEntryField::Streamer(class TBuffer &)" (?Streamer@MyNumberEntryField@@UAEXAAVTBuffer@@@Z)
error LNK2019: unresolved external symbol "public: static class TClass * __cdecl MyNumberEntryField::Class(void)" (?Class@MyNumberEntryField@@SAPAVTClass@@XZ) referenced in function "public: virtual class TClass * __thiscall MyNumberEntryField::IsA(void)const " (?IsA@MyNumberEntryField@@UBEPAVTClass@@XZ)
error LNK2001: unresolved external symbol "public: virtual void __thiscall MyNumberEntry::ShowMembers(class TMemberInspector &)" (?ShowMembers@MyNumberEntry@@UAEXAAVTMemberInspector@@@Z)
error LNK2001: unresolved external symbol "public: virtual void __thiscall MyNumberEntry::Streamer(class TBuffer &)" (?Streamer@MyNumberEntry@@UAEXAAVTBuffer@@@Z)
error LNK2019: unresolved external symbol "public: static class TClass * __cdecl MyNumberEntry::Class(void)" (?Class@MyNumberEntry@@SAPAVTClass@@XZ) referenced in function "public: virtual class TClass * __thiscall MyNumberEntry::IsA(void)const " (?IsA@MyNumberEntry@@UBEPAVTClass@@XZ)
error LNK2019: unresolved external symbol "class ROOT::TGenericClassInfo * __cdecl ROOT::GenerateInitInstance(class MyNumberEntryField const *)" (?GenerateInitInstance@ROOT@@YAPAVTGenericClassInfo@1@PBVMyNumberEntryField@@@Z) referenced in function "void __cdecl ROOT::`dynamic initializer for 'R__dummyintdefault6''(void)" (??__ER__dummyintdefault6@ROOT@@YAXXZ)
error LNK2019: unresolved external symbol "class ROOT::TGenericClassInfo * __cdecl ROOT::GenerateInitInstance(class MyNumberEntry const *)" (?GenerateInitInstance@ROOT@@YAPAVTGenericClassInfo@1@PBVMyNumberEntry@@@Z) referenced in function "void __cdecl ROOT::`dynamic initializer for 'R__dummyintdefault7''(void)" (??__ER__dummyintdefault7@ROOT@@YAXXZ)

I have tried everything I can think of, looking down into Root code, but haven’t been able to figure this issue out. I am not yet well enough versed in how ClassDef stuff works in Root, so I am certain I am missing some small thing. While I continue digging, I am posting this to see if anyone has an idea of what I should look at in order to fix the issue. Thanks for any help. The code for my new classes (given as MyNumberEntry and MyNumberEntryField) is shown below:

MyNumberEntry.h:

#ifndef MY_NUMBER_ENTRY_H
#define MY_NUMBER_ENTRY_H

#include "Rtypes.h"
#include "Windows4Root.h"
#include "TGNumberEntry.h"

class MyNumberEntryField : public TGNumberEntryField
{
public:
    MyNumberEntryField(const TGWindow *p, Int_t id,
                      Double_t val, GContext_t norm,
                      FontStruct_t font = GetDefaultFontStruct(),
                      UInt_t option = kSunkenFrame | kDoubleBorder,
                      Pixel_t back = GetWhitePixel());
    MyNumberEntryField(const TGWindow *parent = 0,
                      Int_t id = -1, Double_t val = 0,
                      EStyle style = kNESReal,
                      EAttribute attr = kNEAAnyNumber,
                      ELimit limits = kNELNoLimits,
                      Double_t min = 0, Double_t max = 1);
    ~MyNumberEntryField() { }
    
    virtual void SetNumber(Double_t val);
    void SetObjID(int id) { m_iObjId = id; }
    int GetObjID() { return m_iObjId; }

private:
    int m_iObjId;

public:
   ClassDef(MyNumberEntryField,0)  // Entry field widget for several numeric formats (wraps TGNumberEntryField)
};

class MyNumberEntry : public TGNumberEntry
{
public:
    MyNumberEntry(const TGWindow *parent = 0, Double_t val = 0,
                 Int_t digitwidth = 5, Int_t id = -1, 
                 EStyle style = kNESReal,
                 EAttribute attr = kNEAAnyNumber,
                 ELimit limits = kNELNoLimits,
                 Double_t min = 0, Double_t max = 1);
    ~MyNumberEntry() { }

    void SetObjID(int id) { fNumericEntry->SetObjID(id); }
    int GetObjID() { return fNumericEntry->GetObjID(); }

    MyNumberEntryField *GetNumberEntry() const {
        // Get the number entry field
        return fNumericEntry;
    }

protected:
   MyNumberEntryField *fNumericEntry;  // Number text entry field

public:
   ClassDef(MyNumberEntry,0)  // Entry field widget for several numeric formats (wraps TGNumberEntry)
};

#endif // MY_NUMBER_ENTRY_H

MyNumberEntry.cpp:

#include "MyNumberEntry.h"

ClassImp(MyNumberEntryField);
ClassImp(MyNumberEntry);

/*******************************************
/*  class MyNumberEntryField
/*******************************************/

MyNumberEntryField::MyNumberEntryField(const TGWindow * p, Int_t id,
                                       Double_t val, GContext_t norm,
                                       FontStruct_t font, UInt_t option,
                                       ULong_t back)
    : TGNumberEntryField(p, id, val, norm, font, option, back) { }

MyNumberEntryField::MyNumberEntryField(const TGWindow * parent,
                                       Int_t id, Double_t val,
                                       EStyle style, EAttribute attr,
                                       ELimit limits, Double_t min,
                                       Double_t max)
    : TGNumberEntryField(parent, id, val, style, attr, limits, min, max) { }

MyNumberEntryField::~MyNumberEntryField()
{
}

void MyNumberEntryField::SetNumber(Double_t val)
{
    // set the number in the base class
    TGNumberEntryField::SetNumber(val);
    // then do my stuff
    DoMyStuff(); // function not shown
}
    
/*******************************************
/*  class MyNumberEntry
/*******************************************/

MyNumberEntry::MyNumberEntry(const TGWindow *parent, Double_t val,
                 Int_t digitwidth, Int_t id, 
                 EStyle style,
                 EAttribute attr,
                 ELimit limits,
                 Double_t min, Double_t max)
    : TGNumberEntry(parent, val, digitwidth, id, style, attr, limits, min, max)
{
}

MyNumberEntry::~MyNumberEntry()
{
}

ROOT Version: 5.28
Platform: Window 7
Compiler: VS2008


Did you generate a dictionary for that class? (It is required for any class with a ClassDef macro unless you use ClassDefInline).

1 Like

I hadn’t. I created a dictionary and now it works. Thanks. I am still trying to learn the ins and outs of Root. I had looked up the ClassDef and ClassImp, but didn’t find any useful information. Thanks. Now I can move forward with this.

Note that ClassImp is vestigial and no longer needed.

Is that the case for 5.28 though? ClassImpl being vestigial I mean.

Same-ish. It is helpful only if you (still) rely/use THtml to generate documentation.

Not sure if this should be a new topic. I’ll post it here since it seems directly related, and hope that someone will let me know if it should be. But I’m just wondering. I have gotten passed the original problem by creating the necessary dictionary file and compiling it into my build. Now I’m trying to create a Connect to my object to replace the Connect that was made to the original TGNumberEntry object. I have updated the header file as followings:

class MyNumberEntry : public TGNumberEntry
{
public:
	...
	Constructor/destructor decl
	...
	
	virtual void ValueSet(Long_t val); //*SIGNAL*  - this is the new declaration

public:
	ClassDef(MyNumberEntry,0)  // Entry field widget for several numeric formats (extends TGNumberEntry)
};

And have updated my cpp file as:

...
	Constructor/destructor def
...

void MyNumberEntry::ValueSet(Long_t val) //*SIGNAL* - this is the new definition
{
	Emit("ValueSet(Long_t)", val);
}

Basically I have just made a copy of the ValueSet() signal as it is in TGNumberEntry. This is because when I tried to just connect to the Connect() call, just as it was before inheriting the class, hoping that it would connect to TGNumberEntry::ValueSet(), it crashes the application with “0xC0000005: Access violation reading location 0x00000014”. The stack says it happened calling IsA() inside the ClassDef() macro, which calls Class() and crashes. As stated I have the necessary dictionary being created, using the line:

(ROOTSYS)\bin\rootcint -f (InputDir)MyNumberEntry_dictionary.cpp -c (InputDir)MyNumberEntry.h "(InputDir)LinkDef_MyNumberEntry.h"

where the LinkDef_MyNumberEntry.h file is as follows:

#pragma link off all globals;
#pragma link off all classes;
#pragma link off all functions;

// classes inherited from TGNumberEntry and TGNumberEntryField
#pragma link C++ class MyNumberEntry+;
#pragma link C++ class MyNumberEntryField+;

When I add MyNumberEntry::ValueSet(Long_t val) to my inherited class, I get the same crash. I really am not sure what I am missing this time.

Can you run the failing example with valgrind:

valgrind --suppressions=$ROOTSYS/etc/valgrind-root.supp ....

this might tell us what is going wrong.