I’m writing this article because I want to share programming habits with you. What habit did prove to be a good one for you? Share yours then, please.
if not ImpersonateLoggedOnUser(token) then RaiseLastOsError;
In this way you don’t miss any failed call and wonder what is happening (I can recall a lot of forum threads that ask about this).
RaiseLastOsError generates an EOSError exception that contains the error number and text in its message. It also stores the GetLastError value in its property ErrorCode.
One more good thing is that a caller of the function, where this code is implemented, cannot ignore an error value. The API function ImpersonateLoggedOnUser is very problematic because if it fails all subsequent calls are made on the security token of the process instead of the thread. So a client could access more things than usual. So if we had a function that implements the example
function MyImpersonateUserToken(UserName : String; out UserData : PUserData) : Boolean; begin //get user token here //get user information from token here and store it into UserData result := ImpersonateLoggedOnUser(token);
As you can see a caller can just do the following
MyImpersonateUserToken(UserName, UserData);
If UserData.Groups.IsMemberOf('Administrators') then
Be aware that the example is only fictional. However I have seen many production codes that call WinAPI without error checking. The WinAPI usually (and the code above definitely) does not touch any of the output parameters if they fail. So you probably get an Access Violation (best case) or it works randomly (worst case). See that UserData is not initialized here. But what about this one:
procedure MyImpersonateUserToken(UserName : String; out UserData : PUserData); begin //get user token here if not GetUserToken then raiseLastOsError; //get user information from token here and store it into UserData if not GetTokenInformation then raiseLastOsError; if not ImpersonateLoggedOnUser(token) then raiseLastOsError;
You see that the function is really a procedure now that throws exceptions in case of failure. In this way the code following the function call MyImpersonateUserToken isn’t executed at all. Instead, we need to wrap an exception trap around the function call to let the code continue.
try
MyImpersonateUserToken(UserName, UserData);
except
end;
if UserData.Groups.IsMemberOf('Administrators') then
Okay, everbody should see that this is a really ******* code and should not be left in this way.
If you make an habit from this you won’t miss the old way. Instead it takes less time to implement and I don’t mention the time searching for memory leaks.
Furthermore, I think it looks much better and besides of that it is really easy to read and understand.
However, the most important reason (for me) is that any user who calls this function incorrectly (e.g. user not found) gets a nasty exception thrown into the face.
These are the reasons why JWSCL throws exceptions in 99,99% of its methods instead of returning error values.
Programmers have a hard to find memory leaks. There are libraries (like JWSCL) that support smart pointers but Delphi comes also with “smart pointers” (besides interfaces). Although it needs some more work. If you have translated code from C to Delphi you have seen it maybe. I’m talking about “goto” statements in C code.
{
...
if (!GetTokenInformation(...))
goto exit_1;
goto exit_0;
:exit_1
CloseHandle(hToken);
:exit_0
return (result);
}
In Delphi we don’t use goto because we were told that it is bad habit. So we directly put the exit after each failed function called.
begin ... if not GetToken(hToken) then begin result := true; exit; end; if (not GetTokenInformation(...)) then begin CloseHandle(hToken); result := FALSE; exit; end; CloseHandle(hToken); result := TRUE; end;
In more complex code you can easily miss a failure code path or miss to close a handle or free an object. So why don’t do it this way?
... if not GetToken(hToken) then raiseLastError; try if (not GetTokenInformation(...)) then raiseLastError; if GetMeOutOfHere() then exit; finally CloseHandle(hToken); end; end;
The try/finally statement ensures that the code within finally is executed. It does not matter that an exception is raised (we know that already). Even if we call “exit” the finally part is also executed.
However, you need to make sure that the variables within finally are valid.
1. CloseHandle throws an exception if the handle is not valid. This only happens if it detects an attached debugger.
2. Free, FreeAndNil, FreeMem, Dispose will throw an exception depending on the content of the given pointer. In worst case nothing happens and you get garbage results anywhere else in your application.
So the correct way is to trap every allocated variable (pointer, class) in a separate try/finally statement.
New(Ptr); try MyClass := TMyClass.Create; try ... finally FreeAndNil(MyClass); end; finally Dispose(Ptr); Ptr := nil; end;
Make it an habit to implement the finally part immediately. Delphi helps you by adding the finally part automatically.
New(Ptr); try (1) finally Dispose(Ptr); Ptr := nil; end;
Now continue at position (1).
One more tip: If you have a lot of source lines between try/finally you can also put a comment behind finally to show the initial call.
New(Ptr); try ... many lines ... finally //New(Ptr); Dispose(Ptr); Ptr := nil; end;
In this way you don’t mix up the variable names allocated and freed. And we also don’t mix up the allocation types (e.g. GetMem <> Dispose)
If you want to share your habits you can do this by leaving a comment. Or better you can write your own article and link it here so a conection is made.
Looking forward to hearing from you!
Christian
12 Responses
Oliver
14|Jan|2010 1I prefer using stack-allocated template classes that hold instances of whatever resource they’re supposed to handle (buffers, class instances, handles …). Due to the fact that the container class is on the stack it’s being destroyed when leaving the scope. With it, dies the contained class instances. Really convenient.
Alas, Delphi doesn’t offer that. In fact there used to be a way to work around this using (the now obsolete) “object” (instead of “class”) syntax that would basically resort to a similar method. Well, I guess Delphi never had it with syntactic sugar
Another workaround depends on the implementation of (COM) interfaces to get rid of the try/finally that is just cluttering your code. Marcel and I used that for a sample project about the Native API for the Delphi Magazine (when it was still in print). IIRC we implemented a UNICODE_STRING wrapper this way. But again, this is also just a crutch because Delphi lacks the means to express it any other way without.
Readability means maintainability. And given the lack of better alternatives I understand that such stuff has to be used as it is in Delphi.
Jeroen Pluimers
14|Jan|2010 2@Oliver: you can do that with interface references to objects that descend from TInterfacedObject. Pretty easy, for instance to save/restore the screen cursor.
Ali
15|Jan|2010 3@Oliver: Delphi 2007 – 2010 support records which contain fields, methods, and properties, just like classes. Such records are stored on stack, so you can use the same technique you mentioned in newer versions of Delphi using records.
For more information about such records, you can refer to this:
http://docwiki.embarcadero.com/RADStudio/en/Structured_Types
Christian Wimmer
15|Jan|2010 4Records are no replacement for classes.
Jolyon Smith
15|Jan|2010 5Not sharing a good habit, but adding that making assumptions about errors to raise can be as dangerous as not raising errors.
There is an assumption in the VCL that cost me countless hours of frustration:
function TWinControl.GetDeviceContext(var WindowHandle: HWnd): HDC;
begin
if csDesigning in ComponentState then
Result := GetDCEx(Handle, 0, DCX_CACHE or DCX_CLIPSIBLINGS)
else
Result := GetDC(Handle);
if Result = 0 then raise EOutOfResources.CreateRes(@SWindowDCError);
WindowHandle := FHandle;
end;
I was getting EOutOfResources errors and spent hours fruitlessly trying to figure out where my seemingly very simple code was haemhorraging resources so badly that I was exhausting the system.
I eventually decided that I was being lied to, tracked the exception back to this assumption and determined that the problem was not in fact a lack of resources but simply an invalid window handle.
With that more accurate information, identifying the problem then took a matter of moments!!!
Thanks for nothing VCL!
Christian Wimmer
15|Jan|2010 6Yes, I also think this is an error. It should have been EOsError.
Roddy
15|Jan|2010 7@Ali – AFAIK, Records can’t have destructors, so they cannot automatically free owned resources.
stanleyxu2005
15|Jan|2010 8Delphi is really not a good language for clean code. Take a look at language D.
Christian Wimmer
15|Jan|2010 9Well, mathematicians would argue against that
Oliver
17|Jan|2010 10@Jeroen: this is what I mentioned as the second workaround. It didn’t strike me as easy or simple, though. Let alone portable. And it adds bloat to my binary that I may not be ready to accept
… portability is a big issue for me, btw.
@Ali: thanks, wasn’t aware of it. However, others seem to disagree that this is a full replacement and given the quality of BDS 2006 when I bought it, I’m not ready to shell out another few hundred Euros just to get the latest and greatest (as in most bloated
) Delphi. Of course Embarcadero will blackmail me, approximately this year, to upgrade now or never be able to upgrade again from BDS 2006 to the newest version of whatever the BDS is called now. Let’s see whether I will resist.
@stanleyxu2005: Hmm, I’d also argue in favor of D, but there are other clean languages as well. All in all it depends on the problem at hand to figure which tools to use. Oh, and of course a little discipline is required during the development. I have to maintain a horrible legacy project written in BCB6, that shows how the easy “RAD-style” can condone bad programming habits, such as sprouting of code that is based on the idea of “forms”, rather than separating that code into classes and have the form own such a class instance or similar …
However, the usability of the tool to be used is of importance. This is something where I have my problem with (Borland/Inprise/Borland/Codegear/Embarcadero)’s solutions since Delphi 7, at latest.
Perhaps, though, those “improvements” which aren’t useful, but instead annoying, to me make a lot of sense to others. And after all (Borland/Inprise/Borland/Codegear/Embarcadero) has introduced Unicode after only a little more than a decade of fully “unicodified” Windows … and of course they had to do it in the “worst possible way(tm)”. For one, there is now only Unicode, instead of the good old choice between ANSI and Unicode.
Perhaps I should just wait a few more decades … or instead continue to use my current tool set and adjust that whenever the need arises.
// Oliver
Jeroen Pluimers
19|Jan|2010 11@Oliver: I must have been sleeping when I skipped everything after the word COM.
Sorry. You are right. But only partially
Since Delphi interfaces are completely decoupled from COM (i.e. they don’t depend on COM, they just use the same internal structure so it is easy to consume COM interfaces), you don’t get the bloat that prevents you to be portable. As a matter of fact, descending from TInterfacedObject and using interfaces works just as well in Kylix and Delphi .NET as it does in Delphi Win32.
I find Delphi 2010, 2007 and 7 the most stable versions so far. Delphi 2010 is about as fast as Delphi 7 once was, but Delphi 2007 is not that much slower.
Lately, I had to do some maintenance in Delphi 7, and it was a pain going back so far. It lacks so much productivity I have now (and I do a lot of coding that does not use the form/frame/datamodule designers at all).
@Roddy: read this piece by Allen Bauer about finalizing records: http://blogs.embarcadero.com/abauer/2008/09/18/38869
It is tricky, but can be done.
But you should hide that stuff as deep as possible though
–jeroen
–jeroen
Wouter van Nifterick
06|Mar|2010 12Side note: you could use SysUtils.Win32Check to make your code a bit easier to read:
procedure MyImpersonateUserToken(UserName : String; out UserData : PUserData);
begin
//get user token here
Win32Check(GetUserToken);
//get user information from token here and store it into UserData
Win32Check(GetTokenInformation);
Win32Check(ImpersonateLoggedOnUser(token));
…
end;
Leave a reply
Search
Paypal donation (EUR)
Categories
Archives
Tags
Recent Posts
Recent Comments
Blogroll
JEDI Sites
Pages
A design creation of Design Disease
Copyright © 2007 - JEDI Windows API - is proudly powered by WordPress
InSense 1.0 Theme by Design Disease brought to you by HostGator Web Hosting.