0

Hello StackOverflow community, I ran into a problem with one of my Delphi projects. Wrote a sip-phone class to call clients. Works fine so far but I need the program to wait until the call is done. thats how I start a call with client:

procedure TDialog_TelefonVOIP_Test.ButtonQuickCallClick(Sender: TObject);
var
  tmpTel: TTelefon;
begin
  tmpTel := TTelefon.Create(Self, '192.168.x.y', 'xy', '******', True);
  try
    tmpTel.Call('001726599722', True, 'This is a text to speech test. Is this working?');
  finally
    FreeAndNil(tmpTel);
  end;
end;

The problem is that the program runs into FreeAndNil(tmpTel) before the call is done. Heres the simplified code of my TTelefon class:

unit TEST_Telefon;

interface

uses
  Classes, SysUtils, Controls, Dialogs, Vcl.ExtCtrls, Vcl.Forms, System.Threading,
  sipclient, call, ringtone;

type
  RCallData = record
    Call: ICall;
    Number: string;
    TTS: Boolean;
    TTSText: string;
  end;

var
  CallData: RCallData;
  SIP_Client: Tsipclient;

type
  TTelefon = class(TObject)
  private
    FServerConnected: Boolean;
    FRinging: Boolean;
    FCallConnected: Boolean;

    Caller: TComponent;

    procedure OnAnswer(Sender: TObject; const aCall: ICall);
    procedure OnHangUp(Sender: TObject; const aCall: ICall);

  protected
    function Connect(const aServer, aUser, aPass: string): Boolean;
    function Disconnect(): Boolean;

  public
    Server: string;
    User: string;
    Pass: string;

    property ServerConnected: Boolean read FServerConnected;
    property Ringing: Boolean read FRinging;
    property CallConnected: Boolean read FCallConnected;

    constructor Create(aCaller: TComponent; const aServer, aUser, aPass: string; aConnect: Boolean=True);
    destructor Destroy(); override;

    function Call(aNumber: string; aTTS: Boolean=False; aTTSText: String=''): Boolean;
    function HangUp(aDisconnect: Boolean=False): Boolean;

    function GetAnrufStatus(aAnruf: ICall=nil): string;
  end;

implementation

{ TTelefon }

constructor TTelefon.Create(aCaller: TComponent; const aServer, aUser, aPass: string; aConnect: Boolean=True);
begin
  try
    Caller := aCaller;

    if not Assigned(SIP_Client) then
      SIP_Client := TSIPclient.Create(Caller);

    SIP_Client.OnAnswer := OnAnswer;
    SIP_Client.OnBye := OnHangup;

    Server := aServer;
    User := aUser;
    Pass := aPass;

    FServerConnected := False;
    FCallConnected := False;
    FRinging := False;

    if aConnect then
      FServerConnected := Connect(Server, User, Pass);
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

function TTelefon.Connect(const aServer, aUser, aPass: string): Boolean;
var
  tmpConnected: Boolean;
begin
  Result := False;
  try
    tmpConnected := False;

    SIP_Client.Host := aServer;
    SIP_Client.User := aUser;
    SIP_Client.Password := aPass;
    SIP_Client.Proxy := aServer;
    SIP_Client.Active := True;
    SIP_Client.Register;
    tmpConnected := True;

    Result := tmpConnected;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

function TTelefon.Call(aNumber: string; aTTS: Boolean=False; aTTSText: String=''): Boolean;
var
  tmpOK: Boolean;
begin
  try
    tmpOK := False;
    if not FServerConnected then
      FServerConnected := Connect(Server, User, Pass);

    if FServerConnected then
    begin
      CallData.Number := aNumber;
      CallData.TTS := aTTS;
      CallData.TTSText := aTTSText;

      if not Assigned(CallData.Call) then
        CallData.Call := SIP_Client.Call(CallData.Number)
      else if CallData.Call.State = csRinging then
        CallData.Call.Answer()
    end;

    Result := tmpOK;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

function TTelefon.HangUp(aDisconnect: Boolean=False): Boolean;
var
  tmpAufgelegt: Boolean;
begin
  Result := False;
  try
    tmpAufgelegt := False;

    if FCallConnected then
    begin
      if Assigned(CallData.Call) then
        CallData.Call.EndCall();
      tmpAufgelegt := True;

      if aDisconnect and FServerConnected then
        tmpAufgelegt := Disconnect();
    end
    else
      tmpAufgelegt := True;

    OnHangUp(nil, CallData.Call);

    Result := tmpAufgelegt;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

function TTelefon.Disconnect(): Boolean;
begin
  Result := False;
  try
    SIP_Client.Active := False;
    FServerConnected := False;
    Result := not FServerConnected;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

procedure TTelefon.OnAnswer(Sender: TObject; const aCall: ICall);
begin
  inherited;
  try
    if Assigned(aCall) then
      CallData.Call := aCall;
    FCallConnected := True;
    FRinging := False;

    if CallData.TTS AND (CallData.TTSText <> '') then
    begin
      Sleep(50);
      if not Assigned(CallData.Call) then
        CallData.Call := aCall;
      CallData.Call.PlayText(CallData.TTSText, Integer(0));
    end;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

