Jump to content
RokWeb

iOS 17, Delphi 11.3, TidTCPClient, CP1251

Recommended Posts

Hello.

 

Users of my application began to complain about an error in the application after updating to iOS 17 (screenshot attached). The server is written in Delphi 7 and sends data in CP1251 encoding (it is not possible to change this). Before the iOS 17 update, the application worked and no errors occurred.

 

The exception occurs in the code section of the idGlobal.pas module:

  else
    FMaxCharSize := LocaleCharsFromUnicode(FCodePage, FWCharToMBFlags, @cValue[0], 2, nil, 0, nil, nil); //returns less than 1 and then throws an exception RSInvalidCodePage
    if FMaxCharSize < 1 then begin
      raise EIdException.CreateResFmt(@RSInvalidCodePage, [FCodePage]);
    end;

 

From the information that I was able to find, a relatively similar problem was discussed here:
https://youtrack.jetbrains.com/issue/KT-59124/kmm-production-sample-iOS-app-crashes-after-start-on-iOS17-Beta

 

If I understand correctly, the problem was solved by updating XCode to 15 Beta 6 and using the current version of the iOS SDK.

 

Tell me, please, has anyone encountered this problem? Are there any solutions (I can’t update to the latest versions of XCode and iOS SDK)?

Screenshot-0928-101654.png

Share this post


Link to post
3 hours ago, RokWeb said:

If I understand correctly, the problem was solved by updating XCode to 15 Beta 6 and using the current version of the iOS SDK.

If I understand correctly, the current beta of Xcode is relevant only to those developing for Vision OS -  it is otherwise the same as the released version.

3 hours ago, RokWeb said:

The exception occurs in the code section of the idGlobal.pas module

Can you provide evidence that this is the case? Regardless, I'm not sure why Indy chooses to use its own function over the one in System.pas - the code for it is executed differently on iOS than on Windows.

Share this post


Link to post
10 hours ago, Dave Nottage said:

Can you provide evidence that this is the case?

Clearly it is, since the user is seeing an Indy error message.

10 hours ago, Dave Nottage said:

Regardless, I'm not sure why Indy chooses to use its own function over the one in System.pas - the code for it is executed differently on iOS than on Windows.

Which function are you referring to? The failing code is calling LocaleCharsFromUnicode(), which is an RTL system function.

Share this post


Link to post
14 hours ago, RokWeb said:

Users of my application began to complain about an error in the application after updating to iOS 17 (screenshot attached). The server is written in Delphi 7 and sends data in CP1251 encoding (it is not possible to change this). Before the iOS 17 update, the application worked and no errors occurred.

What does the call stack look like when the error occurs? What is your code doing that it is trying to instantiate an Indy encoding that is not working correctly with CP1251?

Share this post


Link to post
16 minutes ago, Remy Lebeau said:

Как выглядит стек вызовов в случае возникновения ошибки? Что делает ваш код, который пытается создать экземпляр кодировки Indy, которая не работает правильно с CP1251?

