Страницы

Поиск по вопросам

Показаны сообщения с ярлыком delphi-xe. Показать все сообщения
Показаны сообщения с ярлыком delphi-xe. Показать все сообщения

пятница, 14 февраля 2020 г.

Сортировка по первой букве / цифре

#delphi #delphi_xe


Столкнулась с такой задачей. В текстовом файле имеются строки такого типа:

Беляев Антон Степанович 19.08.1986
Алексеев Денис Владимерович 20.06.1980
Еврошина Анна Петровна 30.07.1975
Belyaev Anton Stepanovich 08/19/1986
Alexeev Denis Vladimirovich 06.20.1980
Evroshina Anna Petrovna 07/30/1975
3.Belyaev Anton Stepanovich 08/19/1986
2.Alexeev Denis Vladimirovich 06.20.1980
1.Evroshina Anna Petrovna 07/30/1975


Вот вопрос: Как отсортировать список этих строк в алфавитном порядке.Что бы на выходе
получился результат (если есть номера в списке то сортировать просто по возрастанию).
То есть так:

1.Evroshina Anna Petrovna 07/30/1975
2.Alexeev Denis Vladimirovich 06.20.1980
3.Belyaev Anton Stepanovich 08/19/1986
Alexeev Denis Vladimirovich 06.20.1980
Belyaev Anton Stepanovich 08/19/1986
Evroshina Anna Petrovna 07/30/1975
Алексеев Денис Владимерович 20.06.1980
Беляев Антон Степанович 19.08.1986
Еврошина Анна Петровна 30.07.1975


