2012-06-11 31 views
6

Tôi nhận được một lỗi truy cập Vi phạm bất ngờ trong đoạn mã sau:Strange AV khi lưu trữ một giao diện tài liệu tham khảo Delphi

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var 
    p: ^ITest; 

begin 
    GetMem(p, SizeOf(ITest)); 
    p^ := TTest.Create; // AV here 
    try 
    finally 
    p^ := nil; 
    FreeMem(p); 
    end; 
end. 

Tôi biết rằng giao diện nên được sử dụng khác nhau. Tuy nhiên tôi đang làm việc trên một codebase kế thừa sử dụng cách tiếp cận này. Và tôi đã rất ngạc nhiên khi thấy rằng nó không đủ để dự trữ SizeOf (ITest) bộ nhớ để đặt một ITest ở đó.

Bây giờ thú vị nếu tôi thay đổi dòng đầu tiên để

GetMem(p, 21); 

hơn AV đã biến mất. (20 byte trở xuống không thành công). Giải thích cho điều này là gì?

(Tôi đang sử dụng Delphi XE2 Cập nhật 4 + HotFix)

Xin đừng nhận xét về cách khủng khiếp mã là hoặc đề nghị như thế nào điều này có thể được mã hóa đúng cách. Thay vào đó, hãy trả lời tại sao cần phải đặt trước 21 byte thay vì SizeOf (ITest) = 4?

+0

AV = vi phạm truy cập? – kol

+0

Mã của bạn trông thật kỳ lạ. Tại sao bạn cần "^ ITest" nhất và cặp GetMem/FreeMem? TTest là hậu duệ của TInterfacedObject, vì vậy p nên đơn giản là một ITest. Nó được tính tham chiếu, vì vậy nó sẽ bị phá hủy tự động khi nó nằm ngoài phạm vi. Không cần sử dụng GetMem/FreeMem. – kol

+1

Đây là cách hoàn toàn không chính xác để làm việc với giao diện. Bạn có thể giải thích những gì bạn đang hy vọng để thực hiện, để có thể ai đó có thể chỉ cho bạn một hướng tốt hơn? –

Trả lời

26

gì bạn đã viết một cách hiệu quả được thực hiện logic sau đằng sau hậu trường:

var 
    p: ^ITest; 
begin 
    GetMem(p, SizeOf(ITest)); 
    if p^ <> nil then p^._Release; // <-- AV here 
    PInteger(p)^ := ITest(TTest.Create); 
    p^._AddRef; 
    ... 
    if p^ <> nil then p^._Release; 
    PInteger(p)^ := 0; 
    FreeMem(p); 
end; 

GetMem() không được bảo đảm để không ra gì nó phân bổ. Khi bạn gán thể hiện đối tượng mới cho giao diện có thể biến đổi, nếu byte không phải là 0, RTL sẽ nghĩ rằng đã có một tham chiếu giao diện hiện có và sẽ cố gắng gọi phương thức _Release() của nó, gây ra AV vì nó không được hỗ trợ bởi ví dụ đối tượng. Bạn cần không ra các byte được phân bổ trước, sau đó các RTL sẽ thấy một tham chiếu giao diện nil và không cố gắng để gọi phương thức _Release() của nó nữa:

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var    
    p: ^ITest;    

begin    
    GetMem(p, SizeOf(ITest));    
    try 
    FillChar(p^, SizeOf(ITest), #0); // <-- add this! 
    p^ := TTest.Create; // <-- no more AV 
    try 
     ... 
    finally 
     p^ := nil; 
    end; 
    finally 
    FreeMem(p); 
    end; 
end. 
+0

Cảm ơn Remy, giải thích rõ ràng –

+6

Hoặc sử dụng AllocMem thay vì GetMem + FreeMem –

+0

Hiệu chỉnh: Ý tôi là GetMem + FillChar. FreeMem ist vẫn cần thiết cho AllocMem. –