2013-11-15 51 views
6

我們可以有關聯的對象到的TStringList一起添加字符串:如何區分TStringList中的指針和TObject項?

list: TStringList; 
obj: MyObject; 

obj := MyObject.Create(); 
list.AddObject("real object", obj); 

此外,它可以非常方便簡單的字符串的指針,也就是一個整數值連接,像這樣:

list.AddObject("just an index", Pointer(7)); 

如果我以後訪問這個列表中的一個對象如何知道它是一個MyObject還是一個指針?我想是這樣的:

for i := 0 to list.Count-1 do 
    if list.Objects[i] is MyObject then 
    begin 
    // ... 
    // Do something with list.Objects[i] 
    // ... 
    end; 

但是這顯然會導致訪問衝突,如果list.Objects [I]只是一個指針。 在此先感謝!

+7

最好的辦法是不要混用兩種。如果你想存儲的對象,存儲對象,然後你可以和'nil'比較或者使用'Assigned'。如果你想存儲整數,存儲整數(這是'指針(7)'所做的 - 它不存儲*指針*,它存儲整數類型轉換爲一個指針,通過編譯器抱怨) –

+1

一般而言,你可以假設低於大約$ FFFF的地址是假指針 - 不是對象,你沒有對該內存範圍的讀訪問權限。 t找到文檔來支持它。 –

+2

這個問題背叛了好奇心(好)或者魯莽(壞)。避免在實際代碼中使用不安全或破碎的想法,否則會發生不好的壞事。如果這是好奇心,正如我在這裏假設的那樣,那麼可以考慮通過這個問題來思考。爲什麼不堅持Tobbs併成爲數據持有者? Tmyintegerdata ... –

回答

7

如果要安全地將整數和對象存儲到一個字符串列表中,請定義一個變體容器類來存放整數或對象。

下面是一個粗略概括的類,包括一個測試項目。

unit VariantContainer; 

interface 

uses Variants,SysUtils; 

Type 
    TVariantContainer = class 
    private 
     FVariant : Variant; 
    public 
     constructor Create(aValue: Integer); overload; 
     constructor Create(aValue: TObject); overload; 
     function IsInteger: Boolean; 
     function IsObject: Boolean; 
     function AsObject: TObject; 
     function AsInteger: Integer; 
    end; 

implementation 

function TVariantContainer.AsInteger: Integer; 
begin 
    if not IsInteger then 
    raise Exception.Create('Variant is not Integer'); 
    Result := FVariant;  
end; 

function TVariantContainer.AsObject: TObject; 
begin 
    if not IsObject then 
    raise Exception.Create('Variant is not TObject'); 
    Result := TVarData(FVariant).VPointer; 
end; 

function TVariantContainer.IsInteger: Boolean; 
begin 
    Result := VarIsType(FVariant, varInteger); 
end; 

function TVariantContainer.IsObject: Boolean; 
begin 
    Result := VarIsType(FVariant, varByRef); 
end; 

constructor TVariantContainer.Create(aValue: Integer); 
begin 
    Inherited Create; 
    FVariant := aValue; 
end; 

constructor TVariantContainer.Create(aValue: TObject); 
begin 
    Inherited Create; 
    TVarData(FVariant).VType:= VarByRef; 
    TVarData(FVariant).VPointer:= aValue; 
end; 

end. 

program ProjectTestVariantContainer; 

{$APPTYPE CONSOLE} 
uses 
    Variants,SysUtils,Classes,VariantContainer; 

Type 
    TMyObj = class 
    s:String; 
    end; 

var 
    sList: TStringList; 
    o: TMyObj; 
    i: Integer; 