Хотела бы добавить: Сложность в том что, в одном текстовом файле, фамилии как на
русском так и на английском + попадаются строки где идет нумерация фамилия имя отчество.
По этому я и не могу справится с этой задачей :(. Может есть какие функции или варианты
решение такой задачи или это не решаемая задача ?. Заранее благодарна за помощь...

Мои попытки сделать функцию сортировки по возрастанию:

var
 sl: TStringList;
 i: Integer;

function Compare(List: TStringList; Index1, Index2: Integer): Integer;
begin
 if List[Index1] > List[Index2] then
   Result := +1
 else if List[Index1] < List[Index2] then
   Result := 1
 else
   Result := 0;
end;

begin
 sl := TStringList.Create;
 sl.CustomSort(Compare);
 for i := 0 to sl.Count - 1 do
   WriteLn(sl[i]);
 sl.Free
end.

    


Ответы

Ответ 1



StrCmpLogicalW - функция сравнения строк, которая рассматривает цифры как числа, а не как строки. Пример использования: uses Winapi.ShLwApi, System.Generics.Defaults, System.Generics.Collections, System.SysUtils; var VItem: string; VItems: array [0..8] of string = ( 'Беляев Антон Степанович 19.08.1986', 'Алексеев Денис Владимерович 20.06.1980', 'Еврошина Анна Петровна 30.07.1975', 'Belyaev Anton Stepanovich 08/19/1986', 'Alexeev Denis Vladimirovich 06.20.1980', 'Evroshina Anna Petrovna 07/30/1975', '3.Belyaev Anton Stepanovich 08/19/1986', '2.Alexeev Denis Vladimirovich 06.20.1980', '1.Evroshina Anna Petrovna 07/30/1975' ); begin TArray.Sort(VItems, TDelegatedComparer.Construct( function(const Left, Right: string): Integer begin Result := StrCmpLogicalW(PChar(Left), PChar(Right)); end) ); for VItem in VItems do begin Writeln(VItem); end; Readln; end.

Ответ 2



Вот такой вариант решения проблемы. Что называется "в лоб". procedure TForm1.Button1Click(Sender: TObject); var List: TStringList; Begin List := TStringList.Create; try // Загружаем данные List.Add('Беляев Антон Степанович 19.08.1986'); List.Add('Алексеев Денис Владимерович 20.06.1980'); List.Add('Еврошина Анна Петровна 30.07.1975'); List.Add('Belyaev Anton Stepanovich 08/19/1986'); List.Add('Alexeev Denis Vladimirovich 06.20.1980'); List.Add('Evroshina Anna Petrovna 07/30/1975'); List.Add('3.Belyaev Anton Stepanovich 08/19/1986'); List.Add('2.Alexeev Denis Vladimirovich 06.20.1980'); List.Add('1.Evroshina Anna Petrovna 07/30/1975'); // Сортировка List.Sort; // Показываем результат for i:=0 to List.Count - 1 do ShowMessage(List.Strings[i]); finally List.Free; end; end; Уверен, что можно найти более элегантный способ решения, но если время дорого (а иначе не бывает), то я бы использовал такой подход.

воскресенье, 9 февраля 2020 г.

Перегрузка операторов перечисляемого типа в Delphi

#delphi #перегрузка_операторов #delphi_xe


Можно ли перегрузить оператор для перечисляемого типа?
т.е. имеем например:

Type
  TAngle = (a0, a90, a180, a270);

Var
  AngleA, AngleB, AngleC: TAngle;

AngleA := a90;
AngleB := a180;
AngleC := AngleA + AngleB; //a270
AngleC := a270 + AngleB;   //a90
AngleC := AngleB + a180;   //a0


По идее перегрузить бы оператор сложения, но это возможно только для записей, как
я понимаю? Может можно запись через Implicit неявно приводить к этому перечисляемому
типу? Но тогда это всё равно будет уже новый тип.

Вообще логика такая получается, пока сделал через функцию:

Result := TAngle((Ord(AngleA) + Ord(AngleB)) Mod 4)

    


Ответы

Ответ 1



Как уже сказано в ответе @zed перегрузка доступна только для записей. Поэтому, если хочется, вы можете добавить соответствующую структуру, и работать с ней. TAngleValue = (av0, av90, av180, av270); TAngle = record FValue : TAngleValue; function getAngle():integer; public class operator Add(a, b: TAngle ):TAngle; class operator Implicit(angle: TAngleValue):TAngle; property Angle: integer read getAngle; property Value: TAngleValue read FValue; end; перегрузив сложение и, для начала, неявное приведение class operator TAngle.Add(a, b: TAngle): TAngle; begin result.FValue := TAngleValue( (byte(a.FValue) + byte(b.FValue)) mod 4); end; class operator TAngle.Implicit(angle: TAngleValue): TAngle; begin result.FValue := angle; end; function TAngle.getAngle(): integer; begin result := byte(FValue)*90; end; в итоге само манипулироввание значениями получится таким, как вы хотели var a, b, c : TAngle; begin a := av90; b := av180; c := a + b + av90; writeln( a.Angle, ' ', b.Angle, ' ', c.Angle); readln; end.

Ответ 2



Нельзя такого сделать. В десктопных версиях Delphi перегрузка операторов возможна только у записей: Operator Overloading (Delphi). Для мобильных версий (с ARC) перегрузка доступна так же и для классов: Class Operators in Delphi

понедельник, 6 января 2020 г.

Как исправить код, чтобы не было 'String index to var param'?

#delphi #строки #delphi_xe


Function exchange(input: String): String;
Var
  I: Integer;
Begin
  Result:=input;
  I:=Length(Result);
  For I:=1 To I Do Begin
    If Result[I]='a' Then Result[I]:='b'; //[DCC Error] test.pas(13): E1047 Unsafe
code 'String index to var param'
    If Result[I]='c' Then Result[I]:='d'; //[DCC Error] test.pas(14): E1047 Unsafe
code 'String index to var param'
    If Result[I]='e' Then Result[I]:='f'; //[DCC Error] test.pas(15): E1047 Unsafe
code 'String index to var param'
  End;
End;


Именно изменить, что можно отключить предупреждения я знаю, мне интересно как же
пишется этот "надёжный" код и что именно ему тут не нравится?
    


Ответы

Ответ 1



Вот такой код будет корректным и безошибочным: Function exchange(input: String): String; Var I: Integer; sb: TStringBuilder; Begin sb := TStringBuilder.Create(input); try For I := 0 To sb.Length - 1 Do Begin if sb.Chars[I] = 'a' Then sb.Chars[I] := 'b'; if sb.Chars[I] = 'c' Then sb.Chars[I] := 'd'; if sb.Chars[I] = 'e' Then sb.Chars[I] := 'f'; End; Result := sb.ToString; finally sb.Free; end; End; А теперь объяснение: В современных версиях Делфы, при внедрении поддержки новых платформ (iOS, Android, x64 и т.п.) было принято решение уходить от строк индексируемых 1 .. N в пользу стандартных с индексацией 0 .. N-1. Данное изменение ломает практически весь существующий код. Поэтому его внедрение идет очень медленно и постепенно. В какой-то версии добавили предупреждения, в какой-то {$IFDEF ZEROBASEDSTRINGS}. Где-то эта директива включена по умолчанию, но в основном нет. О чем говорит данное предупреждение - о том, что обращаться к строкам по индексу небезопасно, т.к. индексация может быть заранее неизвестна (в зависимости от директивы и целевой платформы). Как быть? Самое простое решение, если вам нужна только Win32 - отключить предупреждения и спокойно жить дальше. Самое правильное, если вам нужны новые платформы - переписать код с использованием построителя строк (TStringBuilder), который сам разберется как именно строка индексируется, и предоставляет к ней единый интерфейс через свои методы. P.S. Как ни странно, For I:=1 To I Do является вполне корректным кодом, т.к. цикл сохраняет максимальное значение до начала итерирования.

Ответ 2



Как было сказано выше, варнинг появляется из-за того, что современные версии Delphi стали поддерживать различные платформы, на которых нумерация строк сделана с 0, а не с 1, как это было раньше на Win32. И теперь, чтобы код работал на всех платформах, каноничный способ итерироваться по строкам заключается в использовании функций Low и High: function Exchange(const AInput: string): string; var I: Integer; begin Result := AInput; for I := Low(Result) to High(Result) do begin if Result[I] = 'a' then Result[I] := 'b' else if Result[I] = 'c' then Result[I] := 'd' else if Result[I] = 'e' then Result[I] := 'f'; end; end; Однако, данный способ сработает только в Delphi XE3 и выше, а вот в XE2 и ниже, будут ошибки: E2198 Low cannot be applied to a long string E2198 High cannot be applied to a long string И если вдруг хочется поддерживать старые версии Delphi, то придётся написать свои обёртки над этими функциями и использовать их: function StrLow(const S: string): Integer; inline; begin Result := {$IFDEF XE3UP} Low(S) {$ELSE} 1 {$ENDIF}; end; function StrHigh(const S: string): Integer; inline; begin Result := {$IFDEF XE3UP} High(S) {$ELSE} Length(S) {$ENDIF}; end; Однако, и здесь есть подводный камень - если вы вдруг в какой-то функции захотите временно включить/выключить поддержку новых строк через директиву ZEROBASEDSTRINGS и вызвать одну из этих функций, то вы получите ошибку, т.к. они будут выдавать результат в не зависимости от этой настройки (будут использоваться глобальные настройки этой директивы). Поэтому, просто не стоит управлять руками этой директивой внутри своих функций/юнитов. Подробное обсуждение вопроса о написании обратно-совместимого кода см. тут: How to work with 0-based strings in a backwards compatible way since Delphi XE5?

среда, 1 января 2020 г.

Чем заменить Suspend и Resume при работе с потоками?

#delphi #многопоточность #delphi_xe


Есть такой метод в одном стареньком компоненте:

procedure TTangentThread.SetActive(Value: Boolean);
begin
 if Value <> FActive then
  begin
   FActive := Value;
   case FActive of
    False:
     begin
      FThread.Suspend;
      if (IsRuntime) and (Assigned(FOnSuspend)) then FOnSuspend(self);
     end;
    True:
     begin
      FThread.Resume;
      if (IsRuntime) and (Assigned(FOnResume)) then FOnResume(self);
     end;
   end;
  end;
end;


И он, в общем то, работает, но компилятор говорит, что Suspend и Resume устарели
и их лучше не использовать. Как это нынче правильно переписать, не используя "устаревших"
методов?
    


Ответы

Ответ 1



Suspend и Resume на самом деле выполняются операционной системой. Для Windows внутри этих методов вызываются SuspendThread и ResumeThread Функции "живее всех живых", но... использовать их действительно не стоит. Проблема в том, что они работают с потоком "здесь и сейчас". То есть - если команда Suspend застала поток посередине выполнения оператора (например - присвоения строки) то именно в этой точке поток заснет. Естественно, такая "произвольность" не способствует качественному выполнению приложения, особенно - если дополнительный и основной поток работают над разделяемыми (совместными) данными. Например, дополнительный поток начал изменять массив (строку, список и т.п.), в это время его "заснули", основной поток обнулил массив и разбудил дополнительный. В дополнительном потоке штатная проверка размерности уже пройдена, поэтому он не узнает о том, что того элемента, над которым поток пытается работать, уже нет. В результате - гарантированная порча памяти со всеми вытекающими последствиями. Именно поэтому нужно использовать объекты синхронизации: TCriticalSection, TMutex, TEvent, TSemaphore, TMultiReadExclusiveWriteSynchronizer, которыми ограждается доступ к разделяемым ресурсам и / или посылаются "правильные", в "нужные моменты времени" управляющие сигналы. Альтернатива (не кросс-платоформенная) - использовать механизм сообщений. В качестве примера: дополнительный поток по команде должен выполнить какую-то работу и ожидать новую команду: uses System.SyncObjs; type TmyThread = class(TThread) private FEvent: TEvent; // "синхронизатор" procedure DoWork; // в этом методе будет выполняться полезная работа protected procedure Execute; override; public constructor Create; destructor Destroy; override; procedure StartSingleWork; // этот метод вызывается "извне" end; { TmyThread } constructor TmyThread.Create; begin inherited Create(False); FEvent := TEvent.Create; // создаем сигнальное событие end; procedure TmyThread.Execute; begin while not Terminated do begin FEvent.WaitFor; // ожидаем, пока взведут событие FEvent.ResetEvent; // сбрасываем событие,чтобы // опять войти в ожидание на WaitFor if not Terminated then // и если поток не уничтожают DoWork; // делаем свое дело. end; end; procedure TmyThread.StartSingleWork; begin // кто-то извне хочет, чтобы поток выполнил свою работу FEvent.SetEvent; // выводим поток из спячки. // поток будет выведен из ожидания в WaitFor, // выполнит полезную работу и опять заснет. end; procedure TmyThread.DoWork; begin // здесь выполняется какая-то полезная работа // в контексте нашего дополнительного потока. end; destructor TmyThread.Destroy; begin Terminate; // начинаем уничтожение FEvent.SetEvent; // выводим поток из спячки while not Finished do // ждем, пока он завершится Sleep(0); FreeAndNil(FEvent); // и уничтожаем содержимое потока. inherited; end; В дополнение: на мой взгляд, лучшая из статей про многопоточность в Delphi (и не только). К сожалению - только в web.archive... Upd. Старый форум Винграда ожил, прямая ссылка на статью

Ответ 2



Эти методы не "устарели", дело здесь в другом. Насколько я знаю, они используются отладчиком для управления потоками во время отладки. И если эти методы используются в вашем приложении, можно нарваться на "непонятные глюки" во время отладки. Или вот что будет с вашим приложением, если вы приостановите поток в то время когда он занял какой-нибудь критически важный системный объект? По-хорошему, приостанавливать работу потока нельзя, можно "попросить" его остановиться в удобный для него момент. Для этого надо использовать объекты синхронизации, например TEvent. Поток в своем цикле проверяет состояние этого объекта синхронизации и либо ждет пока можно будет работать, либо работает. Если вам нужно только один раз запустить поток в нужный момент, то можно использовать метод Start. Если вам очень хочется избавиться от предупреждений компилятора, можно использовать свойство Suspended (Suspended := False; или Suspended := True;) вместо Suspend и Resume. Это уберет предупреждения компилятора, но не избавит от возможных проблем.

воскресенье, 15 декабря 2019 г.

Можно ли использовать это поле в IDE Delphi для своих целей?

#delphi #ide #delphi_xe




В инспекторе объектов имеется поле, похожее на многострочное, которое тупо копирует
название свойств при навигации, т.е. оно абсолютно бесполезно. Что ещё хуже, его даже
нельзя скрыть!

Хотелось бы в своём проекте показывать там краткое описание свойства, но я так и
не нашёл никакой информации, как можно изменять то, что там отображается. Возможно
ли это и если да, то как?
    


Ответы

Ответ 1



Обновление, про DescriptionPane: за работу с панелью DescriptionPane отвечает интерфейс IPropertyDescription, реализующий единственный метод: function GetDescription: string; Если включить в созданный редактор свойства реализацию этого интерфейса, то своё описание будет появляться в этой панели при выделении свойства. Пример, на основе того же VirtualTreeView: TCheckImageKindProperty = class(TEnumProperty,... IPropertyDescription) public ... function GetDescription: string; end; function TCheckImageKindProperty.GetDescription: string; begin Result:='TCheckImageKindProperty.GetDescription'; end; //==================== RegisterPropertyEditor(TypeInfo(TCheckImageKind), nil, '', TCheckImageKindProperty); Про панельку рядом с DescriptionPane - HotCommands: За работу с этой панелью (и за контекстное меню при клике по компоненту) отвечает TComponentEditor, а именно - его методы function GetVerb(Index: Integer): string; virtual; function GetVerbCount: Integer; virtual; procedure ExecuteVerb(Index: Integer); virtual; Перекрыв их в своем зарегистрированном редакторе для компонента можно не только показывать информацию, но и выполнять необходимые действия. Для примера я взял VirtualTreeView и изменил его редактор type TVirtualTreeEditor = class (TDefaultEditor) public procedure Edit; override; // этот метод был procedure ExecuteVerb(Index: Integer); override; // остальные - добавил function GetVerb(Index: Integer): string; override; function GetVerbCount: Integer; override; end; procedure TVirtualTreeEditor.ExecuteVerb(Index: Integer); begin inherited; ShowMessage('Verb '+IntToStr(Index)); end; function TVirtualTreeEditor.GetVerb(Index: Integer): string; begin if Index = 0 then Result:='VT action'; end; function TVirtualTreeEditor.GetVerbCount: Integer; begin Result:=1; end; И пересобрал дизайн-пакет компонента.

Ответ 2



Это не поле, а панель - Status Bar, на которой выводится системная информация. В данном случае информация о свойстве. Незнаю есть такая возможность в ранних версиях или нет, но в более поздних версиях можно данную панель отключить, для этого нужно: Кликнуть правой кнопкой мыши на Status Bar (панель) или в окне Object Inspecto'а Выбрать пункт Properties (в результате откроется окно настроек Status Bar'a) Снять галочку с пункта Show status bar'a и нажать кнопку Save

