Khám Phá Chi Tiết Chức Năng TValue Trong Môi Trường Delphi

1. Cấu Trúc Dữ Liệu Của TValue

Lớp TValue được khai báo trong đơn vị hệ thống System.Rtti.pas. Đây là công cụ chính để thao tác với dữ liệu phản chiếu (RTTI) tại thời gian chạy.

Có ba phương thức cốt lõi cần lưu ý:

  • Make(...): Thực hiện đóng gói bất kỳ loại dữ liệu nào vào một phiên bản TValue.
  • ExtractRawData(...)ExtractRawDataNoCopy(...): Dùng để giải nén dữ liệu từ TValue ra kiểu mục tiêu. Sự khác biệt nằm ở việc quản lý bộ nhớ: ExtractRawDataNoCopy có thể trả về con trỏ đến dữ liệu trên Heap, trong khi ExtractRawData đảm bảo an toàn hơn bằng cách sao chép dữ liệu.
  • GetReferenceToRawData: Cung cấp trực tiếp con trỏ trỏ đến vùng nhớ chứa giá trị thực tế.

2. Chuyển Đổi Kiểu Dữ Liệu Sang TValue

Dưới đây là ví dụ minh họa việc tạo đối tượng TValue từ các kiểu nguyên thủy và cấu trúc (Record).

program DemoChuyenDoiCoBan;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;

var
  bienSoLe : Integer;
  tGiaTriSo : TValue;
  
  toaDo : TRect;
  tGiaTriKhoi : TValue;

begin
  bienSoLe := 1234;
  // Sử dụng Make để chuyển đổi kiểu Integer
  TValue.Make(@bienSoLe, TypeInfo(Integer), tGiaTriSo); 
  WriteLn('Giá trị số:', tGiaTriSo.ToString);
  
  toaDo.Left := 10;
  toaDo.Right := 20;
  // Đóng gói cấu trúc TRect vào TValue
  TValue.Make(@toaDo, TypeInfo(TRect), tGiaTriKhoi);
  WriteLn('Thông tin khối:', tGiaTriKhoi.ToString);
  
  Readln;
end.

3. Truy Xuất Lại Kiểu Dữ Liệu Từ TValue

Khi tiến hành phản trình tự hóa (deserialization), nếu xác định được đích đến, ta có thể khởi tạo một mẫu rỗng cho TValue như sau:

TValue.Make(nil, TypeInfo(TargetType), OutputValue);

Sau đó dùng ExtractRawData để gán ngược lại dữ liệu về biến thường:

program DemocGiaiMa;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;

var
  nguonDuLieu : TRect;
  kqTrichXuat : TRect;
  doiTuong : TValue;

begin
  nguonDuLieu.Left := 15;
  nguonDuLieu.Right := 25;
  
  // Bước 1: Biến thường thành TValue
  TValue.Make(@nguonDuLieu, TypeInfo(TRect), doiTuong);
  
  // Bước 2: TValue quay lại biến thường
  doiTuong.ExtractRawData(@kqTrichXuat);
  
  WriteLn('Left:', kqTrichXuat.Left);
  WriteLn('Right:', kqTrichXuat.Right);

  Readln;
end.

4. Thao Tác Thành Viên Qua Pointer

Với một đối tượng đã được bọc bởi TValue, ta có thể dùng GetReferenceToRawData để lấy địa chỉ tham chiếu. Kết hợp với TRttiContext, ta có thể thay đổi giá trị các trường (field) mà không cần biết tên biến cụ thể tại thời điểm biên dịch.

program DemoCanhCapThanhVien;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;

var
  cauTrucMau : TRect;
  giaTriChung : TValue;
  moTaLoai : TRttiContext;

