1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! The COM/ActiveX shim, so legacy Windows hosts can embed the Denise control.
//!
//! VB6, MFC, Delphi and WinForms all reach a control the same way: a class id in
//! the registry, a DLL that answers `DllGetClassObject`, and an object
//! implementing the OLE control interfaces. [`denise_win32`] already provides the
//! window such an object would host; this crate is the wrapper around it.
//!
//! # Status
//!
//! The server is here: the four `Dll*` exports, a class factory, and a control
//! implementing `IOleObject`, `IOleInPlaceObject`, `IOleWindow`, `IOleControl`,
//! `IPersistStreamInit`, `IDispatch`, `IViewObject2` and the connection point
//! that carries its events. A container can instantiate it, site it, activate it
//! in place — at which point it creates a real [`denise_win32`] child window —
//! script it by name, sink its events, and tear it down again.
//!
//! It can also be asked to draw without any of that, which is what a form editor
//! does: `IViewObject2::Draw` renders the tree from the current property values
//! into whatever device context the container passes, with no site and no
//! window. Without it a control dropped on a form is a blank rectangle until the
//! form runs. The geometry that decides where the picture goes is in
//! [`view`], outside `cfg(windows)` with the rest of the arithmetic.
//!
//! `registry`, `himetric` and `dispatch` are the halves that can be tested
//! without Windows, and they are also the halves that most often go wrong: a
//! control fails to appear in a host's toolbox for one of about four reasons, all
//! of them a missing or wrong registry value, and none of them producing an error
//! anywhere. So those lists are data, and the tests check them as data.
//!
//! # Scripting it
//!
//! ```text
//! $panel = New-Object -ComObject Denise.Panel
//! $panel.Caption = "Hei"
//! $panel.Caption
//! ```
//!
//! That works because there is a type library now. Hosts that bind names late —
//! VBScript, JScript, VB6 through an `Object` variable, MFC's
//! `COleDispatchDriver`, every OLE container — never needed one and are unchanged.
//! PowerShell did: it builds its member table from type information and will not
//! ask for a name it has not been told about. See the `typelib` module for what that took
//! and what was tried first.
//!
//! The surface is still short. A type library makes each member *discoverable*,
//! which is a reason to have fewer good ones rather than a licence to add more.
//!
//! | Member | Dispid | |
//! |---|---|---|
//! | `Text` | 1 | property, read/write — the field's contents |
//! | `Caption` | 2 | property, read/write — the heading |
//! | `Enabled` | 3 | property, read/write — whether the field and button take input |
//! | `Refresh` | 4 | method — repaint everything |
//! | `Change` | 1 | event — somebody typed in the field |
//! | `Click` | -600 | event — the button was pressed (`DISPID_CLICK`) |
//!
//! ```text
//! $panel = New-Object -ComObject Denise.Panel
//! $panel.Caption = "Hei"
//! $panel.Caption
//! ```
//!
//! Events arrive through a connection point. The library names
//! `DDenisePanelEvents` as the class's default source, so a host that reads it can
//! wire them up by itself — `WithEvents` in VB6, `Register-ObjectEvent` in
//! PowerShell. By hand it is [`DIID_DENISE_PANEL_EVENTS`] passed to
//! `IConnectionPointContainer::FindConnectionPoint`, then advise an object
//! implementing `IDispatch`: there is no vtable to match and nothing to compile
//! against, only `Invoke` with one of the two dispids above.
//!
//! # Safe for scripting
//!
//! The control claims it, through `IObjectSafety` and through the two component
//! categories in the registry, because hosts are split on which one they ask.
//!
//! The claim is worth stating precisely, since claiming it carelessly is how
//! ActiveX earned its reputation. The scriptable surface is the six rows above:
//! two strings, a boolean and a repaint. Nothing in it opens a file, spawns a
//! process, reads the registry, resolves a host name, or takes a pointer or a
//! window handle from the caller — and `Load` reads nothing, so untrusted *data*
//! has nothing to be untrusted with. A script that drives this control as far as
//! it goes has changed some text on a panel.
//!
//! That is a claim about the surface as it stands. Add a member that reaches
//! outside the control and it has to be argued again rather than inherited; the
//! [`safety`] module is where the argument lives, next to the code that makes it.
//!
//! # What is not here yet
//!
//! **A form editor that has actually hosted it.** The two pieces one needs are
//! both here — a type library to read and a design-time view to draw — and both
//! are checked on every push. Neither has been in front of VB6 or an MFC dialog
//! editor.
//!
//! # What has been verified
//!
//! On Windows 11 ARM64: registered with `regsvr32`, instantiated through
//! `CoCreateInstance`, sited, activated in place, and rendering — with text typed
//! into it, including AltGr and dead keys. Then scripted: `Caption`, `Text` and
//! `Enabled` set and read **by name** through `GetIDsOfNames` and `Invoke`, a sink
//! advised on the connection point, and `Change` and `Click` arriving at it.
//!
//! The design-time view too, out of the registered server and before the control
//! was sited: a card inset from the frame with the heading inside it, printed as
//! text by `examples/host.rs` because that path never reaches a screen. What that
//! adds to the unit tests is the registry — they construct the control directly,
//! so only this says the *installed* DLL exposes `IViewObject2` at all.
//!
//! And from PowerShell, which is the host that needed the type library:
//!
//! ```text
//! TypeName: System.__ComObject#{4c5148ff-09f3-4c34-9b77-00c850e1f940}
//!
//! Name MemberType Definition
//! ---- ---------- ----------
//! Refresh Method void Refresh ()
//! Caption Property string Caption () {get} {set}
//! Enabled Property bool Enabled () {get} {set}
//! Text Property string Text () {get} {set}
//! ```
//!
//! Worth reading closely, because it confirms four separate things: the class
//! points at the dispinterface, `Refresh` returns `VT_VOID` rather than the
//! `VT_EMPTY` that once made a host unwrap a null, each property was reassembled
//! from the two entries a library stores it as, and the types survived.
//!
//! The part worth naming is the re-entrancy. The click handler assigns to
//! `Caption` and the change handler reads `Text`, both from inside the event the
//! control raised — a few hundred round trips over eighteen clicks and every
//! keystroke of a sentence, with the control's `RefCell` borrowed around all of
//! them.
//!
//! `examples/host.rs` is the container that did it, and it goes through the
//! registry rather than around it, so what it proves is the *registered* server.
//!
//! # Registering it
//!
//! ```text
//! regsvr32 denise_activex.dll
//! regsvr32 /u denise_activex.dll
//! ```
//!
//! Registration also writes `denise_activex.tlb` beside the DLL and registers it.
//! The registry records that file's full path, so **the two have to stay
//! together** — moving the DLL and re-registering is fine; moving it and not
//! re-registering leaves a library nobody can load.
//!
//! The class id, the library id and the two interface ids are generated once and
//! never change: a host stores them in a form file or a compiled binary, so a new
//! one on every build would break every project that ever embedded the control.
pub use DIID_DENISE_PANEL_EVENTS;
pub use DenisePanel;
pub use ;
pub use ;
/// Compiles the examples in this crate's README, so they cannot drift from the API
/// they claim to demonstrate. Never built except under `cargo test --doc`.
;