Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Почему возникает ошибка AV при работе с TRttiProperty.GetValue в Delphi?

Delphi , Компоненты и Классы , RTTI

 

Введение

При работе с RTTI (Run-Time Type Information) в Delphi иногда возникают неожиданные ошибки доступа к памяти (Access Violation). В данной статье мы разберём конкретный случай с получением значения свойства CaretPosition компонента TMemo через RTTI, который приводит к AV ошибке, а также предложим возможные решения этой проблемы.

Описание проблемы

Рассмотрим следующий код, который пытается получить значение свойства CaretPosition через RTTI:

procedure TFormSimpleDraw2.Button2Click(Sender: TObject);
var
  Value: TValue;
  LType: TRttiType;
  RttiContext: TRttiContext;
  CaretPositionProp: TRttiProperty;
  CaretPosition: TCaretPosition;
begin
  RttiContext := TRttiContext.Create;
  LType := RttiContext.GetType(Memo1.ClassInfo);

  CaretPosition := Memo1.CaretPosition;

  CaretPositionProp := LType.GetProperty('CaretPosition');
  Value := CaretPositionProp.GetValue(Memo1); //AV происходит здесь
end;

При выполнении этого кода возникает Access Violation при вызове GetValue. Давайте разберёмся, почему это происходит.

Первое решение: проверка на nil

Первое, что стоит сделать - это добавить проверки на nil для объектов RTTI:

procedure TFormSimpleDraw2.Button2Click(Sender: TObject);
var
  Value: TValue;
  LType: TRttiType;
  RttiContext: TRttiContext;
  CaretPositionProp: TRttiProperty;
  CaretPosition: TCaretPosition;
begin
  RttiContext := TRttiContext.Create;

  LType := RttiContext.GetType(Memo1.ClassInfo);
  if LType = nil then Exit;

  CaretPosition := Memo1.CaretPosition;

  CaretPositionProp := LType.GetProperty('CaretPosition');
  if CaretPositionProp = nil then Exit;

  Value := CaretPositionProp.GetValue(Memo1);
  // ...
end;

Однако, как показала практика, проблема не в том, что свойства не найдены (CaretPositionProp не равен nil), а в том, как вызывается геттер этого свойства.

Причина проблемы: несоответствие соглашения о вызовах

Как выяснил Stefan Glienke, корень проблемы в том, что геттер свойства CaretPosition объявлен с соглашением о вызовах cdecl, в то время как RTTI система по умолчанию предполагает стандартное соглашение register.

// Пример объявления свойства с cdecl геттером
property CaretPosition: TCaretPosition read GetCaretPosition cdecl write SetCaretPosition;

RTTI система в Delphi не учитывает соглашение о вызовах при инвокации геттеров и сеттеров свойств, что приводит к ошибке доступа к памяти.

Техническое объяснение

При вызове TRttiProperty.GetValue происходит следующее:
1. Система RTTI находит указатель на метод-геттер
2. Вызывается TRttiInstanceProperty.DoGetValue, который использует System.Rtti.Invoke
3. Внутри Invoke предполагается соглашение register, в то время как реальный геттер использует cdecl

Это несоответствие приводит к неправильной работе стека вызовов и, как следствие, к AV.

Решение от Remy Lebeau

Remy Lebeau открыл баг-репорт в Embarcadero Quality Portal (RSS-3574) с описанием проблемы и предложил следующее временное решение:

  1. Использовать отладчик для изменения параметра CallingConvention в System.Rtti.Invoke с ccReg на ccCdecl
  2. После этого изменения AV исчезает

Это подтверждает, что проблема именно в несоответствии соглашений о вызовах.

Альтернативные решения

Пока проблема не исправлена в самом Delphi, можно предложить несколько обходных путей:

1. Прямой вызов геттера

Если вам нужно только значение CaretPosition, проще использовать прямое обращение:

CaretPosition := Memo1.CaretPosition;

2. Использование обёртки для вызова с правильным соглашением

Можно создать специальную функцию для вызова методов с соглашением cdecl:

function GetCDeclPropertyValue(Instance: TObject; Prop: TRttiProperty): TValue;
var
  Method: TRttiMethod;
  Addr: Pointer;
begin
  Method := (Prop as TRttiInstanceProperty).PropInfo.Getter;
  Addr := Method.CodeAddress;

  // Здесь должна быть реализация вызова с соглашением cdecl
  // Это требует низкоуровневой работы с указателями и стеком
  // и может быть нестабильным решением

  // В реальном коде это будет сложнее, чем показано здесь
  Result := TValue.From<TCaretPosition>(PCaretPosition(Addr)^);
end;

3. Использование интерфейсов вместо RTTI

Если возможно, создайте интерфейс с нужными свойствами и работайте через него:

type
  IMemoHelper = interface
    function GetCaretPosition: TCaretPosition;
    property CaretPosition: TCaretPosition read GetCaretPosition;
  end;

// Реализация через класс-помощник

4. Ожидание исправления от Embarcadero

Следите за обновлениями Delphi и исправлением бага RSS-3574.

Пример безопасного кода

Вот более безопасная версия исходного кода с обработкой возможных ошибок:

procedure TFormSimpleDraw2.Button2Click(Sender: TObject);
var
  Value: TValue;
  LType: TRttiType;
  RttiContext: TRttiContext;
  CaretPositionProp: TRttiProperty;
begin
  try
    RttiContext := TRttiContext.Create;
    try
      LType := RttiContext.GetType(Memo1.ClassInfo);
      if LType = nil then
      begin
        ShowMessage('Не удалось получить тип для TMemo');
        Exit;
      end;

      CaretPositionProp := LType.GetProperty('CaretPosition');
      if CaretPositionProp = nil then
      begin
        ShowMessage('Свойство CaretPosition не найдено');
        Exit;
      end;

      try
        Value := CaretPositionProp.GetValue(Memo1);
        // Работа с Value
      except
        on E: Exception do
          ShowMessage('Ошибка при получении значения: ' + E.Message);
      end;
    finally
      RttiContext.Free;
    end;
  except
    on E: Exception do
      ShowMessage('Общая ошибка: ' + E.Message);
  end;
end;

Заключение

Проблема с AV при вызове TRttiProperty.GetValue для свойства CaretPosition в TMemo связана с несоответствием соглашений о вызовах между RTTI системой и реальным геттером свойства. Это известная проблема (RSS-3574), и её окончательное решение должно прийти с обновлениями Delphi.

До тех пор рекомендуется:
1. Использовать прямое обращение к свойству вместо RTTI, если это возможно
2. Быть осторожным при работе с RTTI для свойств, которые могут использовать нестандартные соглашения о вызовах
3. Следить за обновлениями Delphi и исправлением данного бага

Работа с RTTI в Delphi - мощный инструмент, но, как видно из этого примера, требует понимания внутренних механизмов и осторожности при использовании.

Создано по материалам из источника по ссылке.

Ошибка AV при работе с TRttiProperty.GetValue в Delphi возникает из-за несоответствия соглашений о вызовах между RTTI системой (ожидающей register) и реальным геттером свойства (использующим cdecl), что приводит к неправильной работе стека.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: RTTI ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-08-27 09:37:19/0.0040721893310547/0