begin
  moTaLoai := TRttiContext.Create;
  try
    // Tạo TValue rỗng tương ứng với TRect
    TValue.Make(nil, TypeInfo(TRect), giaTriChung);
    
    // Gán giá trị trực tiếp vào vùng nhớ thông qua RTTI
    moTaLoai.GetType(TypeInfo(TRect)).GetField('Left').SetValue(giaTriChung.GetReferenceToRawData, 100);
    moTaLoai.GetType(TypeInfo(TRect)).GetField('Right').SetValue(giaTriChung.GetReferenceToRawData, 200);
    
    // Xuất kết quả để kiểm chứng
    giaTriChung.ExtractRawData(@cauTrucMau);
    WriteLn(cauTrucMau.Left);
    WriteLn(cauTrucMau.Right);
  finally
    moTaLoai.Free;
  end;
  Readln;
end.

5. Các Hàm Tiện Ích Generic

Định ngôn ngữ Delphi cung cấp các hàm mở rộng giúp quá trình chuyển đổi trở nên ngắn gọn hơn so với việc gọi thủ tục Make thủ công:

  • From<T>: Tự động suy luận và đóng gói dữ liệu.
  • AsType<T>: Ép kiểu ngược lại.
  • IsType<T> / TryAsType<T>: Kiểm tra tính tương thích kiểu trước khi ép buộc.
program DemoGenericHuuIch;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;

var
  diaChi : TRect;
  ketQua : TRect;
  baoVon : TValue;
begin
  diaChi.Left := 5;
  diaChi.Right := 15;

  // Cách viết ngắn gọn hơn nhiều
  baoVon := TValue.From<TRect>(diaChi);

  WriteLn('Kiểu khớp?', baoVon.IsType<TRect>);

  ketQua := baoVon.AsType<TRect>;

  WriteLn(ketQua.Left);
  WriteLn(ketQua.Right);
  Readln;
end.

6. Xử Lý Mảng Động

Nếu đối tượng TValue được khởi tạo từ một mảng, ta cần sử dụng nhóm phương thức chuyên biệt để duyệt hoặc chỉnh sửa phần tử:

  • GetArrayLength: Lấy độ dài mảng.
  • GetArrayElement(Index): Lấy giá trị tại chỉ số.
  • SetArrayElement(Index, Value): Ghi đè giá trị.

Bạn có thể định nghĩa mảng động thông qua alias:

type
  TMangSo = array of Integer;
// Hoặc sử dụng generice sẵn có trong System.pas
// IntArr : TArray<Integer>;

7. Lưu Ý Về Việc Chuyển Đổi Variant

Đây là khu vực dễ gây nhầm lẫn. Hàm TValue.FromVariant thực chất sẽ giải mã nội dung bên trong Variant chứ không giữ nguyên định dạng Variant.

program DemoFromVariant;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo, Rtti;

var
  bienLinhDong : Variant;
  doiTuongKyHuu : TValue;
begin
  bienLinhDong := 'Delphi Text';
  doiTuongKyHuu := TValue.FromVariant(bienLinhDong);
  // Kết quả Kind sẽ là TkString, không phải TkVariant
  WriteLn('Loại:', GetEnumName(TypeInfo(TTypeKind), Ord(doiTuongKyHuu.Kind)));
  WriteLn('Nội dung:', doiTuongKyHuu.ToString);

  Readln;
end.

Nếu bạn muốn giữ nguyên cấu trúc Variant bên trong TValue, hãy sử dụng cú pháp Generic:TValue.From<Variant>:

program DemoGiurnamVariant;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo, Rtti;

var
  bienLinhDong : Variant;
  doiTuongKyHuu : TValue;
begin
  bienLinhDong := 'Hello World';
  // Giữ nguyên kiểu Variant
  doiTuongKyHuu := TValue.From<Variant>(bienLinhDong);
  WriteLn('Loại:', GetEnumName(TypeInfo(TTypeKind), Ord(doiTuongKyHuu.Kind)));
  WriteLn('Nội dung:', doiTuongKyHuu.AsType<Variant>);

  Readln;
end.

Thẻ: Delphi Object-Pascal RTTI TValue System-Rtti

Đăng vào ngày 23 tháng 6 lúc 14:04