Ответ 3



"Left" - нет (это вообще число и это поле активно используется по назначению - указывает смещение относительно родителя). Для краткого описания можно использовать Hint. При этом это поле может быть видимо конечному пользователю! Так же есть целочисленное свойство Tag, которое можно использовать по своему усмотрению. Обзор свойств компонент: http://www.delphi.int.ru/articles/106/ - возможно тут найдется что-то для тебя полезное.

среда, 12 июня 2019 г.

Как изменить Content Type в THTTPClient Delphi

Пишу мобильное приложение под Android на Delphi 10. Моя программа отправляет post и get запросы использую компонент THTTPClient. Скрипт на сервере принимает запросы в формате json, по этому при отправке запроса нужно обязательно указать в заголовке "Content Type: application/json". Дело в том, что у меня не получается изменить Content Type. При отправке Get запроса он изменятся, а при отправке Post запроса - нет. Использую примерно такой код:
procedure ThomeForm.Button1Click(Sender: TObject); var data: TStringList; H: THTTPClient; begin data := TStringList.Create; data.Add('{"phone":"' + HomeForm.Phone.Text + '"}');
h := THTTPClient.Create;
h.ContentType := 'application/json'; h.Accept := 'application/json, text/plain, */*';
log.Text := h.Post('https://site.ru/api/phone', data).ContentAsString(); end;
Почему такое происходит и как это исправить?


