-
Notifications
You must be signed in to change notification settings - Fork 55
mCtrl Internals
Disclaimer: Do not expect this document to be complete documentation of mCtrl internals. The definite documentation of mCtrl internals is the source code itself. This document rather offers some general rules and hints where to start studying its source code, and explains few patterns (or best practices) used in mCtrl implementation.
Any changes in the exported interface (include/mCtrl.h
, include/mctrl/*.h
) have to be very careful. Main rules are:
- Consistency: Follow similar interface in
MCTRL.DLL
,USER32.DLL
orCOMCTL32.DLL
. For example the tree-list view control borrows a lot from standard tree and list controls. - Compatibility: Although our interface is not yet rock stable (until we reach version 1.0), source as well as binary compatibility changes should already be avoided. (Once we reach 1.0, we will use the word "must" instead.)
- New types (especially non-trivial structures) should be defined as extensible in the future. (Use
cbSize
as 1st member of structure where future adding new of members is possible.) - Beware of conflict. Use
mc
,MC
prefix for all symbols intended to be used by apps andMCTRL
ormctrl
for internal ones (which are not part of public contract but need to reside in public header.)
There are two initialization phases. The 1st one is when system loader calls DllMain()
. As Windows calls this function with loader lock held, there are severe limitations what can be done there. See the following articles:
- Some reasons not to do anything scary in your DllMain
- Another reason not to do anything scary in your DllMain: Inadvertent deadlock
- Does creating a thread from DllMain deadlock or doesn't it?
Most importantly, malloc()
, free()
, LoadLibrary()
, LoadLibraryEx()
, FreeLibrary()
, anything from COMCTL32.DLL
or USER32.DLL
must not be called from DllMain()
. Most functions (except those listed) from KERNEL32.DLL
should be ok to call.
Therefore we initialize only very few mCtrl internal modules from the DllMain()
. (Functions called at this stages typically have a name <modulename>_init()
for initialization, and <modulename>_fini()
for uninitialization.)
As most MCTRL.DLL
goodies are not yet initialized, those functions are allowed to use only few global variables: mc_instance
and mc_instance_kernel32
.
All other initialization is deferred to the time when application calls some public initialization function. The implementation in module.c
makes sure that the public function initializes the given module as well as all modules the given module depends on.
The public initialization functions have names like mc<ModuleName>_Initialize()
and mc<ModuleName>_Uninitialize()
. mc<ModuleName>_Initialize()
implementation always iterates over all internal modules the given public module depends on, and initializes them (or increases their reference count if they already are initialized) in the right order. Similarly, mc<ModuleName>_Uninitialize()
walks over the same module list in reversed order, decrements the reference counts, and if the count reaches zero for any module, it performs its uninitialization. Both function use a critical section to synchronize their work.
All modules which can be initialized directly or indirectly through this machinery from the public API follow the naming convention <modulename>_init_module()
and <modulename>_fini_module()
for the init/cleanup functions to be called from module.c
.
Application can use multiple threads and MCTRL.DLL
implementation has to deal with it. Of course we assume the application does it in the right way (i.e. control messages are only sent from the thread which created the given control).
Therefore, if some Win32API functionality needs per-thread initialization (e.g. COM in single-threaded aperture or BufferedPaintInit()
in UXTHEME.DLL
), it is typically done during creation/destruction of the control and not when the control class is registered or unregistered. Controls can be created in different (and numerous) threads then the class registration.
MCTRL.DLL
should not start new threads on its own, with exceptions where a standard DLL does it internally for its own purpose. (E.g. MSHTML.DLL
used for the HTML control does so.)
In general, the public interface of MCTRL.DLL
supports the ANSI/Unicode string duality in the same way as most of Win32 API does. Any patches should follow that.
This in particular means that any types (e.g. structures) containing strings, any messages or functions twiddling with strings (or structures with them) should exist in two favors: Unicode (suffix W
) and ANSI (A
). And that there should be a resolution macro (same name but without the suffix) resolving to the former (#ifdef UNICODE
) or the latter (otherwise). Use macro MCTRL_NAME_AW
for the resolution.
Internally, MCTRL.DLL
stores all strings as Unicode. But mainly for historical reasons, the strings are typed as TCHAR*
from <tchar.h>
. Ancient versions of mCtrl supported ANSI as well as Unicode build of the library which stored the strings as ANSI. ANSI build of mCtrl is no longer supported but for consistency reason, TCHAR
type is still preferred for strings stored internally.
Some parts of MCTRL.DLL
use COM. mCtrl is a plain C project (no C++) so it sometimes means more typing (casting, method calling though the ugly macros ISomeClass_SomeMethod(self, ...)
instead of self->SomeMethod(...)
). In general we use COM only when needed and not as a default option.
Initializing COM in a DLL can be tricky. COM provides several modes (or "apertures" how MSDN calls it). And we should not prevent any application linking with us to use aperture it wishes.
Therefore we support two modes:
-
Applications which are completely COM-unaware. I.e. application who never call
CoInitialize()
or similar function. In these casesxcom_init()
performs the initialization andxcom_fini()
performs unitialization. -
Applications which play with COM. We expect the application initializes COM as it likes. But the initialization has to happen before call to
MCTRL.DLL
. I.e. the application is responsible to guarantee that any thread calling intoMCTRL.DLL
is free to safely use COM throughout lifetime of the application.
Any new code has to support both cases. All other situations are explicitly not supported.
See src/xcom.h
which provides functions to deal with COM initialization.
When designing new control, consider where its internal state can be reasonably embedded in the kind of model (e.g. a document for edit control, a table for grid control) which is worth of its own existence. As a rule of thumb, controls providing complex and rich data contents are of this nature.
If so, the control should then play the role of "view" in the MVC paradigm. (Application's dialog or its window procedure is then the "controller".)
The data model should have public functions so application can change it independently on any control. And any change in the data model should be reflected by all views (controls) where the model is installed.
The module src/viewlist.h
implements simple support for management of views sharing single data model.
-
src/misc.h
: Included (almost) everywhere. It should generally be the 1st header to include in any new internal header file. Newcomers should see what goodies it offers and use its macros and functions instead of reinventing the wheel. -
src/theme.h
: Wrapper functions forUXTHEME.DLL
. Note the functions were recently made public and are also available throughinclude/mCtrl/theme.h
to the application linking withMCTRL.DLL
. -
src/generic.h
: Functions for typical handling of some control messages. Called from multiple controls window procedures. -
src/xdraw.h
: "Better painting." In general mCtrl controls stick with GDI. But in some cases it is not sufficient because anti-aliasing (chart control) or alpha channel or other features (image view control) are needed. It is a lightweight wrapper of Direct2d or GDI+ (the former is preferred if available in run-time). -
src/module.m
: Handles initialization and uninitialization of various modules (i.e. mCtrl parts which can be initialized independently) and dependencies between them.
Q: Is there any documentation for coding new Win32API controls?
A: AFAIK, there has been no good one about this topic but some very trivial tutorials. So I tried to write something myself. See the article Custom Controls in Win32 API: The Basics and all its sequel articles. It's not complete but it's good start for newbies.
Q: What is preferred way of participating?
A: Github pull requests.
Q: What are the criteria for pull request acceptation into upstream?
A: Emphasis is on two points: New control/feature must be of general usability (control usable only for your specific application is not a subject for inclusion in upstream). And if the patch involves public API, it has to be really good in terms of ease of use, consistency, compatibility and orthogonality because any public API implies burden for future maintenance. Implementation may change quite easily but the interface has to be more stable due the compatibility requirements. But don't fear, I don't bite. If I see potential in the new feature I am usually quite helpful ;-)