begin 
    o := TMyObj.Create; 
    o.s := 'Hello'; 
    sList := TStringList.Create; 
    sList.OwnsObjects := True; // List owns container objects 
    sList.AddObject('AnInteger',TVariantContainer.Create(3)); 
    sList.AddObject('AnObject',TVariantContainer.Create(o)); 
    for i := 0 to sList.Count-1 do 
    begin 
    if Assigned(sList.Objects[i]) then 
    begin 
     if TVariantContainer(sList.Objects[i]).IsInteger then 
     WriteLn(TVariantContainer(sList.Objects[i]).AsInteger) 
     else 
     if TVariantContainer(sList.Objects[i]).IsObject then 
     WriteLn(TMyObj(TVariantContainer(sList.Objects[i]).AsObject).s); 
    end; 
    end; 
    ReadLn; 
    o.Free; 
    sList.Free; 
end. 
+1

不要忘記釋放'TVariantContainer'對象,然後釋放'TStringList'(除非將其'OwnsObjects'屬性設置爲True-XE2 +),否則將泄漏內存。 –

+0

是的,這種重構是可行的,但我想知道是否有一個很好的簡短的方法來檢查而不需要這種改變。感謝您的建議。 – grafd

+0

+1,但有沒有一個原因,你實際上使用'Variant'作爲後臺字段而不是'Integer'或'Pointer'? –

5

這是完全有可能增加一個整數至極恰好指向 的對象。同樣,完全有可能在您的列表中有一個指向 對象的指針,該對象已經被釋放。

底線,你就可以開始在內存中四處尋找你想要的,沒有防彈方式知道,如果你的StringList包含整數或指針。

由於您不應該混合不同的類型,所以也不需要需要知道。更好的方法是創建兩個包含一個Stringlist的類,並使外部類類型安全地處理。您的問題然後成爲一個沒有問題。

假設你的德爾福版本不支持泛型

TStringIntegerMap = class 
    private FStringIntegerList: TStringList; 
    public 
    procedure Add(const Key: string; Value: Integer); 
    ... // Add the other required equivalent TStringlist methods 
    end; 

    TStringObjectMap = class 
    private FStringObjectList: TStringList; 
    public 
    procedure Add(const Key: string; Value: TObject); 
    ... // Add the other required equivalent TStringlist methods 
    end; 

請注意,這只是給你一個如何實現這樣的類的要點。

+0

如果我可以很容易地將兩個字符串列表分開,那麼這將是一種方法。謝謝。 – grafd

3

TObject實際上是一個指針。因此,考慮到後者是前者,根本無法區分指針和TObject。

如果您知道有關某個對象的信息,並且您需要稍後獲取該知識,請不要丟棄該知識。如果您以後需要了解某些內容,請記住它。

0

作爲@DavidHeffernan正確地指出,類類型是指針,所以它們是語義上等同,並且沒有辦法區分,而不必存儲某種類型的指示。

但是,如果您打算詢問「如何找出給定的任意指針是否指向對象實例?有針對的解決方案:

/// <summary> 
/// Verifies that the argument points to valid object instance. 
/// </summary> 
/// <exception cref="EAccessViolation"> 
/// If segmentation fault occurs while reading VMT and/or its field from the 
/// specified memory address. 
/// </exception> 
/// <remarks> 
/// Delphi only, incompatible with FPC. 
/// </remarks> 
/// <example> 
/// <code> 
/// procedure TForm1.FormCreate(Sender: TObject); 
/// begin 
/// ShowMessage(BoolToStr(IsInstance(Self), True)); 
/// end; 
/// </code> 
/// </example> 
function IsInstance(Data: Pointer): Boolean; 
var 
    VMT: Pointer; 
begin 
    VMT := PPointer(Data)^; 
    Result := PPointer(PByte(VMT) + vmtSelfPtr)^ = VMT; 
end; 

我已爲整個在線文檔,所以我覺得更多的評論是不必要的,但我想回顧一下故意無效指針就像你的榜樣的Pointer(7)無疑令訪問衝突錯誤。因此,如果高Word S上的指針爲零(就在同樣的邏輯Windows.IS_INTRESOURCE宏,您可以進行初步檢查:

function Is_IntResource(lpszType: PChar): BOOL; 
begin 
    Result := ULONG_PTR(lpszType) shr 16 = 0; 
end;