Alexey
Alexey

Reputation: 1

"Unresolved external" errors while compiling C++Builder 6 project

I have some errors while compiling a C++Builder 6 threads project. I don't know how to fix them.

Unit1.cpp:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

using namespace std;

int i1 = 0, i2 = 0;
T1 *Thread1, *Thread2;
bool Term1, Term2;
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}

void __fastcall T1::Execute()
{
    do
        Synchronize (UpdateTimer);
    while (!Terminated);
}
//-----------------------------------------------
void __fastcall T1::UpdateTimer()
{
    Timer->Enabled = !Terminated;
}

//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    ComboBox1->ItemIndex = 3;
    ComboBox2->ItemIndex = 3;
    Thread1 = new T1(false);
    Thread1->Timer = Timer1;
    Thread1->FreeOnTerminate = true;
    Term1 = false;
    Thread2 = new T1(true);
    Thread2->Timer = Timer2;
    Thread2->FreeOnTerminate = true;
    Term2 = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    i1++;
    Label1->Caption = i1;
    Timer1->Enabled = false;

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer2Timer(TObject *Sender)
{
    i2++;
    Label2->Caption = i2;
    Timer2->Enabled = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BRes1Click(TObject *Sender)
{
    if (!Term1)
        Thread1->Resume();
    else ShowMessage("Ïîòîê óæå çàâåðøåí");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BRes2Click(TObject *Sender)
{
    if (!Term2)
        Thread2->Resume();
    else ShowMessage("Ïîòîê óæå çàâåðøåí");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BTerm1Click(TObject *Sender)
{
    Thread1->Terminate();
    Term1 = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BTerm2Click(TObject *Sender)
{
    Thread2->Terminate();
    Term2 = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BSuspend1Click(TObject *Sender)
{
    if (!Term1)
        Thread1->Suspend();
    else ShowMessage("Ïîòîê óæå çàâåðøåí");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BSuspend2Click(TObject *Sender)
{
    if (!Term2)
        Thread2->Suspend();
    else ShowMessage("Ïîòîê óæå çàâåðøåí");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BSyncClick(TObject *Sender)
{
    if (Term1 || Term2)
        ShowMessage("Ïîòîê óæå çàâåðøåí");
    else
    {
        if (!Thread1->Suspended)
            Thread1->Suspend();
        if (!Thread2->Suspended)
            Thread2->Suspend();
        Timer1->Enabled = false;
        Timer2->Enabled = false;
        i1 = 0;
        i2 = 0;
        Thread1->Resume();
        Thread2->Resume();
    }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{
    if (Term1)
        ShowMessage("Ïîòîê óæå çàâåðøåí");
    else
        switch (ComboBox1->ItemIndex)
        {
            case 0: Thread1->Priority = tpIdle;          break;
            case 1: Thread1->Priority = tpLowest;        break;
            case 2: Thread1->Priority = tpLower;         break;
            case 3: Thread1->Priority = tpNormal;        break;
            case 4: Thread1->Priority = tpHigher;        break;
            case 5: Thread1->Priority = tpHighest;       break;
            case 6: Thread1->Priority = tpTimeCritical;  break;
        }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ComboBox2Change(TObject *Sender)
{
    if (Term2)
        ShowMessage("Ïîòîê óæå çàâåðøåí");
    else
        switch (ComboBox2->ItemIndex)
        {
            case 0: Thread2->Priority = tpIdle;          break;
            case 1: Thread2->Priority = tpLowest;        break;
            case 2: Thread2->Priority = tpLower;         break;
            case 3: Thread2->Priority = tpNormal;        break;
            case 4: Thread2->Priority = tpHigher;        break;
            case 5: Thread2->Priority = tpHighest;       break;
            case 6: Thread2->Priority = tpTimeCritical;  break;
        }
}
//---------------------------------------------------------------------------

Uni1.h

//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TGroupBox *GroupBox1;
    TGroupBox *GroupBox2;
    TLabel *Label1;
    TButton *BRes1;
    TButton *BSuspend1;
    TButton *BTerm1;
    TLabel *Label2;
    TButton *BRes2;
    TButton *BSuspend2;
    TButton *BTerm2;
    TComboBox *ComboBox1;
    TComboBox *ComboBox2;
    TButton *BSync;
    TTimer *Timer1;
    TTimer *Timer2;
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall Timer1Timer(TObject *Sender);
    void __fastcall Timer2Timer(TObject *Sender);
    void __fastcall BRes1Click(TObject *Sender);
    void __fastcall BRes2Click(TObject *Sender);
    void __fastcall BTerm1Click(TObject *Sender);
    void __fastcall BTerm2Click(TObject *Sender);
    void __fastcall BSuspend1Click(TObject *Sender);
    void __fastcall BSuspend2Click(TObject *Sender);
    void __fastcall BSyncClick(TObject *Sender);
    void __fastcall ComboBox1Change(TObject *Sender);
    void __fastcall ComboBox2Change(TObject *Sender);
private:    // User declarations
public:     // User declarations
    __fastcall TForm1(TComponent* Owner);
};

class T1 : public TThread
{
private:
    void _fastcall UpdateTimer();
protected:
    void _fastcall Execute();
public:
    __fastcall T1(bool CreateSuspended);
    TTimer *Timer;
};

//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Errors:

[Linker Error] Unresolved external 't1::' referenced from C:\USERS\ALEXEY\DESKTOP\CCC\UNIT1.OBJ
[Linker Error] Unresolved external '__fastcall T1::T1(bool)' referenced from C:\USERS\ALEXEY\DESKTOP\CCC\UNIT1.OBJ

Upvotes: 0

Views: 975

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595329

You have declared a class T1 that has a constructor declared in it, but you did not actually implement that constructor. That is what the linker is complaining about.

You need to add that implementation to your .cpp file, eg:

__fastcall T1::T1(bool CreateSuspended)
    : TThread(CreateSuspended)
{
    FreeOnTerminate = true; // <-- this should be moved in here
}

That will solve the linker error.


That being said, you have some design flaws in your code.

You are creating the Thread1 object with CreateSuspended=false, which gives it permission to start running immediately once its constructor is finished. But you are not assigning its Timer member until after the constructor has exited. So the thread can start running before its Timer is assigned. The only reason this "works" in your example is because your use of TThread::Synchronize() will not call T1::UpdateTimer() until after TForm1::FormCreate() 1 has assigned the Timer member and exited

1: BTW, you should NOT be using that event in C++ at all, as it can cause undefined behavior. Use the Form's constructor instead.

But this is not a good design to rely on. Since your thread class depends on an external TTimer object, you should either:

  • create the Thread1 object with CreateSuspended=true instead, and then Resume() that thread after you have fully initialized its members. Which is what you are doing with the Thread2 object.

  • pass the desired TTimer object pointer to each thread's constructor, eg:

class T1 : public TThread
{
private:
    TTimer *Timer;
    void _fastcall UpdateTimer();
protected:
    void _fastcall Execute();
public:
    __fastcall T1(bool CreateSuspended, TTimer *ATimer);
};

...

__fastcall T1::T1(bool CreateSuspended, TTimer *ATimer)
    : TThread(CreateSuspended), Timer(ATimer)
{
    FreeOnTerminate = true;
}

...

Thread1 = new T1(false, Timer1);
Thread2 = new T1(true, Timer2);

Also, you are setting FreeOnTerminate=true for both threads, but you are not assigning an OnTerminate event handler to each thread, so you have no way to know for sure if the threads are actually valid when you try to access them in your button OnClick event handlers. If the threads terminate for any reason, including an unexpected crash, then you will be accessing dangling pointers.

Also, you should not be Suspend()'ing a running thread from outside of that thread's own Execute() method for ANY reason. You don't know the state of the thread at the time of the suspension. It could be (and likely will be, in this case) in the middle of a Synchronize() call, which can cause deadlocks that are waiting on the suspended Synchronize() to finish its work. If you want to pause a worker thread, use a separate flag instead, or even a TEvent, which the thread can check periodically when it is safe and appropriate to do so.


Lastly, calling TThread::Synchronize() in a tight loop in a worker thread, as you are doing, is a complete waste of a worker thread, since ALL of its work is being performed in the main UI thread only. You may as well just get rid of your TThread class completely. Using a TTimer instead will suffice, or a PostMessage() loop, or a TApplication::OnIdle event handler, or a TAction(Manager)::OnUpdate event handler, etc. Many different ways to approach this.

Upvotes: 1

Related Questions