dummzeuch 1505 Posted March 23, 2020 (edited) The base Exception class introduces various constructors. If you use ressources strings, there are two options: raise Exception.CreateFmt(MY_RES_STRING, [parameters, go, here]); raise Exception.CreateResFmt(@MY_RES_STRING, [parameters, go, here]); The second form is the one used everywhere in the RTL/VCL. It actually accepts a PResStringRec parameter. Up to now I also used that form in some of my internal libraries. But when I enabled the "Typed @ operator" compiler option today to get the compiler to find some errors in my code, it turned out that this form doesn't compile any more (in Delphi 2007, haven't tried anything else). Given that the first form works fine, why would I want to use the second form anyway? Edited March 23, 2020 by dummzeuch Share this post Link to post
Der schöne Günther 316 Posted March 23, 2020 We are using this. Passing a pointer to a ResourceString instead of the (already translated) ResourceStrings allows us to show localized error messages to the user while using English messages when logging the exceptions to disk. Share this post Link to post
Anders Melander 1782 Posted March 23, 2020 Constant resourcestring references are resolved during initialization by System._InitResStrings. If you change the application language after that, e.g. by calling LoadResourceModule, then these references will not be updated. In the example below the first ShowMessage in unaffected by the change of language while the second one isn't. resourcestring sFoo = 'Foo'; sBar = 'Bar'; const sFooBar: array[boolean] of string = (sFoo, sBar); sBetterFooBar: array[boolean] of PResStringRec = (@sFoo, @sBar); begin LoadResourceModule('foofoo.bar'); ShowMessage(sFooBar[True]); ShowMessage(LoadResString(sBetterFooBar[True])); end; Apart from that I suspect the RTL/VCL uses PResStringRec for C++ compatibility. 1 Share this post Link to post
Der schöne Günther 316 Posted March 23, 2020 I should have mentioned we are using the good old GnuGetText / dxGetText for localization. It seems to do some magic to swap out the method for resolving resource strings from System.pas with something else. I can only say that "It just works". Share this post Link to post
Anders Melander 1782 Posted March 23, 2020 1 hour ago, Der schöne Günther said: It seems to do some magic to swap out the method for resolving resource strings from System.pas with something else. That's fairly easy and you don't need to hack the system unit. You just need to patch the LoadStringW import. For example the following patches FindResourceW and a few others to implement a DFM fallback mechanism that loads from the main module in case the resource module doesn't contain a given DFM resource. It can easily be modified to patch LoadStringW to load resourcestrings from a dictionary or whatever. unit amLocalization.FindResourceFallback; interface function EnableResourceLoadingFallback: Boolean; implementation uses Windows; function HookAPI(const Name, Module: string; Hook: pointer): pointer; var ImageBase, Old: Cardinal; PEHeader: PImageNtHeaders; PImport: PImageImportDescriptor; PRVA_Import: LPDWORD; ProcAddress: Pointer; begin Result := nil; ImageBase := GetModuleHandle(NIL); PEHeader := Pointer(Int64(ImageBase) + PImageDosHeader(ImageBase)._lfanew); // pointer to the imports table of the main process: PImport := Pointer(PEHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + ImageBase); // pointer to the WinAPI function we want to hook: ProcAddress := GetProcAddress(GetModuleHandle(PChar(Module)), PChar(Name)); if ProcAddress = NIL then Exit; while PImport.Name <> 0 do begin PRVA_Import := LPDWORD(pImport.FirstThunk + ImageBase); while PRVA_Import^ <> 0 do begin if PPointer(PRVA_Import)^ = ProcAddress then begin // initially imports table is in read-only segment: if not VirtualProtect(PPointer(PRVA_Import), 4, PAGE_READWRITE, Old) then Exit; Result := PPointer(PRVA_Import)^; // replacing import address with our own: PPointer(PRVA_Import)^ := Hook; // restoring old memory protection mode: if not VirtualProtect(PPointer(PRVA_Import), 4, Old, Old) then begin Result := nil; Exit; end; end; Inc(PRVA_Import); end; Inc(PImport); end; end; type TFindResourceW = function(hModule: HMODULE; lpName, lpType: PWideChar): HRSRC; stdcall; var FindResourceW: TFindResourceW = nil; function HookedFindResourceW(hModule: HMODULE; lpName, lpType: PWideChar): HRSRC; stdcall; begin Result := FindResourceW(hModule, lpName, lpType); if (Result = 0) and (hModule <> hInstance) then Result := FindResourceW(hInstance, lpName, lpType); end; type TLoadResource = function(hModule: HINST; hResInfo: HRSRC): HGLOBAL; stdcall; var LoadResource: TLoadResource = nil; function HookedLoadResource(hModule: HINST; hResInfo: HRSRC): HGLOBAL; stdcall; begin Result := LoadResource(hModule, hResInfo); if (Result = 0) and (hModule <> hInstance) then Result := LoadResource(hInstance, hResInfo); end; type TSizeofResource = function(hModule: HINST; hResInfo: HRSRC): DWORD; stdcall; var SizeofResource: TSizeofResource = nil; function HookedSizeofResource(hModule: HINST; hResInfo: HRSRC): DWORD; stdcall; begin Result := SizeofResource(hModule, hResInfo); if (Result = 0) and (hModule <> hInstance) then Result := SizeofResource(hInstance, hResInfo); end; function EnableResourceLoadingFallback: Boolean; begin FindResourceW := HookAPI('FindResourceW', kernel32, @HookedFindResourceW); Result := Assigned(FindResourceW); if (Result) then begin LoadResource := HookAPI('LoadResource', kernel32, @HookedLoadResource); Result := Assigned(LoadResource); end; if (Result) then begin SizeofResource := HookAPI('SizeofResource', kernel32, @HookedSizeofResource); Result := Assigned(SizeofResource); end; end; initialization EnableResourceLoadingFallback; finalization end. Share this post Link to post
Vandrovnik 214 Posted March 23, 2020 "fairly easy" 🙂 In VirtualProtect calls, hard-coded value "4" - isn't it better to use sizeof(pointer) instead, so that it is safe in 64-bits too? Or this code is for 32-bits only? Share this post Link to post
Anders Melander 1782 Posted March 24, 2020 1 hour ago, Vandrovnik said: isn't it better to use sizeof(pointer) instead Yes. I've only used this on 32-bit and I don't know if the structures are the same for 64-bit. Share this post Link to post
spbogie 2 Posted April 7, 2020 One reason for using the CreateRes version over the normal Create is to avoid all of the additional code that the compiler generates to load the resource string before passing it to Create. An example to help demonstrate (compiled w/ 10.3.3 in Win32-Release configuration) procedure DoExceptRes; var lRes: Boolean; begin lRes := DoSomething; if not lRes then raise Exception.CreateRes(@MY_RES_STRING); end; 004EA78B 90 nop TestMain.pas.117: lRes := DoSomething; 004EA78C E8F7FFFFFF call DoSomething TestMain.pas.118: if not lRes then 004EA791 84C0 test al,al 004EA793 7516 jnz +22 ; $004ea7ab TestMain.pas.119: raise Exception.CreateRes(@MY_RES_STRING); 004EA795 B9B4A04E00 mov ecx,$004ea0b4 004EA79A B201 mov dl,$01 004EA79C A13C9B4100 mov eax,[$00419b3c] 004EA7A1 E886BFF3FF call Exception.CreateRes 004EA7A6 E8D1F9F1FF call @RaiseExcept TestMain.pas.120: end; 004EA7AB C3 ret procedure DoExcept; var lRes: Boolean; begin lRes := DoSomething; if not lRes then raise Exception.Create(MY_RES_STRING); end; TestMain.pas.125: begin 004EA7AC 55 push ebp 004EA7AD 8BEC mov ebp,esp 004EA7AF 6A00 push $00 004EA7B1 33C0 xor eax,eax 004EA7B3 55 push ebp 004EA7B4 68FFA74E00 push $004ea7ff 004EA7B9 64FF30 push dword ptr fs:[eax] 004EA7BC 648920 mov fs:[eax],esp TestMain.pas.126: lRes := DoSomething; 004EA7BF E8C4FFFFFF call DoSomething TestMain.pas.127: if not lRes then 004EA7C4 84C0 test al,al 004EA7C6 7521 jnz +33 ; $004ea7e9 TestMain.pas.128: raise Exception.Create(MY_RES_STRING); 004EA7C8 8D55FC lea edx,[ebp-$04] 004EA7CB B8B4A04E00 mov eax,$004ea0b4 004EA7D0 E8F755F2FF call LoadResString 004EA7D5 8B4DFC mov ecx,[ebp-$04] 004EA7D8 B201 mov dl,$01 004EA7DA A13C9B4100 mov eax,[$00419b3c] 004EA7DF E814BEF3FF call Exception.Create 004EA7E4 E893F9F1FF call @RaiseExcept TestMain.pas.129: end; 004EA7E9 33C0 xor eax,eax 004EA7EB 5A pop edx 004EA7EC 59 pop ecx 004EA7ED 59 pop ecx 004EA7EE 648910 mov fs:[eax],edx 004EA7F1 6806A84E00 push $004ea806 004EA7F6 8D45FC lea eax,[ebp-$04] 004EA7F9 E88202F2FF call @UStrClr 004EA7FE C3 ret 004EA7FF E998F8F1FF jmp @HandleFinally 004EA804 EBF0 jmp -16 ; $004ea7f6 004EA806 59 pop ecx 004EA807 5D pop ebp 004EA808 C3 ret Note how in the case where we just call Create instead of CreateRes, Delphi needs to generate a call to LoadResString within the context of our procedure in order to actually turn the resourcestring into a value that can be passed into Exception.Create. This isn't really an issue though because it only happens if we're actually going to raise the exception, and CreateRes will ultimately need to do the same thing internally. The issue comes from the fact that Delphi needs somewhere to put the string it gets back from LoadResString before passing it to Exception.Create. To that end it generate an implicit local string variable, and because strings are managed, and local variable have a scope covering the entire procedure lifetime, it wraps the entire body of our procedure into an implicit try...finally to ensure the string is cleaned up. As a result, we will incur the cost of this try...finally scaffold and the call to UStrClr every time we call DoExcept. Even if it never actually raises an Exception. A similar thing applies to the use of CreateFmt vs Create(Format(...)). The compiler will generate an implicit variable to store the result of the call to Format, and you will incur this overhead regardless of whether the actual call to Format ever happens. Whether or not this actually makes a measurable real world difference will depend on your use case, but it is worth being aware of. 1 1 Share this post Link to post
Fr0sT.Brutal 900 Posted April 8, 2020 15 hours ago, spbogie said: As a result, we will incur the cost of this try...finally scaffold and the call to UStrClr every time we call DoExcept. Even if it never actually raises an Exception. A similar thing applies to the use of CreateFmt vs Create(Format(...)). Noticed this hidden catch too so turned all my routines to use CreateFmt. Btw, the same goes for string concatenation raise Exception.Create('Error: '+ErrorText); 1 Share this post Link to post
Stefan Glienke 2002 Posted April 9, 2020 (edited) 15 hours ago, Fr0sT.Brutal said: Noticed this hidden catch too so turned all my routines to use CreateFmt. Btw, the same goes for string concatenation That is why for performance critical code such code should be moved to an extra (or nested) routine. Otherwise you get pro- and epilogue polluted with code that is not necessary in the majority of cases. Additional read: https://www.delphitools.info/2009/05/06/code-optimization-go-for-the-jugular/ Edited April 9, 2020 by Stefan Glienke Share this post Link to post
Fr0sT.Brutal 900 Posted April 9, 2020 6 hours ago, Stefan Glienke said: That is why for performance critical code such code should be moved to an extra (or nested) routine. Otherwise you get pro- and epilogue polluted with code that is not necessary in the majority of cases. Yep, I know 😉 For my projects this fact pushed me to change most of in-place string concatenations to formatting of constant literals which additionally is good preparation for easy localization in future. Share this post Link to post
jbg 239 Posted April 9, 2020 14 hours ago, Stefan Glienke said: Otherwise you get pro- and epilogue polluted with code that is not necessary in the majority of cases. At least it is not as performance damaging as preconditions. I remember the Indy project changed all the "if condition then raise" to something like "EIdException.RaiseIf(condition, ExceptionString-Concatenation)" in 2003/2004 and changed all that back because of the performance hit. And even in Java with a JIT-Compiler you shouldn't use preconditions if you don't know the effect it has: Somebody changed all the exception handling in a Base64 decoder to preconditions and the result was that converting a 500 MB file took hours instead of milliseconds. The garbage collector had to clean up all the strings that were created for every byte that was read and there were a lot of preconditions in the inner most loop. Share this post Link to post
Stefan Glienke 2002 Posted April 9, 2020 It gets bad if the implicit string or other managed type variables are the only of that type because then you get the implicit try finally added. And at least for win32 we know now that try/finally is implemented kinda badly: https://quality.embarcadero.com/browse/RSP-27375 Share this post Link to post