2015-03-17 10 views
16

Następująca funkcja pobiera zaznaczony tekst w formancie Richedit, zapisuje do TMemoryStream wewnątrz funkcji wywołania zwrotnego, a następnie zwraca jako zwykły tekst ciąg nieprzetworzonego kodu rtf.Dlaczego ten kod kończy się niepowodzeniem podczas deklarowania TMemoryStream lokalnie, ale działa po deklaracji globalnej?

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; 

Powyższe działa zgodnie z oczekiwaniami, bez żadnych błędów.

staram Jednak aby uniknąć globalnie deklarowanych zmiennych, jeśli to możliwe i przechowywać je lokalnie w procedurze lub funkcji, która tego potrzebuje, ale z jakiegoś powodu uznającej MS: TMemoryStream; wewnątrz funkcji GetSelectedRTFCode nie z Priviliged Instrukcji i błędów naruszenie zasad dostępu.

więc mając to na uwadze, a jedyną zmianą była poniżej MS: TMemoryStream; zadeklarowana lokalnie zawiedzie:

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; 

Dlaczego deklarowania pamięci stream zmienną pracy na całym świecie, ale nie gdy zadeklarowana lokalnie?

Odpowiedz

20

Problem polega na tym, że korzystanie z funkcji zagnieżdżonej jako oddzwaniania jest nieprawidłowe. Przez przypadek implementacji za pomocą funkcji zagnieżdżonej w ten sposób działa 32-bitowy kompilator, o ile funkcja zagnieżdżona nie odwołuje się do żadnych zmiennych lokalnych otaczających funkcji.

Jednak, gdy funkcja zagnieżdżona odnosi się do takich zmiennych lokalnych, należy przekazać dodatkowy ukryty parametr, aby zagnieżdżona funkcja mogła uzyskać dostęp do ramek stosu funkcji otaczających. W przypadku kompilatora 64-bitowego zawsze przekazywany jest ukryty dodatkowy parametr.

Znajdziesz wiele przykładów w Internecie, gdzie ludzie demonstrują przekazywanie funkcji zagnieżdżonych jako wywołania zwrotne. Ale wszystkie te przykłady łamania documented zasady języków:

zagnieżdżonych procedur i funkcji (procedury zadeklarowane w ramach innych procedur) nie może być stosowany jako wartości procesowych, ani też predefiniowane procedury i funkcje.

Należy zaprzestać korzystania z funkcji zagnieżdżonych dla wywołań zwrotnych. Musisz zadeklarować, że funkcja zwrotna ma zasięg globalny. Przepuścić strumień pamięci przez element dwCookie struktury 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; 

Uwaga w szczególności, że nie zostały wykorzystane operatora @ aby uzyskać adres funkcja zwrotna za. Użycie operatora @ w funkcji powoduje zahamowanie sprawdzania typu. Gdybyś nie użył operatora @, kompilator byłby w stanie powiedzieć ci swoje błędy.

Kompilator powiedziałby:

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

i zanotować również, że kod deklaruje typ końcowego parametru niepoprawnie. Jest to parametr referencyjny typu Longint. Ponownie, kompilator może to zgłosić i zgłosi to, chyba że użyłeś @ do uzyskania adresu funkcji.

Ten drugi błąd prowadzi do realizacji wywołania zwrotnego. Jest niepoprawny. Wartość zwracana wskazuje na sukces.Wartość zero jest używana do wskazania sukcesu, każda inna wartość wskazuje na niepowodzenie. Liczba zapisanych bajtów musi zostać zwrócona za pomocą końcowego parametru. Twój callback powinien wyglądać tak:

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

To tylko System.Math potrzebne dla If Then. Nie wspominałem o tym, bo podejrzewałam, że wiesz, gdzie go znaleźć. Można to zrobić również za pomocą instrukcji if. –

+0

Możesz również ["dodać trochę cukru"] (http://pastebin.com/udGkgwUz). Btw. wydaje się, że EMBT zbliża się do poprawnego prototypu tego wywołania zwrotnego (jeszcze nie poprawne, ale bliżej). Zmienił się od Delphi XE3. Może w XE8 będzie w końcu poprawne. – TLama

+0

@TLama Ah, radości błędnie przetłumaczonych prototypów Win32. W tym przypadku nie ma to większego znaczenia, ponieważ jest 0, lub nie 0. –

Powiązane problemy