I can't check because I have an iPhone with iOS 15.6 and updating to iOS 17 is scary since Delphi 11.3 doesn't fully support iOS 17 (for example, as far as I know, debugging doesn't work)

 

constructor TClientThread.Create(AHost: String; APort: Integer);
begin
  inherited Create(True);
  FClient := TIdTCPClient.Create;
  FClient.ReadTimeout := 100;
  FClient.ConnectTimeout := 1000;
  FHost := AHost;
  FClient.Host := FHost;
  FClient.Port := APort;
  FClient.CreateIOHandler();
  FClient.IOHandler.DefStringEncoding := IndyTextEncoding(1251);
end;

 

And the Execute method of the data flow:

    try
      try
        while not Terminated do
        begin

          Sleep(1);

          FClient.IOHandler.CheckForDisconnect(true,true);

          if FClient.IOHandler.InputBufferIsEmpty then
          begin
            if not FClient.IOHandler.CheckForDataOnSource(10) then
            begin
                CSForData.Enter;
                try
                  if (Length(FSendData) > 0) then
                  FClient.IOHandler.Write(FSendData, IndyTextEncoding(1251));
                  FSendData := '';
                finally
                  CSForData.Leave;
                end;
                Continue;
            end;
          end;
            {$IFDEF IOS}
            FData := FData + FClient.IOHandler.InputBufferAsString(IndyTextEncoding(1251));
            {$ENDIF}

          if Length(FData) > 0 then
          begin
            Synchronize(nil,DoData);
            SetLength(FData, 0);
          end;

          CSForData.Enter;
          try
            if (Length(FSendData) > 0) then FClient.IOHandler.Write(FSendData, IndyTextEncoding(1251));
            FSendData := '';
          finally
            CSForData.Leave;
          end;

        end;

      except
        on E: Exception do
          DoError(E);
      end;
    finally
      FClient.Disconnect(False);
      if FClient.IOHandler <> nil then FClient.IOHandler.InputBuffer.Clear;
      Synchronize(DoDisconnected);
    end;

 

Edited by RokWeb

Share this post


Link to post
24 minutes ago, Remy Lebeau said:

Which function are you referring to?

I was looking at the wrong part of IdGlobal, i.e. line 2865 (in Delphi 11.3)

Share this post


Link to post
5 hours ago, RokWeb said:

I can't check because I have an iPhone with iOS 15.6 and updating to iOS 17 is scary since Delphi 11.3 doesn't fully support iOS 17

Ok

5 hours ago, RokWeb said:

for example, as far as I know, debugging doesn't work

FYI, the whole point of setting the IOHandler.DefStringEncoding property is so you don't need to keep specifying an encoding on every subsequent read/write string operation on the IOHandler, like you are doing.

 

That said, where is your call to Connect()? You don't need to call CreateIOHandler() if you set the DefStringEncoding after Connect() exits successfully.

 

In any case, the code shown is fine as far as encodings are concerned, so the error has to be caused by an underlying failure of the newer OS, or a bug in the RTL.

 

Either way, try using the charset name via  IndyTextEncoding('Windows-1251') or GetCharsetEncoding('Windows-1251') instead of using the codepage number via IndyTextEncoding(1251). If those still fail, then the latter one provides a GIdEncodingNeeded event so you can provide Indy with your own class that implements the IIdTextEncoding interface, so you can do your own encoding/decoding of the CP1251 data (which is fairly trivial using a simple lookup table via https://en.m.wikipedia.org/wiki/Windows-1251)

5 hours ago, RokWeb said:

 


constructor TClientThread.Create(AHost: String; APort: Integer);
begin
  inherited Create(True);
  FClient := TIdTCPClient.Create;
  FClient.ReadTimeout := 100;
  FClient.ConnectTimeout := 1000;
  FHost := AHost;
  FClient.Host := FHost;
  FClient.Port := APort;
  FClient.CreateIOHandler();
  FClient.IOHandler.DefStringEncoding := IndyTextEncoding(1251);
end;

 

And the Execute method of the data flow:


    try
      try
        while not Terminated do
        begin

          Sleep(1);

          FClient.IOHandler.CheckForDisconnect(true,true);

          if FClient.IOHandler.InputBufferIsEmpty then
          begin
            if not FClient.IOHandler.CheckForDataOnSource(10) then
            begin
                CSForData.Enter;
                try
                  if (Length(FSendData) > 0) then
                  FClient.IOHandler.Write(FSendData, IndyTextEncoding(1251));
                  FSendData := '';
                finally
                  CSForData.Leave;
                end;
                Continue;
            end;
          end;
            {$IFDEF IOS}
            FData := FData + FClient.IOHandler.InputBufferAsString(IndyTextEncoding(1251));
            {$ENDIF}

          if Length(FData) > 0 then
          begin
            Synchronize(nil,DoData);
            SetLength(FData, 0);
          end;

          CSForData.Enter;
          try
            if (Length(FSendData) > 0) then FClient.IOHandler.Write(FSendData, IndyTextEncoding(1251));
            FSendData := '';
          finally
            CSForData.Leave;
          end;

        end;

      except
        on E: Exception do
          DoError(E);
      end;
    finally
      FClient.Disconnect(False);
      if FClient.IOHandler <> nil then FClient.IOHandler.InputBuffer.Clear;
      Synchronize(DoDisconnected);
    end;

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×