Jac Goudsmit wants to merge 4 commits from /u/jac_goudsmit/log4cpp/ to master, 2026-03-27
The NTEventLogAppender can now be instantiated with a location of the NTEventLogAppender.dll. By specifying a full path to the file, it's not necessary to make sure the file is in the PATH.
Also added a constructor that doesn't initialize the registry and keeps the appender closed.
Also fixed a few minor bugs and improved internal consistency.
The changes should be fully backwards compatible, i.e. any existing code that uses the NTEventLogAppender as intended, should still work the same way.
Please see check-in comments in the branch for many more details.
| Commit | Date | |
|---|---|---|
|
[19cf8a]
(bug-148-improve-nteventlogappender)
by
NTEventLogAppender: Changing the code so member variables don't get changed after construction |
2017-10-01 06:07:32 | Tree |
|
[2ede7f]
by
NTEventLogAppender: Improved flexibility, fixed bug 148 The old version of addRegistryInfo would always use "NTEventLogAppender.dll" without drive or directory, so it was necessary to put the DLL in the PATH or change the PATH to add the directory where the DLL is stored. This is undesirable and unnecessary: if the full path of the DLL is stored in the registry, it doesn' t need to be in the PATH. Another problem was that the reopen() function would always get the registry key to check if it existed, which should not be necessary in most cases. And if the registry key already existed, the function wouldn't save the values so it was impossible to deliberately change the values. Furthermore, the addRegistryInfo() function returned void, which made it impossible to notify the caller of a failure, which is not impossible; keep in mind that the registry can only be written if the program runs as administrator. Finally, the protected registry functions used TCHAR in their prototypes, which is a very bad idea: programs that are compiled in Unicode and use these functions would be incompatible with the log4cpp.dll because the DLL and the program would disagree on what a TCHAR is. This results in linker problems that are potentially very difficult or impossible to debug, especially for beginning Windows programmers. I moved the registry modifying code into an overloaded reopen() function that takes a parameter for the name, and a parameter for the DLL location. If the DLL location is blank, it uses "NTEventLogAppender.dll" for backwards compatibility. reopen() with parameters always attempts to write the values into the registry unless it's unable to open the key. If the source name (which is used as registry subkey) is blank, the parameterized reopen() fails. The non-parameterized reopen() doesn't operate on the registry, it merely calls ::RegisterEventSource. The protected functions that operate on the registry were corrected to use LPCSTR (const char *) instead of TCHAR * (without const). Existing software that calls these functions should still work. Two constructors were added: one that accepts the DLL path as a parameter and one that creates the instance in closed state, i.e. without calling reopen(). This fixes the problem that derived classes might not want the constructor to call the reopen() function, and because virtual functions aren't really virtual when called from a constructor, it would not be enough to override the function in the derived class. Other changes: |
2017-08-13 08:30:49 | Tree |
|
[e4df8c]
by
NTEventLogAppender: Improved internal consistency; deprecated open() The old open() function was only safe to call when the appender is in closed state, otherwise it would leak a handle. Therefore it made sense to move the functionality of the open() function to the reopen() function, so that close() is called first, and so that any failure can be reported as a "false" result. The open() function now delegates to reopen(). I added a \deprecated tag so that Doxygen will flag it as such. I didn't add a __declspec(deprecated) because I think Visual C++ 6 won't handle it correctly. I also rewrote the comments for reopen() and close() to indicate that these functions correctly handle corner cases (opening when already open, closing when already closed). |
2017-08-13 06:18:55 | Tree |
|
[90c122]
by
NTEventLogAppender: Improved internal consistency, removed tabs - close() now resets _hEventSource to NULL to prevent dangling handle |
2017-08-13 02:35:51 | Tree |
Hello Jac,
Thank you for this great amount of work and sorry that I haven't replied earlier.
I will seek through the changes and let you know if there are any additions I wish to be considered.
Alex.
I've looked through the commits for changing NTLogAppender and got there few questions. Would you tell please:
E.g, call to RegSetValueEx() resolves to RegSetValueExA() (Ansi) or RegSetValueExW() (Unicode)
It could be reasonable to use more wide type LPCTSTR instead of concrete LPCSTR.
(quick link to that is https://stackoverflow.com/a/8481533/532541 )
Last edit: Alexander Perepelkin 2017-08-21
Hi Alex,
Whether I personally call reopen in my project is not important. But reopen is a public function so it CAN be called. If you're asking this to find out why I moved the registry code to an overloaded reopen(), the reasons for that are:
I don't know if the Log4CPP took any measures to
#undef _UNICODEbefore#include <tchar.h>but if it doesn't, a project that has_UNICODEdefined and includes log4cpp/NTEventLogAppender.h, will see an external declaration of (say)HKEY regGetKey(wchar_t *subkey, DWORD *disposition);becauseTCHAR *is interpreted aswchar_t *instead ofchar *. So when someone wants to write a derived class that calls this function, their compiler will complain if they want to call the function with e.g."mysubkey"because that's not a wide-character string. So they'll useL"mysubkey"orTEXT("mysubkey~)which will compile without errors or warnings. But then the linker will look for a public symbol in any library (including the import library of log4cpp) for the function with the wchar_t pointer and it won't find it, because the log4cpp library was compiled without Unicode, so the mangled function name has a different symbol in the import library and the DLL than what the linker of this user is looking for.Because Windows has an API that's mostly based on C and PASCAL, overloading between two functions with the same name but different parameters (char vs. wchar_t) is impossible. So Windows uses a preprocessor trick to do this: When you call CreafeFile you really call CreateFileA or CreateFileW depending on your Unicode setting.
The old NTEventLogAppender code only provides one implementation of the registry functions, so unless it takes measures to define its own TCHAR type, you're making the preprocessor and compiler promise something that isn't delivered by the library: a Unicode version of the functions. There are two ways to solve this: either provide two versions of the functions or change the declaration of the function into a non-ambiguous one. Since the registry functions aren't really a core functionality of the NTEventLogAppender anyway (they are arguably just made available as protected functions for convenience), I decided to do the latter: change the declaration so that it would be non-ambiguous. There's just one problem with my change: The change from
char *toLPCSTR(which translated toconst char *) will also change the mangled signature of the functions, so that those who use the registry functions will have to recompile and relink to the import library (if you simply replace the DLLs in an existing binary project, you'll get an execution error if the registry functions are used). But no code has to be changed in any client projects that use the registry functions. I figured this would be a small and acceptable price to pay.Sorry this got so long. Thanks for reading and thanks for considering my changes!
===Jac
I asked you whether you call it or not because you are the first user of this new code and may have a pattern.
Please put some thoughts into the following:
and reopen() be the only public overload for reopening
I'll get back to TCHAR etc. a bit later.
Last edit: Alexander Perepelkin 2017-08-23
If I understand you correctly you want to store the DLL location at construction time, and then not allow the client code to change it anymore, so it's not possible to change the location.
I disagree with that. I think there are possible use cases where the Appender is created when the program is started, but where the DLL location is not yet known until later, or may change later. For one thing, the registry key where the subkey and values need to be stored, is read-only unless the program runs as administrator. So an application might do an impersonation to get sufficient access rights to the registry, and then initialize the registry values and keys.
If I understand you correctly, you would like to separate the registry code into a separate function that's private and gets called from the constructor as well as from reopen(), but keep the other code.
I see the advantage in that: the constructors would call that function instead of a virtual function (which is undesirable as we discussed because it might confuse people), and it wouldn't unnecessarily call close() (via reopen) as part of the construction.
I think that's a good idea but I would do it slightly differently. How about if I write a static public function NTEventLogAppender::InitRegistry that creates or updates a registry key for event logging, and takes parameters for all the 4 values that are stored in the registry, with default values that correspond to the current situation?
===Jac
Last edit: Jac Goudsmit 2017-08-23
1) Object of NTEventLogAppender class is initialized automatically using values suplied in config file.
It's not responsibility of the library to track changes of the configuration in registry. If something gets changed in configuration, logging subsystem may be shutdown and reinitialized by the directive from the application. This is why name, source and dllLocation must be immutable fields.
2) I see the public static method is a bit overkill for the task. The only need here is to move virtual method away from ctr, this is well solved by having separate private impl method. public static would be visible outside and confuse potential users of the class.
I'm sorry that it's taking me a while to get back on this; I've been very busy with work and other projects. I hope to fix the code in my repo later this week, then I'll update this discussion. Thanks!
===Jac
Hi Alex,
I just changed the code so that the member variables of the NTEventLogAppender class are not changed after construction.
That means some things that were possible before this latest change (19cf8a969762599a198e0f276238d4b86977ffc9) like changing the DLL location on the fly, aren't possible anymore (as you requested), but I made it so that they can still be accomplished by creating a subclass.
Thanks for considering!
===Jac
Summing comments on commits in merge request and my replies:
close()is either the last action performed by the class in destructor, or the action which is immediately followed by the open() and rewites handle right away. Hence, though nulling it might look like a proper precaution, it is redundant.Having checks in functions open() and
_append()would hide mistaken parameters instead of raising errors.If
_strSourceNameis empty, it most probably means that this is a mistake and it should be caught and fixed during testing (instead of being hidden behind a check)open()is protected and can not be called by clients of the class. Other calls are controlled by the implementation and those do not leak a handle.That would mix semantics of open/reopen, making implementation more complex than it can be.
These are all good points and could be fixed independently. Unfortunately, current merge request takes care of all of them together and they are now on top of another changes.
Rewriting registry keys on each reopen() call is solved by moving
addRegistryInfo()invokation into constructor. That way multiple reopen() calls will not cause many cycles of equivalent writes to registry.Other two points can be considered as a list of good items to do.