Ответ

У HttpClient есть 4 перегруженных (overload) Post метода:
// Post a raw file without multipart info function Post( const AURL: string; const ASourceFile: string; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil ): IHTTPResponse; overload;
// Post TStrings values adding multipart info function Post( const AURL: string; const ASource: TStrings; const AResponseContent: TStream = nil; const AEncoding: TEncoding = nil; const AHeaders: TNetHeaders = nil ): IHTTPResponse; overload;
// Post a stream without multipart info function Post( const AURL: string; const ASource: TStream; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil ): IHTTPResponse; overload;
// Post a multipart form data object function Post( const AURL: string; const ASource: TMultipartFormData; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil ): IHTTPResponse; overload;
Первый и третий методы отправляют данные как есть, второй метод (которым вы воспользовались) отправляет данные как application/x-www-form-urlencoded, четвёртый метод отправляет данные как multipart/form-data
Таким образом, вам нужно использовать первый или третий метод, если вы хотите отправлять сырые данные с указанием своего Content-Type.
Пример, с использованием третьего метода:
var data: TStringStream; H: THTTPClient; begin data := TStringStream.Create; try data.WriteString('{"phone":"' + HomeForm.Phone.Text + '"}');
h := THTTPClient.Create; try h.ContentType := 'application/json'; h.Accept := 'application/json, text/plain, */*'; log.Text := h.Post('https://site.ru/api/phone', data).ContentAsString(); finally h.Free; end; finally data.Free; end; end;

