Currently, the TRegKey constructors may fail and leave the encapsulated HKEY handle invalid. While you can check the validity of the TRegKey object with the conversion operator THandle, there is currently no way to get the error code for the failure. The enclosed patch adds a GetError member function to TRegKey, to allow access to the error code.
However, this is only a minimal and unsatisfactory solution. Ideally, the TRegKey constructors should throw TXRegistry on failure, in accordance with good C++ coding practice. Except for the TRegConfigFile constructor, all of the code in OWLNext that uses TRegKey already assumes that successful construction of TRegKey will leave the object in a valid state (non-null handle), without doing any error checking (although, some of the client code performs error-checking on subsequent operations on the key).
PS. The patch also adds GetHandle to expose the OS handle of TRegKey. Although the class already has a THandle conversion operator for this purpose, adding GetHandle is consistent with the rest of OWLNext. The patch now excludes setting output parameters to zero in error cases, as discussed in the forum [discussion:39ed1d9276].
Bugs: #498
Discussion: Exception "TRegKey::TRegKey: Init failed"
Discussion: 39ed1d9276
Discussion: Some Bugfixes
Discussion: Some Bugfixes
Discussion: OWL 7 and Clang
Discussion: Help on incompatible changes in 7 series
Discussion: Help on incompatible changes in 7 series
Feature Requests: #207
Anonymous
Diff:
Related
Discussion: 39ed1d9276
Last edit: Vidar Hasfjord 2020-04-03
Good work, Chris!
I think error handling is a big enough feature to deserve its own ticket. I have changed the ticket title and description accordingly. Consider creating a separate ticket for the GetHandle function (or since this is minor, and the class already has this capability in the form of a conversion operator, we can just add it in a separate revision without a ticket).
I've reviewed your patch, and here are some comments and nit-picks:
Why not throw exceptions instead of adding GetError? Unless there is a good reason, we should consider exceptions here. Good C++ practice establishes the class invariant in the constructor and throws an exception if it cannot be established. Adding an error state to the class invariant complicates the possible state space. Of course, adding descriptive exceptions is a big change, possibly requiring client code updates, but this ticket will target version 7, which is aiming for modernisation, so that is not much of an issue.
Note that we already have TXRegistry, which can be extended with the error code.
LastRegErrorrather thanlastregerror. See our Coding Standards.Related
Wiki: Coding_Standards
Wiki: Submitting_bugs_and_feature_requests
I've now added TRegKey::GetHandle [r4784] and merged this change into Owlet [r4785], so this ticket can now focus on the error handling issue.
Related
Commit: [r4784]
Commit: [r4785]
I've looked at the original OWL 5 source code, and it seems exception handling was considered but disabled, or never completed, for some reason. The following commented-out line of code is found in the OWL 5 version of one of the TRegKey constructors, and this line is still present in the latest code on the trunk, now refactored into TRegKey::Init (THandle, REGSAM, TCreateOK):
Note that the TRegValue (const TRegValueIterator&) constructor does throw TXRegistry on failure, but this is a change since OWL 5:
Other TRegValue constructors do not throw exceptions, though, which is inconsistent. They call TRegValue::QueryTypeAndSize, but completely ignore the error return code. The QueryTypeAndSize function is new since OWL 5, due to refactorisation, but the original code in the constructors also just ignores error handling.
The original OWL 5 code only uses TXRegistry in TRegSymbolTable::UpdateParams and StreamOut, as well as in TRegistry::Update, and these cases are still present in the latest code on the trunk.
Overall, in summary, the error handling in the registry classes is incomplete and inconsistent.
I was under the impression that it is never a good idea in c++ to throw exceptions in constructors...
A common use pattern of the windows registry is to try to get a value out of the registry and have a default value ready if it doesnt exist, so that might be the reason why exceptions didnt make it too much in that class.
A better API would be maybe something like QueryRegistryValuePathOrDefault ... or something like it.
Too many fine grained exception handlers make the code not very readable.
Hi Chris,
That is incorrect. You are probably confusing the advise about destructors, which are not allowed to throw exceptions — an unhandled exception in a destructor leads to termination of the program. See Item 51, "Destructors, deallocation and swap never fail" in "C++ Coding Standards" by Sutter and Alexandrescu.
The advise for constructors is the opposite — you should "prefer to use exceptions to report errors" (Item 72 in "C++ Coding Standards"):
For example, if TRegKey construction fails, we don't really have a valid registry key object.
I fully agree. Expected scenarios should not be coded with exception handling. The parameter TCreateOK to the TRegKey constructor allows you to create the key if it does not exist. But if NoCreate is passed the constructor should ideally throw an exception if the key does not exist. To avoid awkward exception handling, we probably need a static Exists function (similar to std::filesystem::exists):
For registry values, a convenient API like you suggest would make sense. You can create a function for this purpose quite easily:
As for TRegKey, the check for existence of a value could be wrapped up in a static function TRegValue::Exists, or perhaps TRegKey::HasValue.
Last edit: Vidar Hasfjord 2020-04-13
Yes, guess i confused it with destructors then, thanks. and modern c++ is awesome :)
Ok, if there is no reason to the contrary, I recommend using exceptions to improve error handling in TRegKey (and TRegValue), and I hence vote against applying your patch in its current form. I'll leave it to you to take this any further, but I'll review any code changes, if you decide to go forward.
Last edit: Vidar Hasfjord 2020-04-12
As mentioned, to avoid error codes and exception handling for simple registry queries in ordinary client code, we need a way to check whether a key or value exists before we try to create a TRegKey or TRegValue to represent it, respectively. As suggested, this can be implemented as static member functions in TRegKey and TRegValue:
As a further convenience, we could add another couple of static functions to optionally create a TRegKey or TRegValue object based on whether the underlying registry key or value exists, respectively. These functions can be simply implemented using the Exists functions above and std::optional:
This would allow us to write simple client code like the following:
Alternatively, with some refactoring of the code, TRegValue::Exists and TRegValue::Query can be implemented as non-static member functions TRegKey::HasValue and TRegKey::GetValue, respectively. I think we just need to define TRegValue before TRegKey, using a forward declaration for TRegKey.
And TRegKey::Exists and TRegKey::Query can alternatively be implemented as non-static member functions HasSubkey and GetSubkey, respectively, operating on the parent key.
I think I would prefer this alternative, as it nicely complements the existing non-static member functions GetSubkeyCount and GetValueCount.
For example, with this alternative, the client code above would be rewritten like this:
What do you think?
Last edit: Vidar Hasfjord 2021-11-17
It seems to me that the implementation of a static TRegKey::Query in the manner of calling first Exists() and then creating the actual registry key has a redundant sequence of first opening the key (to check if it exists), then closing it, and then opening it again.
I think that checking if a registry key exists, but not actually using it, is a rare case.
So it will be better if we have a static method that directly open a key and returns it if exists, and then this method can be used to check for key existence as well.
Having an instance method of GetSubKey is also good, and in fact may be better, since in all cases here we have an existing parent instance (at the very least one of the static root keys).
So, my vote is on an instance method that returns a TRegKey if the key exists or null otherwise.
I have now made an experimental implementation of my suggested extensions in Owlet [r4797].
My experimental implementation includes the template function GetValueOrDefault:
This function will look up the value, and if it exists, it will try to perform a conversion on the TRegValue to type T. The type of the default value provided must be convertible to T. Note that pointers are not allowed, i.e. T cannot be LPCTSTR, because that would return a pointer to a dead object (the TRegValue object is local to the GetValueOrDefault function). But you can use owl::tstring instead.
For numeric values, you need to be specific about the data type. Only owl::uint32 and owl::uint64 are supported.
For example:
Related
Commit: [r4797]
Last edit: Vidar Hasfjord 2020-04-14
I like that, seems like a good solution.
Jogy wrote:
True. In my post, that you responded to, TRegKey::Query was implemented like that, for simple exposition. But like you say, it can be optimised by combining the steps and hence avoid opening the key twice. Actually, that is how I implemented the alternative TRegKey::GetSubkey, as you can see in [r4797]:
In effect, this implementation filters out ERROR_SUCCESS and ERROR_FILE_NOT_FOUND as valid program states (not errors) and throws an exception for all other error values.
Chris wrote:
OK, since we have agreement on the solution, I will merge my experimental implementation to the trunk.
Now that we will use TXRegistry more extensively, there is another issue that needs urgent fixing. The TXRegistry class holds a possibly dangling pointer to the key name. There is already a code comment about this (made by me in [r1122] as part of our work on standard string support):
I will follow up on that TODO comment.
Related
Commit: [r1122]
Commit: [r4797]
Last edit: Vidar Hasfjord 2020-04-17
The more you start looking at something, the more issues you find: I've now created a ticket regarding an issue with the TRegistry::Update and Validate functions.
See [bugs:#456].
Related
Bugs:
#456Last edit: Vidar Hasfjord 2020-04-21
I have now fixed the dangling pointer issue with TXRegistry. See ticket [bugs:#457].
This means that the OWLNext source code is now ready to use TXRegistry more consistently and extensively, if you want to go forward with this ticket.
As discussed earlier, my obvious and foremost suggestion is to make the TRegKey constructors throw TXRegistry if they cannot establish a valid handle. Some client code may need to be adapted to work correctly after this change, and even some code that continues to work correctly, should probably be updated, if coded for the old behaviour.
For example, the TRegConfigFile constructor does this to check the validity of the passed HKEY:
If the TRegKey constructor is made to throw, the whole if-statement above becomes redundant. The obvious improvement to TRegConfigFile is to hold the passed root handle as a member of type TRegKey, not a HKEY like now. Then the initialisation of the member will throw an exception if the handle is invalid.
Beyond the TRegKey constructors, you may consider whether to implement exceptions-based error handling in the member functions that currently are just thin wrappers around the corresponding Windows API functions. I suspect that a lot of client code just ignores the error code returned by these functions.
By freeing up the return value of these functions, you may be able to simplify the API. For example TRegKey::QueryInfo takes a boat-load of output pointers as arguments:
With exceptions, it can be changed to return an info structure, for example:
PS. This member function should be const, by the way. (Changed in [r4825].)
Edit: As suggested, a new functional-style overload of TRegKey::QueryInfo has been added, and it works just as described. On errors, it throws TXRegistry. See [r4854].
Related
Bugs:
#457Commit: [r4825]
Commit: [r4854]
Last edit: Vidar Hasfjord 2020-04-20
I've made some refactorisations in the OWLNext core, using the new functions added to TRegKey, leading to much simpler code in many cases. See [r4842].
Related
Commit: [r4842]
OK, as I said earlier, with the TXRegistry fix in place, OWLNext is now ready to use TXRegistry more consistently and extensively.
What would this look like? What changes am I talking about? For example, let's take a look at TRegConfigFile::WriteString. It now looks like this, after my latest refactorisation:
With a transition to TXRegistry we can simplify the code further by using TRegValue's assignment operator:
Here TRegValue's assignment operator will throw TXRegistry on failure. This has already been the case for a long time. The TRegValue constructor should throw an exception if the given key is invalid, but doesn't yet. Likewise, the TRegKey constructor should throw an exception on failure to establish a valid handle, but doesn't yet.
However, note that TRegValue is designed as a lazy reference class, so the constructors should not throw an exception if the value entry in the registry is nonexistent. The check on the existence of the value should happen in the accessor functions (e.g. GetData), and exceptions on failure to create a new value or write an existing one should happen in the mutator functions (e.g. Set).
As such, TRegValue does not represent an entry in the registry. It only represents the value of a potential entry. This is unlike TRegKey which actually does represent a key in the registry (by encapsulating a valid handle to it).
Also note that we could (and ideally should) change the return value to
voidhere, since we no longer use an error return code. This would force clients to update their error handling, if any. However, in this case, WriteString is a virtual function, so changing its signature has ramification throughout the class hierarchy. In a class hierarchy with base classes and virtual functions like here, ideally, the base class should specify in the documentation what exceptions should be expected on failure for each of its virtual functions, and then the derived classes should be written to satisfy that contract (which in our case here, may mean translation of the TXRegistry exception to the exception specified by the base class TConfigFile). Of course, without any specified contract, any C++ function can theoretically fail with any exception, unless the function signature hasnoexceptspecified (orthrow()in legacy code).Last edit: Vidar Hasfjord 2020-04-20
Diff:
Related
Discussion: 39ed1d9276
Hi Chris, based on my latest comments and the lack of objections, I have taken ownership of the ticket. I will implement my primary suggestion, i.e. make TRegKey constructors throw.
Edit: Throwing constructors have been implemented in [r4865], and the changes have been merged into Owlet [r4866].
Please review and give me feedback on any problems you may see.
Related
Commit: [r4865]
Commit: [r4866]
Last edit: Vidar Hasfjord 2020-04-26
I just updated OWLMaker to work with the latest changes in TRegKey [r4873].
This change allowed me to replace old ugly code with much cleaner code.
For example, this is the old code to locate the installation folder for Embarcadero C++Builder/RAD Studio products:
And here it is now:
PS. I've uploaded an updated version of OWLMaker. This also fixes a problem with the automatic update feature (see [r4876]), so you may have to manually download this update.
Related
Commit: [r4873]
Commit: [r4876]