1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** All rights reserved.
|
---|
5 | ** Contact: Nokia Corporation ([email protected])
|
---|
6 | **
|
---|
7 | ** This file is part of the documentation of the Qt Toolkit.
|
---|
8 | **
|
---|
9 | ** $QT_BEGIN_LICENSE:FDL$
|
---|
10 | ** Commercial Usage
|
---|
11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
13 | ** Software or, alternatively, in accordance with the terms contained in a
|
---|
14 | ** written agreement between you and Nokia.
|
---|
15 | **
|
---|
16 | ** GNU Free Documentation License
|
---|
17 | ** Alternatively, this file may be used under the terms of the GNU Free
|
---|
18 | ** Documentation License version 1.3 as published by the Free Software
|
---|
19 | ** Foundation and appearing in the file included in the packaging of this
|
---|
20 | ** file.
|
---|
21 | **
|
---|
22 | ** If you have questions regarding the use of this file, please contact
|
---|
23 | ** Nokia at [email protected].
|
---|
24 | ** $QT_END_LICENSE$
|
---|
25 | **
|
---|
26 | ****************************************************************************/
|
---|
27 |
|
---|
28 | /*!
|
---|
29 | \page activeqt-dotnet.html
|
---|
30 | \title Dot Net Example (ActiveQt)
|
---|
31 |
|
---|
32 | The Dot Net example demonstrates how Qt objects can be used in a
|
---|
33 | .NET environment, and how .NET objects can be used in a Qt
|
---|
34 | environment.
|
---|
35 |
|
---|
36 | If you need to combine Qt and Win Forms widgets in the same
|
---|
37 | application, you might want to use the higher-level
|
---|
38 | \l{QtWinForms Solution} instead.
|
---|
39 |
|
---|
40 | Contents:
|
---|
41 |
|
---|
42 | \tableofcontents
|
---|
43 |
|
---|
44 | \section1 Qt vs. .NET
|
---|
45 |
|
---|
46 | Qt is a C++ library and is compiled into traditional, native
|
---|
47 | binaries that make full use of the performance provided by the
|
---|
48 | runtime environment.
|
---|
49 |
|
---|
50 | One of the key concepts of .NET is the idea of "intermediate language
|
---|
51 | code" - the source code is compiled into a bytecode format, and at
|
---|
52 | runtime, that bytecode is executed in a virtual machine - the \e
|
---|
53 | {Common Language Runtime} (CLR).
|
---|
54 |
|
---|
55 | Another key concept is that of \e {managed code}. This is essentially
|
---|
56 | intermediate language code written in such a way that the CLR can take
|
---|
57 | care of the memory management, i.e. the CLR will do automatic garbage
|
---|
58 | collection, so the application code does not need to explicitly free
|
---|
59 | the memory for unused objects.
|
---|
60 |
|
---|
61 | The MS compilers for C# and VB.NET will only produce managed
|
---|
62 | code. Such programs cannot directly call normal, native functions
|
---|
63 | or classes. \footnote The .NET framework provides Platform Invocation
|
---|
64 | Services - P/Invoke - that enable managed code to call native C (not
|
---|
65 | C++) functions located in DLLs directly. The resulting application
|
---|
66 | then becomes partially unmanaged.\endfootnote
|
---|
67 |
|
---|
68 | The MS C++ compiler for .NET on the other hand, can produce both
|
---|
69 | normal and managed code. To write a C++ class that can be compiled
|
---|
70 | into managed code, the developer must flag the class as managed using
|
---|
71 | the \c __gc keyword, and restrict the code to only use the subset of
|
---|
72 | C++ known as "Managed Extensions for C++", or MC++ for short. The
|
---|
73 | advantage is that MC++ code can freely call and use normal C++
|
---|
74 | functions and classes. And it also works the other way around: normal
|
---|
75 | C++ code can call managed functions and use managed classes (e.g. the
|
---|
76 | entire .NET framework class library), including managed functions and
|
---|
77 | classes implemented in C# or VB.NET. This feature of mixing managed
|
---|
78 | and normal C++ code immensely eases the interoperability with .NET,
|
---|
79 | and is by Microsoft referred to as the "It Just Works" (IJW) feature.
|
---|
80 |
|
---|
81 | This document demonstrates two different ways of integrating normal
|
---|
82 | C++ code (that uses Qt) with managed .NET code. First, the manual way
|
---|
83 | is presented, which includes using a thin MC++ wrapper class around
|
---|
84 | the normal Qt/C++ class. Then, the automated way is presented, which
|
---|
85 | utilizes the ActiveQt framework as a generic bridge. The advantage of
|
---|
86 | the first method is that it gives the application developer full
|
---|
87 | control, while the second method requires less coding and relieves the
|
---|
88 | developer of dealing with the conversion between managed and normal
|
---|
89 | data objects.
|
---|
90 |
|
---|
91 | The impatient reader, who right away wants to see a QPushButton
|
---|
92 | and a custom Qt widget (\l{activeqt/multiple}{QAxWidget2}) run in
|
---|
93 | a .NET GUI application is referred to the example directory of
|
---|
94 | ActiveQt. It contains the result of this walkthrough using both
|
---|
95 | C# and VB.NET, created with Visual Studio .NET (not 2003).
|
---|
96 | Load \c {examples/dotnet/walkthrough/csharp.csproj},
|
---|
97 | \c {examples/dotnet/walkthrough/vb.vbproj}
|
---|
98 | or \c {examples/dotnet/wrapper/wrapper.sln} into the IDE and run
|
---|
99 | the solution.
|
---|
100 |
|
---|
101 | \bold{Remark:} You will notice that in the generated code the following line is
|
---|
102 | commented out:
|
---|
103 |
|
---|
104 | \snippet doc/src/snippets/code/doc_src_examples_activeqt_dotnet.qdoc 0
|
---|
105 |
|
---|
106 | This line is regenerated without comment whenever you change the
|
---|
107 | dialog, in which case you have to comment it out again to be able
|
---|
108 | to run the project. This is a bug in the original version of
|
---|
109 | Visual Studio.NET, and is fixed in the 2003 edition.
|
---|
110 |
|
---|
111 | \section1 Walkthrough: .NET interop with MC++ and IJW
|
---|
112 |
|
---|
113 | Normal C++ classes and functions can be used from managed .NET code by
|
---|
114 | providing thin wrapper classes written in MC++. The wrapper class will
|
---|
115 | take care of forwarding the calls to the normal C++ functions or
|
---|
116 | methods, and converting parameter data as necessary. Since the wrapper
|
---|
117 | class is a managed class, it can be used without further ado in any
|
---|
118 | managed .NET application, whether written in C#, VB.NET, MC++ or other
|
---|
119 | managed programming language.
|
---|
120 |
|
---|
121 | \snippet examples/activeqt/dotnet/wrapper/lib/worker.h 0
|
---|
122 |
|
---|
123 | The Qt class has nothing unusual for Qt users, and as even the Qt
|
---|
124 | specialities like \c Q_PROPERTY, \c slots and \c signals are
|
---|
125 | implemented with straight C++ they don't cause any trouble when
|
---|
126 | compiling this class with any C++ compiler.
|
---|
127 |
|
---|
128 | \snippet examples/activeqt/dotnet/wrapper/lib/networker.h 0
|
---|
129 |
|
---|
130 | The .NET wrapper class uses keywords that are part of MC++ to indicate
|
---|
131 | that the class is managed/garbage collected (\c {__gc}), and that \c
|
---|
132 | StatusString should be accessible as a property in languages that
|
---|
133 | support this concept (\c {__property}). We also declare an event
|
---|
134 | function \c statusStringChanged(String*) (\c {__event}), the
|
---|
135 | equivalent of the respective signal in the Qt class.
|
---|
136 |
|
---|
137 | Before we can start implementing the wrapper class we need a way to
|
---|
138 | convert Qt's datatypes (and potentionally your own) into .NET
|
---|
139 | datatypes, e.g. \c QString objects need to be converted into objects
|
---|
140 | of type \c {String*}.
|
---|
141 |
|
---|
142 | When operating on managed objects in normal C++ code, a little extra
|
---|
143 | care must be taken because of the CLR's garbage collection. A normal
|
---|
144 | pointer variable should not \footnote Indeed, the compiler will in
|
---|
145 | many cases disallow it. \endfootnote be used to refer to a managed
|
---|
146 | object. The reason is that the garbage collection can kick in at any
|
---|
147 | time and move the object to another place on the heap, leaving you
|
---|
148 | with an invalid pointer.
|
---|
149 |
|
---|
150 | However, two methods are provided that solves this problem easily. The
|
---|
151 | first is to use a \e pinned pointer, i.e. declare the pointer variable
|
---|
152 | with the \c __pin keyword. This guarantees that the object pointed to
|
---|
153 | will not be moved by the garbage collector. It is recommended that
|
---|
154 | this method not be used to keep a references to managed objects for a
|
---|
155 | long time, since it will decrease the efficiency of the garbage
|
---|
156 | collector. The second way is to use the \c gcroot smartpointer
|
---|
157 | template type. This lets you create safe pointers to managed
|
---|
158 | objects. E.g. a variable of type \c gcroot<String> will always point
|
---|
159 | to the String object, even if it has been moved by the garbage
|
---|
160 | collector, and it can be used just like a normal pointer.
|
---|
161 |
|
---|
162 | \snippet examples/activeqt/dotnet/wrapper/lib/tools.cpp 0
|
---|
163 | \codeline
|
---|
164 | \snippet examples/activeqt/dotnet/wrapper/lib/tools.cpp 1
|
---|
165 |
|
---|
166 | The convertor functions can then be used in the wrapper class
|
---|
167 | implementation to call the functions in the native C++ class.
|
---|
168 |
|
---|
169 | \snippet examples/activeqt/dotnet/wrapper/lib/networker.cpp 0
|
---|
170 | \codeline
|
---|
171 | \snippet examples/activeqt/dotnet/wrapper/lib/networker.cpp 1
|
---|
172 |
|
---|
173 | The constructor and destructor simply create and destroy the Qt
|
---|
174 | object wrapped using the C++ operators \c new and \c delete.
|
---|
175 |
|
---|
176 | \snippet examples/activeqt/dotnet/wrapper/lib/networker.cpp 2
|
---|
177 |
|
---|
178 | The netWorker class delegates calls from the .NET code to the native
|
---|
179 | code. Although the transition between those two worlds implies a small
|
---|
180 | performance hit for each function call, and for the type conversion,
|
---|
181 | this should be negligible since we are anyway going to run within the
|
---|
182 | CLR.
|
---|
183 |
|
---|
184 | \snippet examples/activeqt/dotnet/wrapper/lib/networker.cpp 3
|
---|
185 |
|
---|
186 | The property setter calls the native Qt class before firing the
|
---|
187 | event using the \c __raise keyword.
|
---|
188 |
|
---|
189 | This wrapper class can now be used in .NET code, e.g. using C++, C#,
|
---|
190 | Visual Basic or any other programming language available for .NET.
|
---|
191 |
|
---|
192 | \snippet examples/activeqt/dotnet/wrapper/main.cs 0
|
---|
193 | \snippet examples/activeqt/dotnet/wrapper/main.cs 1
|
---|
194 | \snippet examples/activeqt/dotnet/wrapper/main.cs 2
|
---|
195 | \snippet examples/activeqt/dotnet/wrapper/main.cs 3
|
---|
196 |
|
---|
197 | \section1 Walkthrough: .NET/COM Interop with ActiveQt
|
---|
198 |
|
---|
199 | Fortunately .NET provides a generic wrapper for COM objects, the
|
---|
200 | \e {Runtime Callable Wrapper} (RCW). This RCW is a proxy for the
|
---|
201 | COM object and is generated by the CLR when a .NET Framework client
|
---|
202 | activates a COM object. This provides a generic way to reuse COM
|
---|
203 | objects in a .NET Framework project.
|
---|
204 |
|
---|
205 | Making a QObject class into a COM object is easily achieved with
|
---|
206 | ActiveQt and demonstrated in the QAxServer examples (e.g., the
|
---|
207 | \l{activeqt/simple}{Simple} example). The walkthrough will use
|
---|
208 | the Qt classes implemented in those examples, so the first thing
|
---|
209 | to do is to make sure that those examples have been built
|
---|
210 | correctly, e.g. by opening the
|
---|
211 | \l{qaxserver-demo-multiple.html}{demonstration pages} in Internet
|
---|
212 | Explorer to verify that the controls are functional.
|
---|
213 |
|
---|
214 | \section2 Starting a Project
|
---|
215 |
|
---|
216 | Start Visual Studio.NET, and create a new C# project for writing a
|
---|
217 | Windows application. This will present you with an empty form in
|
---|
218 | Visual Studio's dialog editor. You should see the toolbox, which
|
---|
219 | presents you with a number of available controls and objects in
|
---|
220 | different categories. If you right-click on the toolbox it allows
|
---|
221 | you to add new tabs. We will add the tab "Qt".
|
---|
222 |
|
---|
223 | \section2 Importing Qt Widgets
|
---|
224 |
|
---|
225 | The category only has a pointer tool by default, and we have to add
|
---|
226 | the Qt objects we want to use in our form. Right-click on the empty
|
---|
227 | space, and select "Customize". This opens a dialog that has two
|
---|
228 | tabs, "COM Components" and ".NET Framework Components". We used
|
---|
229 | ActiveQt to wrap QWidgets into COM objects, so we select the "COM
|
---|
230 | Components" page, and look for the classes we want to use, e.g.
|
---|
231 | "QPushButton" and "QAxWidget2".
|
---|
232 |
|
---|
233 | When we select those widgets and close the dialog the two widgets
|
---|
234 | will now be available from the toolbox as grey squares with their
|
---|
235 | name next to it \footnote Icons could be added by modifying the
|
---|
236 | way the controls register themselves. \endfootnote.
|
---|
237 |
|
---|
238 | \section2 Using Qt Widgets
|
---|
239 |
|
---|
240 | We can now add an instance of QAxWidget2 and a QPushButton to
|
---|
241 | the form. Visual Studio will automatically generate the RCW for the
|
---|
242 | object servers. The QAxWidget2 instance takes most of the upper
|
---|
243 | part of the form, with the QPushButton in the lower right corner.
|
---|
244 |
|
---|
245 | In the property editor of Visual Studio we can modify the properties
|
---|
246 | of our controls - QPushButton exposes the \c QWidget API and has many
|
---|
247 | properties, while QAxWidget2 has only the Visual Studio standard
|
---|
248 | properties in addition to its own property "lineWidth" in the
|
---|
249 | "Miscellaneous" category. The objects are named "axQPushButton1" and
|
---|
250 | "axQAxWidget21", and since especially the last name is a bit
|
---|
251 | confusing we rename the objects to "resetButton" and "circleWidget".
|
---|
252 |
|
---|
253 | We can also change the Qt properties, e.g. set the "text" property
|
---|
254 | of the \c resetButton to "Reset", and the "lineWidth" property of the
|
---|
255 | \c circleWidget to 5. We can also put those objects into the layout
|
---|
256 | system that Visual Studio's dialog editor provides, e.g. by setting
|
---|
257 | the anchors of the \c circleWidget to "Left, Top, Right, Bottom", and
|
---|
258 | the anchors of the \c resetButton to "Bottom, Right".
|
---|
259 |
|
---|
260 | Now we can compile and start the project, which will open a user
|
---|
261 | interface with our two Qt widgets. If we can resize the dialog,
|
---|
262 | the widgets will resize appropriately.
|
---|
263 |
|
---|
264 | \section2 Handling Qt Signals
|
---|
265 |
|
---|
266 | We will now implement event handlers for the widgets. Select the
|
---|
267 | \c circleWidget and select the "Events" page in the property
|
---|
268 | editor. The widget exposes events because the QAxWidget2 class has
|
---|
269 | the "StockEvents" attribute set in its class definition. We implement
|
---|
270 | the event handler \c circleClicked for the \c ClickEvent to increase
|
---|
271 | the line width by one for every click:
|
---|
272 |
|
---|
273 | \snippet examples/activeqt/dotnet/walkthrough/Form1.cs 0
|
---|
274 |
|
---|
275 | In general we can implement a default event handler by double
|
---|
276 | clicking on the widget in the form, but the default events for
|
---|
277 | our widgets are right now not defined.
|
---|
278 |
|
---|
279 | We will also implement an event handler for the \c clicked signal
|
---|
280 | emitted by QPushButton. Add the event handler \c resetLineWidth to
|
---|
281 | the \c clicked event, and implement the generated function:
|
---|
282 |
|
---|
283 | \snippet examples/activeqt/dotnet/walkthrough/Form1.cs 1
|
---|
284 |
|
---|
285 | We reset the property to 1, and also call the \c setFocus() slot
|
---|
286 | to simulate the user style on Windows, where a button grabs focus
|
---|
287 | when you click it (so that you can click it again with the spacebar).
|
---|
288 |
|
---|
289 | If we now compile and run the project we can click on the circle
|
---|
290 | widget to increase its line width, and press the reset button to
|
---|
291 | set the line width back to 1.
|
---|
292 |
|
---|
293 | \section1 Summary
|
---|
294 |
|
---|
295 | Using ActiveQt as a universal interoperability bridge between the
|
---|
296 | .NET world and the native world of Qt is very easy, and makes it
|
---|
297 | often unnecessary to implement a lot of handwritten wrapper classes.
|
---|
298 | Instead, the QAxFactory implementation in the otherwise completely
|
---|
299 | cross-platform Qt project provides the glue that .NET needs to to
|
---|
300 | generate the RCW.
|
---|
301 |
|
---|
302 | If this is not sufficient we can implement our own wrapper classes
|
---|
303 | thanks to the C++ extensions provided by Microsoft.
|
---|
304 |
|
---|
305 | \section2 Limitations
|
---|
306 |
|
---|
307 | All the limitations when using ActiveQt are implied when using this
|
---|
308 | technique to interoperate with .NET, e.g. the datatypes we can use
|
---|
309 | in the APIs can only be those supported by ActiveQt and COM. However,
|
---|
310 | since this includes subclasses of QObject and QWidget we can wrap
|
---|
311 | any of our datatypes into a QObject subclass to make its API
|
---|
312 | available to .NET. This has the positive side effect that the same
|
---|
313 | API is automatically available in
|
---|
314 | \l{http://qt.nokia.com/products/qsa/}{QSA}, the cross platform
|
---|
315 | scripting solution for Qt applications, and to COM clients in general.
|
---|
316 |
|
---|
317 | When using the "IJW" method, in priciple the only limitation is the
|
---|
318 | time required to write the wrapper classes and data type conversion
|
---|
319 | functions.
|
---|
320 |
|
---|
321 | \section2 Performance Considerations
|
---|
322 |
|
---|
323 | Every call from CLR bytecode to native code implies a small
|
---|
324 | performance hit, and necessary type conversions introduce an
|
---|
325 | additional delay with every layer that exists between the two
|
---|
326 | frameworks. Consequently every approach to mix .NET and native
|
---|
327 | code should try to minimize the communication necessary between
|
---|
328 | the different worlds.
|
---|
329 |
|
---|
330 | As ActiveQt introduces three layers at once - the RCW, COM and finally
|
---|
331 | ActiveQt itself - the performance penalty when using the generic
|
---|
332 | Qt/ActiveQt/COM/RCW/.NET bridge is larger than when using a
|
---|
333 | hand-crafted IJW-wrapper class. The execution speed however is still
|
---|
334 | sufficient for connecting to and modifying interactive elements in a
|
---|
335 | user interface, and as soon as the benefit of using Qt and C++ to
|
---|
336 | implement and compile performance critical algorithms into native code
|
---|
337 | kicks in, ActiveQt becomes a valid choice for making even non-visual
|
---|
338 | parts of your application accessible to .NET.
|
---|
339 |
|
---|
340 | \sa {QtWinForms Solution}
|
---|
341 | */
|
---|