procedure TTelefon.OnHangUp(Sender: TObject; const aCall: ICall);
begin
  inherited;
  try
    if Assigned(aCall) then
      CallData.Call := aCall;
    FCallConnected := False;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;

destructor TTelefon.Destroy();
begin
  try
    try
      if FCallConnected then
        HangUp();
      if FServerConnected then
        Disconnect();
    finally
      FreeAndNil(SIP_Client);
    end;
  except
    on e: Exception do
      ShowMessage(e.Message);
  end;
end;


end.

What am i doing wrong? I think i need the functionality of ShowModal() inside the Call() method. Is that possible? I would be glad if anyone could help me!

Greetings

Morris F

MorrisF
  • 11
  • 4
  • have the sip thread post a message to the main thread when its done. In the main thread event you then can free the thread. – whosrdaddy Nov 10 '21 at 09:21
  • The problem is no freeing the thread but freeing my TTelefon calss. I already `FreeAndNil()` the Thread after its work is done inside the TTelefon class. The TCallThread calss is inside the TTelefon class. I think what i need is a nonvisual `ShowModal()` for the TTelefon class – MorrisF Nov 10 '21 at 09:37
  • First, you are missing [mcve] You haven't posted code from your `TTelefon.Call` method and possibly other parts of `TTelefon` class which is crucial for seeing what you are doing wrong. Please edit your question to add missing parts. You are also needlessly running `TTask` inside another thread `Execute` method, but that code is also pretty much broken in more way than one. too long to explain in a comment. – Dalija Prasnikar Nov 10 '21 at 10:10
  • You can find some examples on how to use threads and tasks in my GitHub repository https://github.com/dalijap/code-delphi-async Look under Part 4 -> 22 Tasks – Dalija Prasnikar Nov 10 '21 at 10:23
  • @DalijaPrasnikar, I did post the relevant code of my TTelefon.Call() method: "This is how i start the Thread (inside Call() method): ` if not Assigned(CallThread) then CallThread := TCallThread.Create(Caller, SIP_Client); CallThread.CallDone:= False; CAllThread.Resume; ` " – MorrisF Nov 10 '21 at 10:54
  • Why dont you explain it with a Answer? @DalijaPrasnikar :) – MorrisF Nov 10 '21 at 10:55
  • I am a bit tight on my schedule, so I cannot write an answer. also, you are missing some critical code here, so I would have to imagine what are you working with. That is why I gave you link to GitHub and some examples you can adapt to your code. If I would write an answer right now (without knowing other details), it would not be much different than examples there. – Dalija Prasnikar Nov 10 '21 at 11:44
  • just updated my question. Tell me if u can work with it when u got time – MorrisF Nov 10 '21 at 11:52
  • Free the phone object in the OnTerminate event handler or destructor of the thread – Delphi Coder Nov 10 '21 at 12:47
  • 1
    As far as I can see `TSipClient` is actually asynchronous. You don't need to wait for thread... you are trying to solve some other problem and you thought that adding threads will help. First, threads and tasks are also asynchronous, so if you have problem with asynchronous code you cannot solve that by adding more asynchronous code. I am not sure what is your original problem - is it memory management of `tmpTel` or you need to block UI or disable some parts. You need to go back, remove threads and tasks completely and ask about your original problem. – Dalija Prasnikar Nov 10 '21 at 21:19
  • 1
    Please see [What is the XY problem?](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Dalija Prasnikar Nov 10 '21 at 21:19
  • Well as mentioned i need something like `showModal()` for my `Call()` method. I added threads because google told me to lel But what u said makes sense... adding threads and task to make it more syncron was kinda dumb I am going to remove the thread and update my question – MorrisF Nov 11 '21 at 09:31
  • just updated my question and code. – MorrisF Nov 11 '21 at 09:42
  • 1
    It's asynchronous. You need to respond to the message or event that tells you that the call is done. You keep asking how to write the code synchronously even though you keep being told it is an async model. I suspect you need to spend some time learning what async programming styles are like. – David Heffernan Nov 11 '21 at 20:16
  • You have improved your question, but it still raises more questions on what you need. It is obvious that FreeAndNil fails, because `TSipClient` is asynchronous, meaning code initiating the call will finish before the actual call is completed. This kind of handling shouldn't be a problem as you have event handlers where you can add appropriate code, but for some reason you decided to wrap all that up in another class which now complicates your memory management. – Dalija Prasnikar Nov 15 '21 at 19:46
  • But while adding another class, you have also left some parts as globals - `CallData` and `SipClient` which does not make any sense because you cannot have more than one instance of `TTelefon` at the same time. Is your requirement to have single `SipClient` or you need to handle multiple ones? Why you need that additional wrapper class? – Dalija Prasnikar Nov 15 '21 at 19:49

0 Answers0