вторник, 16 апреля 2019 г.

Перегрузка операторов перечисляемого типа в Delphi

Можно ли перегрузить оператор для перечисляемого типа? т.е. имеем например:
Type TAngle = (a0, a90, a180, a270);
Var AngleA, AngleB, AngleC: TAngle;
AngleA := a90; AngleB := a180; AngleC := AngleA + AngleB; //a270 AngleC := a270 + AngleB; //a90 AngleC := AngleB + a180; //a0
По идее перегрузить бы оператор сложения, но это возможно только для записей, как я понимаю? Может можно запись через Implicit неявно приводить к этому перечисляемому типу? Но тогда это всё равно будет уже новый тип.
Вообще логика такая получается, пока сделал через функцию:
Result := TAngle((Ord(AngleA) + Ord(AngleB)) Mod 4)


Ответ

Как уже сказано в ответе @zed перегрузка доступна только для записей. Поэтому, если хочется, вы можете добавить соответствующую структуру, и работать с ней.
TAngleValue = (av0, av90, av180, av270);
TAngle = record FValue : TAngleValue;
function getAngle():integer; public class operator Add(a, b: TAngle ):TAngle; class operator Implicit(angle: TAngleValue):TAngle;
property Angle: integer read getAngle; property Value: TAngleValue read FValue; end;
перегрузив сложение и, для начала, неявное приведение
class operator TAngle.Add(a, b: TAngle): TAngle; begin result.FValue := TAngleValue( (byte(a.FValue) + byte(b.FValue)) mod 4); end;
class operator TAngle.Implicit(angle: TAngleValue): TAngle; begin result.FValue := angle; end;
function TAngle.getAngle(): integer; begin result := byte(FValue)*90; end;
в итоге само манипулироввание значениями получится таким, как вы хотели
var a, b, c : TAngle; begin a := av90; b := av180; c := a + b + av90;
writeln( a.Angle, ' ', b.Angle, ' ', c.Angle); readln; end.

