2015-03-17 14 views
16

Hàm sau lấy văn bản đã chọn trong điều khiển Richedit, ghi vào một hàm TMemoryStream bên trong hàm gọi lại và sau đó trả về dưới dạng chuỗi văn bản thuần túy mã rtf thô.Tại sao mã này không thành công khi khai báo TMemoryStream cục bộ nhưng hoạt động khi được khai báo trên toàn cầu?

var 
    MS: TMemoryStream; // declared globally and works. 

implementation 

function GetSelectedRTFCode(RichEdit: TRichedit): string; 

    function RichEditCallBack(dwCookie: Longint; pbBuff: PByte; 
    CB: Longint; var pCB: Pointer): Longint; stdcall; 
    begin 
    MS.WriteBuffer(pbBuff^, CB); 
    Result := CB; 
    end; 

var 
    EditStream: TEditStream; 
    SL: TStringList; 
begin 
    MS := TMemoryStream.Create; 
    try 
    EditStream.dwCookie  := SF_RTF or SFF_SELECTION; 
    EditStream.dwError  := 0; 
    EditStream.pfnCallback := @RichEditCallBack; 
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, DWord(@EditStream)); 
    MS.Seek(0, soBeginning); 

    SL := TStringList.Create; 
    try 
     SL.LoadFromStream(MS); 
     Result := SL.Text; 
    finally 
     SL.Free; 
    end; 
    finally 
    MS.Free; 
    end; 
end; 

Các công trình trên không như bất kỳ lỗi nào.

Tuy nhiên, tôi cố gắng tránh các biến được khai báo trên toàn cầu khi có thể và giữ chúng ở địa phương theo quy trình hoặc chức năng cần, nhưng vì lý do nào đó khai báo MS: TMemoryStream; bên trong chức năng GetSelectedRTFCode không thành công với lỗi Vi phạm hướng dẫn và truy cập.

Vì vậy, với ý nghĩ đó, và sự thay đổi duy nhất dưới đây được MS: TMemoryStream; tuyên bố tại địa phương thất bại:

function GetSelectedRTFCode(RichEdit: TRichedit): string; 
var 
    MS: TMemoryStream; // declare here instead of globally but fails. 

    function RichEditCallBack(dwCookie: Longint; pbBuff: PByte; 
    CB: Longint; var pCB: Pointer): Longint; stdcall; 
    begin 
    MS.WriteBuffer(pbBuff^, CB); 
    Result := CB; 
    end; 

var 
    EditStream: TEditStream; 
    SL: TStringList; 
begin 
    MS := TMemoryStream.Create; 
    try 
    EditStream.dwCookie  := SF_RTF or SFF_SELECTION; 
    EditStream.dwError  := 0; 
    EditStream.pfnCallback := @RichEditCallBack; 
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, DWord(@EditStream)); 
    MS.Seek(0, soBeginning); 

    SL := TStringList.Create; 
    try 
     SL.LoadFromStream(MS); 
     Result := SL.Text; 
    finally 
     SL.Free; 
    end; 
    finally 
    MS.Free; 
    end; 
end; 

Tại sao tuyên bố dòng biến bộ nhớ làm việc trên toàn cầu, nhưng thất bại khi tuyên bố tại địa phương?

Trả lời

20

Vấn đề là việc bạn sử dụng hàm lồng nhau làm gọi lại không đúng. Bởi một cơ hội thực hiện bằng cách sử dụng một hàm lồng nhau theo cách đó làm việc cho trình biên dịch 32 bit, miễn là hàm lồng nhau không tham chiếu đến bất kỳ biến cục bộ nào của các hàm xung quanh. Tuy nhiên, ngay khi hàm lồng nhau đề cập đến bất kỳ biến cục bộ nào như vậy, thì một tham số ẩn phụ phải được truyền để hàm lồng nhau có thể truy cập vào các khung chồng chức năng xung quanh. Và đối với trình biên dịch 64 bit, thông số bổ sung ẩn luôn được chuyển.

Bạn sẽ tìm thấy rất nhiều ví dụ trên web nơi mọi người chứng minh truyền hàm lồng nhau dưới dạng gọi lại. Nhưng tất cả các ví dụ này vi phạm các quy tắc của documented của ngôn ngữ:

Không thể sử dụng các thủ tục và chức năng lồng nhau (thường trình theo các thói quen khác).

Điều bạn phải làm là ngừng sử dụng chức năng lồng nhau cho cuộc gọi lại. Bạn cần khai báo hàm gọi lại là có phạm vi toàn cục. Truyền luồng bộ nhớ qua thành viên dwCookie của cấu trúc EDITSTREAM.

// This compiles now, but the callback implementation is wrong, see below 

function RichEditCallBack(dwCookie: DWORD_PTR; pbBuff: PByte; 
    CB: Longint; var pCB: Longint): Longint; stdcall; 
var 
    MS: TMemoryStream; 
begin 
    MS := TMemoryStream(dwCookie); 
    MS.WriteBuffer(pbBuff^, CB); 
    Result := CB; 
end; 

function GetSelectedRTFCode(RichEdit: TRichedit): string; 
var 
    MS: TMemoryStream; 
    EditStream: TEditStream; 
    SL: TStringList; 
begin 
    MS := TMemoryStream.Create; 
    try 
    EditStream.dwCookie  := DWORD_PTR(MS); 
    EditStream.dwError  := 0; 
    EditStream.pfnCallback := RichEditCallBack; 
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, LPARAM(@EditStream)); 
    MS.Seek(0, soBeginning); 

    SL := TStringList.Create; 
    try 
     SL.LoadFromStream(MS); 
     Result := SL.Text; 
    finally 
     SL.Free; 
    end; 
    finally 
    MS.Free; 
    end; 
end; 

Lưu ý rằng tôi chưa sử dụng toán tử @ để nhận địa chỉ của hàm gọi lại. Sử dụng toán tử @ trên một hàm sẽ dẫn đến việc loại bỏ kiểu kiểm tra. Nếu bạn không sử dụng toán tử @, thì trình biên dịch sẽ có thể cho bạn biết những sai lầm của bạn.

Trình biên dịch sẽ nói:

 
[dcc32 Error] E2094 Local procedure/function 'RichEditCallBack' assigned to 
procedure variable 

Và cũng lưu ý rằng mã của bạn tuyên bố kiểu của tham số chính thức không chính xác. Đó là tham số tham chiếu của loại Longint. Một lần nữa, trình biên dịch có thể báo cáo điều này, và không báo cáo điều này, trừ khi bạn đã sử dụng @ để có được địa chỉ hàm.

Lỗi thứ hai này dẫn đến việc triển khai gọi lại. Nó không đúng. Giá trị trả lại cho biết thành công.Giá trị bằng 0 được sử dụng để biểu thị thành công, bất kỳ giá trị nào khác đều cho biết lỗi. Số byte được ghi phải được trả về thông qua tham số cuối cùng. Gọi lại của bạn sẽ trông giống như sau:

function RichEditCallBack(dwCookie: DWORD_PTR; pbBuff: PByte; 
    CB: Longint; var CBWritten: Longint): Longint; stdcall; 
var 
    MS: TMemoryStream; 
begin 
    MS := TMemoryStream(dwCookie); 
    CBWritten := MS.Write(pbBuff^, CB); 
    Result := IfThen(CB = CBWritten, 0, 1); 
end; 
+0

Chỉ là System.Math cần thiết cho IfThen. Tôi đã không đề cập đến nó bởi vì tôi nghi ngờ bạn sẽ biết nơi để tìm thấy nó. Có thể được thực hiện với một câu lệnh if. –

+0

Bạn cũng có thể ['thêm một số đường'] (http://pastebin.com/udGkgwUz). Btw. có vẻ như EMBT đang tiến gần hơn đến nguyên mẫu chính xác của cuộc gọi lại đó (chưa chính xác, nhưng gần hơn). Nó thay đổi kể từ Delphi XE3. Có lẽ trong XE8 sẽ được chính xác cuối cùng. – TLama

+0

@TLama Ah, niềm vui của các nguyên mẫu Win32 được dịch sai. Trong trường hợp này, nó không quan trọng lắm bởi vì nó là 0, hay không 0. –

Các vấn đề liên quan