类映射中用于简化SELECT查询的内存指针有效性问题
问题背景与疑问
我正在通过类映射并将相关信息存储到记录中来改进应用的SELECT功能,相关实现细节如下:
类属性示例
[Field('ID'), PK, AutoInc] property ID: Int64 read FID write FID; [Field('NOTA_ORIGINAL')] property NotaOriginal: string read FNotaOriginal write FNotaOriginal; [Field('NOTA_TROCA')] property NotaTroca: string read FNotaTroca write FNotaTroca;
映射函数代码
for lProperty in lType.GetProperties do begin if (lProperty.HasField<Field>) then begin lMappedField.Field := lProperty.SearchAttribute<Field>.FieldName; lMappedField.PK := False; if (lProperty.HasAttribute<PK>) then begin lMappedField.PK := True; FPKKey := lProperty.SearchAttribute<Field>.FieldName; FPropertyPK := lProperty; end; lMappedField.AutoInc := (lProperty.HasAttribute<AutoInc>); if lProperty is TRttiInstanceProperty then begin PropInstance := TRttiInstanceProperty(lProperty); MemAddress := nil; if PropInstance.IsReadable then begin // Check if lProperty is directly tied to a field if (IntPtr(PropInstance.PropInfo^.GetProc) and PROPSLOT_MASK) = PROPSLOT_FIELD then begin // Obtain the memory address of the directly associated field MemAddress := PByte(FObjeto) + (IntPtr(PropInstance.PropInfo^.GetProc) and (not PROPSLOT_MASK)); lMappedField.Mem := MemAddress; end; end; end; FMapping.Add(lProperty, lMappedField); end; end;
记录结构
TMappedField = record Field: string; PK: Boolean; AutoInc: Boolean; Mem: Pointer; end;
映射存储与关键变量
FMapping: TDictionary<TRttiProperty, TMappedField>; // 映射存储 FSQLTable: string; // 类属性 FPKKey: string; FPropertyPK: TRttiProperty;
SELECT查询相关代码
function TMCRTTILibORM.Load: TMCRTTILibORM; begin FSelectText.Clear; FSelectText.Add(' SELECT FIRST 1 ' + FPKKey + ' FROM ' + FSQLTable + ' '); Result := Self; end; function TMCRTTILibORM.Where(const pProperty: Pointer; const pValue: Variant): TMCRTTILibORM; var lPropertyField: TPair<TRttiProperty, TMappedField>; begin for lPropertyField in FMapping do begin if lPropertyField.Value.Mem = pProperty then begin // Add WHERE or AND to SQL query if FSelectText.Count > 1 then FSelectText.Add(' AND ' + lPropertyField.Value.Field + ' = ') else FSelectText.Add(' WHERE ' + lPropertyField.Value.Field + ' = '); // Handle different types (Integer, Float, String, Enumeration, etc.) // Example for tkInteger: FSelectText.Add(VarToStr(pValue)); Result := Self; Exit; end; end; end; function TMCRTTILibORM.Open: Boolean; var lQuery: TCustomQuery; begin lQuery := TCustomQuery.Create(nil); try lQuery.SQL := FSelectText; lQuery.Open; // Update object with primary key value if lQuery.RecordCount > 0 then begin FPropertyPK.SetValue(FObjeto, lQuery.FieldByName(FPKKey).AsInteger); // Example for tkInteger Result := LoadObject; // Populate the object end; finally lQuery.Free; end; end;
调用方式
Result := ItemObject.Mapping.Load .Where(@ItemObject.ID, pID) .Open;
核心疑问
资深开发者指出,记录中存储的Mem指针可能因Windows内存管理或重定位而失效,导致Where函数中的比较失败。我当前仅映射对象一次并按需获取属性,目前测试均成功,但如果该内存管理问题确实存在,此方案将不可靠。
具体疑问:
- 此场景下对
Mem指针的担忧是否成立?Windows内存管理是否真会改变指针? - 若担忧成立,有什么更好的方式维护该映射以支持SELECT查询?
解答
一、关于Mem指针的担忧是否成立?
这个担忧完全成立,核心原因有三点:
- 对象内存地址可能变动:当容器(比如
TObjectList)扩容、调用ReallocMem,或者Windows堆内存自动整理时,对象实例的内存地址会发生移动,你存储的Mem是基于原对象地址计算的偏移,此时会直接失效。 - 实例绑定问题:你当前的
Mem是绑定到单个FObjeto实例的,一旦换用其他对象实例,同属性的内存地址必然不同,Where方法的指针匹配会直接失败。 - 依赖内部实现细节:通过
PropInfo.GetProc的位掩码判断属性是否绑定字段,属于Delphi未公开的内部实现,不同版本的Delphi可能修改逻辑,存在兼容性风险。
二、替代方案
方案1:基于RTTI属性的直接匹配
利用你已经存储的TRttiProperty作为映射键,直接通过属性对象而非内存地址匹配,这是最稳定的方案:
修改Where方法,接收TRttiProperty作为参数:
function TMCRTTILibORM.Where(const AProperty: TRttiProperty; const AValue: Variant): TMCRTTILibORM; var LMappedField: TMappedField; begin if FMapping.TryGetValue(AProperty, LMappedField) then begin // 添加WHERE/AND子句,同时使用参数化查询避免注入 if FSelectText.Count > 1 then FSelectText.Add(' AND ' + LMappedField.Field + ' = :' + LMappedField.Field) else FSelectText.Add(' WHERE ' + LMappedField.Field + ' = :' + LMappedField.Field); // 绑定参数 FQuery.ParamByName(LMappedField.Field).Value := AValue; end; Result := Self; end;
调用时通过RTTI获取属性对象:
var LContext: TRttiContext; LType: TRttiType; LIDProp: TRttiProperty; begin LContext := TRttiContext.Create; try LType := LContext.GetType(ItemObject.ClassType); LIDProp := LType.GetProperty('ID'); Result := ItemObject.Mapping.Load .Where(LIDProp, pID) .Open; finally LContext.Free; end; end;
如果觉得每次获取RTTI属性麻烦,可以进一步封装,直接接收属性名称字符串:
function TMCRTTILibORM.Where(const APropName: string; const AValue: Variant): TMCRTTILibORM; var LProp: TRttiProperty; LMappedField: TMappedField; begin for LProp in FMapping.Keys do begin if LProp.Name = APropName then begin LMappedField := FMapping[LProp]; // 执行上述SQL拼接与参数绑定逻辑 Break; end; end; Result := Self; end; // 简化调用 Result := ItemObject.Mapping.Load .Where('ID', pID) .Open;
方案2:基于自定义属性元数据匹配
利用你已经定义的Field特性,直接存储属性与数据库字段的映射关系,完全脱离内存地址:
- 映射阶段确保每个属性的
Field特性已正确注册 Where方法通过属性的Field特性值直接关联数据库字段,或通过属性名称查找映射
额外优化建议
- 务必使用参数化查询,避免直接拼接
VarToStr(pValue)到SQL中,既防止SQL注入,又能自动处理不同数据类型的转换。 - 放弃依赖
PROPSLOT_MASK这类内部细节,改用Delphi公开的RTTI API(比如TRttiField)获取属性对应的字段信息,提升兼容性。
内容的提问来源于stack exchange,提问作者missingNO
相关产品推荐
相关产品推荐