вторник, 12 февраля 2019 г.

Как исправить код, чтобы не было 'String index to var param'?

Function exchange(input: String): String; Var I: Integer; Begin Result:=input; I:=Length(Result); For I:=1 To I Do Begin If Result[I]='a' Then Result[I]:='b'; //[DCC Error] test.pas(13): E1047 Unsafe code 'String index to var param' If Result[I]='c' Then Result[I]:='d'; //[DCC Error] test.pas(14): E1047 Unsafe code 'String index to var param' If Result[I]='e' Then Result[I]:='f'; //[DCC Error] test.pas(15): E1047 Unsafe code 'String index to var param' End; End;
Именно изменить, что можно отключить предупреждения я знаю, мне интересно как же пишется этот "надёжный" код и что именно ему тут не нравится?


Ответ

Вот такой код будет корректным и безошибочным:
Function exchange(input: String): String; Var I: Integer; sb: TStringBuilder; Begin sb := TStringBuilder.Create(input); try For I := 0 To sb.Length - 1 Do Begin if sb.Chars[I] = 'a' Then sb.Chars[I] := 'b'; if sb.Chars[I] = 'c' Then sb.Chars[I] := 'd'; if sb.Chars[I] = 'e' Then sb.Chars[I] := 'f'; End; Result := sb.ToString; finally sb.Free; end; End;
А теперь объяснение:
В современных версиях Делфы, при внедрении поддержки новых платформ (iOS, Android, x64 и т.п.) было принято решение уходить от строк индексируемых 1 .. N в пользу стандартных с индексацией 0 .. N-1. Данное изменение ломает практически весь существующий код. Поэтому его внедрение идет очень медленно и постепенно. В какой-то версии добавили предупреждения, в какой-то {$IFDEF ZEROBASEDSTRINGS}. Где-то эта директива включена по умолчанию, но в основном нет.
О чем говорит данное предупреждение - о том, что обращаться к строкам по индексу небезопасно, т.к. индексация может быть заранее неизвестна (в зависимости от директивы и целевой платформы).
Как быть?
Самое простое решение, если вам нужна только Win32 - отключить предупреждения и спокойно жить дальше. Самое правильное, если вам нужны новые платформы - переписать код с использованием построителя строк (TStringBuilder), который сам разберется как именно строка индексируется, и предоставляет к ней единый интерфейс через свои методы.
P.S. Как ни странно, For I:=1 To I Do является вполне корректным кодом, т.к. цикл сохраняет максимальное значение до начала итерирования.

среда, 9 января 2019 г.

Чем заменить Suspend и Resume при работе с потоками?

Есть такой метод в одном стареньком компоненте:
procedure TTangentThread.SetActive(Value: Boolean); begin if Value <> FActive then begin FActive := Value; case FActive of False: begin FThread.Suspend; if (IsRuntime) and (Assigned(FOnSuspend)) then FOnSuspend(self); end; True: begin FThread.Resume; if (IsRuntime) and (Assigned(FOnResume)) then FOnResume(self); end; end; end; end;
И он, в общем то, работает, но компилятор говорит, что Suspend и Resume устарели и их лучше не использовать. Как это нынче правильно переписать, не используя "устаревших" методов?


Ответ

Suspend и Resume на самом деле выполняются операционной системой. Для Windows внутри этих методов вызываются SuspendThread и ResumeThread
Функции "живее всех живых", но... использовать их действительно не стоит. Проблема в том, что они работают с потоком "здесь и сейчас". То есть - если команда Suspend застала поток посередине выполнения оператора (например - присвоения строки) то именно в этой точке поток заснет.
Естественно, такая "произвольность" не способствует качественному выполнению приложения, особенно - если дополнительный и основной поток работают над разделяемыми (совместными) данными. Например, дополнительный поток начал изменять массив (строку, список и т.п.), в это время его "заснули", основной поток обнулил массив и разбудил дополнительный. В дополнительном потоке штатная проверка размерности уже пройдена, поэтому он не узнает о том, что того элемента, над которым поток пытается работать, уже нет. В результате - гарантированная порча памяти со всеми вытекающими последствиями.
Именно поэтому нужно использовать объекты синхронизации: TCriticalSection, TMutex, TEvent, TSemaphore, TMultiReadExclusiveWriteSynchronizer, которыми ограждается доступ к разделяемым ресурсам и / или посылаются "правильные", в "нужные моменты времени" управляющие сигналы. Альтернатива (не кросс-платоформенная) - использовать механизм сообщений
В качестве примера: дополнительный поток по команде должен выполнить какую-то работу и ожидать новую команду:

uses System.SyncObjs; type TmyThread = class(TThread) private FEvent: TEvent; // "синхронизатор"
procedure DoWork; // в этом методе будет выполняться полезная работа protected procedure Execute; override; public constructor Create; destructor Destroy; override;
procedure StartSingleWork; // этот метод вызывается "извне" end;
{ TmyThread }
constructor TmyThread.Create; begin inherited Create(False); FEvent := TEvent.Create; // создаем сигнальное событие end;
procedure TmyThread.Execute; begin while not Terminated do begin FEvent.WaitFor; // ожидаем, пока взведут событие FEvent.ResetEvent; // сбрасываем событие,чтобы // опять войти в ожидание на WaitFor
if not Terminated then // и если поток не уничтожают DoWork; // делаем свое дело. end; end;
procedure TmyThread.StartSingleWork; begin // кто-то извне хочет, чтобы поток выполнил свою работу FEvent.SetEvent; // выводим поток из спячки. // поток будет выведен из ожидания в WaitFor, // выполнит полезную работу и опять заснет. end;
procedure TmyThread.DoWork; begin // здесь выполняется какая-то полезная работа // в контексте нашего дополнительного потока. end;
destructor TmyThread.Destroy; begin Terminate; // начинаем уничтожение
FEvent.SetEvent; // выводим поток из спячки while not Finished do // ждем, пока он завершится Sleep(0); FreeAndNil(FEvent); // и уничтожаем содержимое потока.
inherited; end;
В дополнение: на мой взгляд, лучшая из статей про многопоточность в Delphi (и не только). К сожалению - только в web.archive...
Upd. Старый форум Винграда ожил, прямая ссылка на статью