forked from pyscripter/python4delphi
-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathWrapDelphi.pas
5722 lines (5107 loc) · 186 KB
/
WrapDelphi.pas
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**************************************************************************)
(* This unit is part of the Python for Delphi (P4D) library *)
(* Project home: https://github.com/pyscripter/python4delphi *)
(* *)
(* Project Maintainer: PyScripter (pyscripter@gmail.com) *)
(* Original Authors: Dr. Dietmar Budelsky (dbudelsky@web.de) *)
(* Morgan Martinet (https://github.com/mmm-experts) *)
(* Core developer: Lucas Belo (lucas.belo@live.com) *)
(* Contributors: See contributors.md at project home *)
(* *)
(* LICENCE and Copyright: MIT (see project home) *)
(**************************************************************************)
(*-----------------------------------------------------------------------------
Purpose: Provide automatic wrapping of Delphi variables utilising RTTI
Features:
Published properties and methods compiled with {$METHODINFO ON} are
handled automatically (Note that METHODINFO can be used only with Delphi7
or later, but all the other wrapping features will work with previous
versions of Delphi starting from Delphi5).
Moreover common methods and properties of
the following frequently used Delphi classes are also exported
(Note that this list is not exhaustive):
TObject (ClassName, Free, InheritsFrom)
TPersistent (Assign)
TCollection (sequence interface, Items, Count, Insert, Add, Clear)
TStrings (mapping interface, Text, Add, AddObject, Delete, IndexOf, Clear)
TComponent (Event properties, Subproperties, Owner, ComponentCount, Components)
TControl (Parent)
TWinControl (ControlCount, Controls)
TForm (Show, ShowModal, Release)
TStrings, TCollection.Items, TComponent.Components and
TWinControl.Controls are exposed as sequence/mapping interfaces.
You can also access the Screen and Application objects, and some other
constants like mrOk, mrCancel...
PyDelphiWrapper.RegisterDelphiWrapper allows the customized wrapping of
additional Delphi classes over which you do not have direct control.
PyDelphiWrapper.EventHandlers.RegisterHandler() can be used to add event handling
functionality. TNotify events are handled out-of-the-box. To handle
other types of events you need to write a TEventHandler descendent and
register the EventHandler.
A Module level function CreateComponent(ClassName, Owner) is also exported.
For this function to work, the class needs to be registered using
Classes.RegisterClass (Some classes are already pre-registered like TForm,
TApplication, TScreen, TButton, TCheckBox...).
You can subclass TForm as you would do in Delphi, but you are not able to
override the Delphi methods in Python. There is also a helper
function BindMethodsToEvents that can connect your method handlers to the
component events if you respect a specific pattern for naming your methods:
handle_ComponentName_EventName --> handle_Button1_OnClick
This function is especially useful when you subclass an existing Delphi form,
as the form will already have all the necessary components setup, but you'll
be missing the events to your Python code.
If you subclass Form in Python and name your class with the same name as
an existing Delphi form (that must be registered with RegisterClass),
then this class will be used to instanciate the form instead of the regular empty TForm.
class TTestForm(Form):
def __init__(self, Owner):
self.Caption = self.Caption + ' - changed by Python subclass'
self.BindMethodsToEvents() # this will connect handle_btnAdd_OnClick to btnAdd.OnClick
def handle_btnAdd_OnClick(self, Sender):
self.ListBox1.Items.Add(self.Edit1.Text)
There is also a helper method named SetProps at the TPyDelphiObject level,
allowing any wrapped object to do:
button.SetProps(Left=10, Top=20, Caption='Clickme!)
You can inspect the published properties of any wrapped object by inspecting the
__published__ property.
Note that events requiring var parameters like OnCloseQuery will provide a specific object
containing a single Value property that will hold the actual value of the parameter,
because Python does not allow modifying the parameters:
def handle_close_query(self, sender, accept):
accept.Value = False # accept = False would have not effect!
Usage:
Drop a PyDelphiWrapper component on a form, set its engine and module
properties to a PythonEngine and PythonModule.
Note that it is very important to add each wrapped Delphi unit to your uses
clause or you won't access the specific wrappers as they would not be
registered.
To make it easier, you can simply add the unit WrapDelphiVCL to your uses
clause.
Alternatively create a PyDelphiWrapper component using code,
set its engine and module properties and initialize e.g.
PyDelphiWrapper := TPyDelphiWrapper.Create(Self);
PyDelphiWrapper.Engine := PyEngine;
PyDelphiWrapper.Module := PythonModule;
PyDelphiWrapper.Initialize; // Should only be called if PyDelphiWrapper is created at run time
Use PyDelphiWrapper.Wrap to wrap a given object
var
p : PPyObject;
begin
// Wrap the Form itself.
p := PyDelphiWrapper.Wrap(Form1);
PythonModule.SetVar( 'Form', p );
PyEngine.Py_DecRef(p);
end;
Look at the demos 31 and 32 for further examples of usage.
History:
1.00 24-Feb-2005 Kiriakos Vlahos
Initial release
1.01 12-May-2005 Morgan Martinet
- inherit TPyDelphiWrapper from TPythonClient
- removed type TPythonTypeCustomCreate as TPythonType now has a new attribute GenerateCreateFunction
the custom types didn't use the former CanCreate property and thus CreateComponent conflicted
with the function exposed by TPyDelphiWrapper.
- changed the boolean parameter of TObjectToPyObject into an enumeration, to help understand
when you read the code, if the object is owned or not.
- added property __bound__ to TPyDelphiObject, to know if the wrapper is still bound to the instance.
- added property __owned__ to the base Delphi wrapper, to know if the wrapper owns the underlying
object or not.
- added SqAssItem and SqSlice to the TStringsWrapper
- moved method Show of the Form wrapper to the Control wrapper
- added Exception's message to the exception raised in TPyDelphiMethodObject.Call
- fixed bug in Collection iterator (method Iter and IterNext were swapped)
- refactored iterators with a common base class
- added automatic support of sequences and iterators if the wrapper overrides the GetContainerAccessClass method.
- refactored index checking
- implemented sequence protocol with more collections
- used new class method SetupType for configuring the services exposed by the python type
allowing better polymorphism.
- TStrings wrapper now inherits from TPersistent wrapper.
- Fixed bug in TStrings.SqItem that returned a string instead of a wrapped TObject.
- Changed DelphiObject member field to a property and redefined its type for each subclass,
in order to avoid casting DelphiObject each time need to invoke an attribute.
This was too much error prone, especially with Copy&Paste.
- Added various helper functions to check parameter types.
- Allowed events with TObject subclasses, using an interfaces IFreeNotification/IFreeNotificationSubscriber
- Added helper class TFreeNotificationImpl handling the details of the IFreeNotification implementation.
- Fixed bug when accessing attributes of an unbound wrapper
- Renamed TPyStringsObject into TPyDelphiStrings for consistency
- Changed the TForm wrapper into a TCustomForm wrapper
- Added helper methods ToTuple, ToList to any wrapper supporting sequences (TStrings, TComponent...)
- Added Objects property to TStrings
- TStrings can be accessed with an integer to get the string item or with a key string to get
the associated object.
1.02 23-May-2005 Morgan Martinet
- Wrapped TBasicAction
- Wrapped TActionList
- Wrapped Screen object
- Defined TModalResult constants
- fixed bug when exiting application with Python events still attached -> crash
- fixed bug in event handlers: when destroying an event, only set the handler to nil if it is our handler!
- created TEventHandlers collection
- Moved code that gets/sets events outside of GetAttrO/SetAttrO into TEventHandlers
- return the associated Python handler of an event (in TPyDelphiObject.GetAttrO)
1.03 30-May-2005 Morgan Martinet
- Wrapped TMonitor
- Wrapped TApplication
- The wrappers now will try to receive a free notification from the wrapped object. This will always
work with components and may work with classes that implement IFreeNotification.
- Refactored the registration of wrappers and helper types.
Now you don't have to create your TPythonType instance. This will be done automatically in the
RegisterDelphiWrapper and RegisterHelperType methods.
You can setup the new type by overriding the SetupType class method of TPyObject.
procedure RegisterDelphiWrapper(AWrapperClass : TPyDelphiObjectClass);
RegisterHelperType(APyObjectClass : TPyObjectClass);
Also, note that RegisterDelphiClass as been renamed RegisterDelphiWrapper and there's no
ne need to give the associated Delphi class, as the wrapper class will override a new
class function named DelphiObjectClass that must return the wrapped delphi class.
- Moved wrappers into new dedicated units for each Delphi VCL unit:
WrapDelphiClasses, WrapDelphiControls, WrapDelphiForms, WrapDelphiActnList
- Added a new registration system at the unit level, to allow each dedicated unit to register
the wrappers of the unit's classes.
- New way to define getters/setters by using Class methods instead of global functions,
thanks to Michiel du Toit.
1.04 30-May-2005 Morgan Martinet
- Made WrapDelphi compatible with previous versions of Delphi (below 7):
all the wrapping features are available, and only the dynamic method invocation
relying on {$METHODINFO ON} is disabled. Have to check compilation with D5 to D6.
- Allowed subclassing of components. Introduced new wrappers for TForm and TButton.
- Added new unit WrapDelphiStdCtrls
1.05 11-June-2005 Morgan Martinet
- renamed method TObjectToPyObject into Wrap
- stored default wrapper types pointers into public properties of TPyDelphiWrapper,
for immediate access (instead of doing a lookup in the list).
- added class TPyDelphiVarParameter for handling Delphi var Parameters.
- Defined event for TForm.OnCloseQuery
- Defined event for TForm.OnClose
1.06 13-June-2005 Morgan Martinet
- Created wrappers for all controls of the StdCtrls unit.
- Created wrappers for all controls of the ExtCtrls unit in new unit WrapDelphiExtCtrls.
- Added property __published__ to TPyDelphiObject, that will return the list of all published properties
of the wrapped class. This can be use to know which properties can be accessed and for documenting...
- Made Helper types visible at a module level, because Point, Rect... are helper types.
- Added wrapper for TPoint
- Implemented method TScreen.MonitorFromPoint using Point object.
1.07 25-June-2005 Morgan Martinet
- When creating an instance of a form (with a Python subclass of Form), if the Owner is Application,
then we use Application.CreateForm instead of just instanciating the metaclass, otherwise the Application
will never have a Main form.
- Started making a Python dll module hosting the Delphi wrappers.
- fixed a declaration error of the property setters in TApplication wrapper
- Added method RegisterFunction to TPyDelphiWrapper
- Wrapped api FreeConsole in WrapDelphiForms
- Added method SetProps at the TPyDelphiObject level, allowing any wrapped object to do:
button.SetProps(Left=10, Top=20, Caption='Clickme!)
- Wrapped procedure Abort
- Created new type for wrapping TRect records.
- New behaviour with forms: if you subclass Form in Python and name your class with the same name as
a Delphi form (that must be registered with RegisterClass), then this class will be used to instanciate
the form instead of the regular empty TForm.
- Added a fake get/set method to TPyDelphiObject and create get/set definitions for each published property, using
those fake methods that won't do anything, because the property value will be fetched in the GetAttr method,
before even trying to use the python properties.
This will help a lot documenting existing wrappers, using regular python tools, and it will also allow the
use of the code insight provided by the IDE.
1.08 16-July-2005 Morgan Martinet
- Added method BindMethodsToEvents to TComponent wrapper. It will allow a subclassed form
to automatically bind its controls to the form's methods, if you respect a specific naming
convention. Each method must be named like:
def handle_MyComponent_OnClick(self, sender): pass
Note that for the hooking the form's properties, you have to use a special component name "Self":
def handle_Self_OnCloseQuery(self, sender, CanClose): pass
Note that BindMethodsToEvents accepts a default parameter for specifying the expected prefix,
which defaults to "handle_".
Note that BindMethodsToEvents returns a list of tuples. Each tuple contains:
ComponentName, EventName, MethodObject
This method is especially useful if you create a base form in Delphi, using the form designer,
with no code (or not much), then you subclass this form in Python, provide events that will
be automatically be connected when you invoke BindMethodsToEvents in the __init__ handler.
- Finished cleanup of the property getters (global function --> method)
1.09 18-Dec-2005 Morgan Martinet
- Added new unit WrapDelphiWindows (to define a couple of symbols only)
- Added new unit WrapDelphiComCtrls
- Added new unit WrapDelphiGrids
- Added new unit WrapDelphiGraphics
- Added new unit WrapDelphiButtons
- Wrapped TSize
- Wrapped TCanvas, TGraphic, TBitmap, TMetaFile, TIcon, TPicture
- Wrapped TKeyPressEvent and TKeyEvent
- Made a breaking change when dealing with property sets:
now we expect a sequence of strings. Each string should have the name as the enumeration in the set.
Ex: MainForm.Anchors = ['akLeft', 'akTop']
Of course, a set property will now return a list of strings.
In the past, it would have returned an integer containing all the bits of the set,
and it would have accepted to assign either the same kind of integer value or
a string like "[akLeft, akTop]".
- Made a breaking change when dealing with property enumerations:
return a string representing its value instead of the ordinal value.
- You don't need to call explicitely RegisterClass for your registered Python types as it will be done
automatically for you in RegisterDelphiWrapper. But it is still usefull if you need to create VCL objects
that have no wrapper, using the CreateComponent helper function.
1.10 24-Feb-2006 Morgan Martinet
- Wrapped TPageControl and TTabSheet
1.11 14-Mar-2006 Morgan Martinet
- Added methods Repaint and Invalidate to the TControl wrapper
- Fixed bug when running WrapDelphi without Assertions in the compiler options
thanks to a report from Dominique Whali
- made fields fDefaultIterType and fDefaultContainerType of TPyDelphiWrapper protected
Oct-2019 PyScripter
- Major refactoring and clean-up
- In Delhi version newer than XE, enhanced RTTI is used to provide access to
methods, fields and properties. So in most cases you no longer need to
create wrapping classes.
- __published__ property was replaced with the implementation of the __dir__()
method, so that you can do for example dir(MainForm) to inspect the
methods, fields and properties of MainForm.
- Demo 31 has been updated to test/showcase some of the new features.
Apr-2020 PyScripter
- Wrapping of Records using extended RTTI
- Wrapping of Interfaces using extended RTTI (see unit tests)
2021
- FMX Wrapping by Lucas Belo
- Vcl Menu and Toolbar wrapping by PyScripter
TODO:
- Extend SetProps: if property receiving the value is a TStrings and the value a sequence,
then assign the sequence content to the TStrings.
- can we debug the Python code executed from a triggered event? Presently not, as we directly ask Python
to execute a specific callable...
- Create a simple app that just initializes Python and executes a script? To avoid having a console...
- Bug with Delphi pyd: can't change the application title, because TApplication creates its own handle
- Wrap TApplicationEvents. In fact define the events used by TApplicationEvents.
- Wrap TObjectList
- Unit Test all exposed attributes
- Wrap simple types like TMessage
- Generate Documentation from available metainformation (see __members__, ClassName...)
- Allow Wrappers to handle IFreeNotification for the wrapped object when the object does not
support it, only when the wrapper knows that it is safe enough (singleton delphi object)
- Be able to return an object containing the current event handler of any Delphi object that was hooked by Delphi,
and not by Python, as presently, if a button has a Delphi OnClick event, inspecting this event from Python
will return None.
-----------------------------------------------------------------------------*)
{$I Definition.Inc}
unit WrapDelphi;
interface
uses
SysUtils, Classes, PythonEngine, TypInfo, Types,
Variants,
{$IFNDEF FPC}
{$IFDEF EXTENDED_RTTI}
Rtti,
{$ELSE}
ObjAuto,
{$ENDIF}
{$ENDIF}
Contnrs;
Type
TObjectOwnership = (soReference, soOwned);
// forward declaration
TPyDelphiWrapper = class;
{
If you want to benefit from subscribing to events from Python when your
wrapped class does not inherit from TComponent, then you can simply
implement the IFreeNotification, store the subscriber event sink and
trigger its Notify method in your destructor.
Note that TFreeNotificationImpl does all the plumbing for you.
}
IFreeNotificationSubscriber = interface
['{F08FB6EA-3D8B-43C0-8343-77C8E06DE401}']
procedure Notify(ADeletedObject : TObject);
end;
IFreeNotification = interface
['{085FD1BB-44FC-457A-B357-4E06071BBEA5}']
procedure Subscribe(const ASubscriber: IFreeNotificationSubscriber);
procedure UnSubscribe(const ASubscriber: IFreeNotificationSubscriber);
end;
{ Helper class that handles the detail of implementing IFreeNotification.
Usage:
TMyClass = class(TInterfacedObject, IFreeNotification)
private
fFreeNotifImpl : IFreeNotification;
protected
property FreeNotifImpl : IFreeNotification read fFreeNotifImpl implements IFreeNotification;
public
constructor Create;
end;
constructor TMyClass.Create;
begin
fFreeNotifImpl := TFreeNotificationImpl.Create(Self);
end;
}
TFreeNotificationImpl = class(TInterfacedObject, IFreeNotification)
private
fSubscribers : TInterfaceList;
fOwner: TObject;
function GetSubscribers : TInterfaceList;
protected
// implementation of IFreeNotification
procedure Subscribe(const ASubscriber: IFreeNotificationSubscriber);
procedure UnSubscribe(const ASubscriber: IFreeNotificationSubscriber);
public
constructor Create(AOwner : TObject);
destructor Destroy; override;
property Owner : TObject read fOwner;
end;
{
This class helps wrappers to implement sequence and iterator protocols.
You must subclass it, override GetItem, GetSize.
If you override IndexOf, then you must override SupportsIndexOf and return True.
If you override SetItem, then you must override SupportsWrite and return True.
You can give a specific name to the container if you override the Name function.
Note that an instance of this class must belong to a single owner, if you want
to give it to another class (like a container to an iterator, then you must
clone it).
}
TContainerAccess = class
private
fContainer: TObject;
fWrapper: TPyDelphiWrapper;
protected
function Wrap(Obj : TObject; Ownership: TObjectOwnership = soReference) : PPyObject;
public
constructor Create(AWrapper : TPyDelphiWrapper; AContainer: TObject); virtual;
function Clone : TContainerAccess; virtual;
function GetItem(AIndex : Integer) : PPyObject; virtual; abstract;
function GetSize : Integer; virtual; abstract;
function IndexOf(AValue : PPyObject) : Integer; virtual;
function SetItem(AIndex : Integer; AValue : PPyObject) : Boolean; virtual;
class function ExpectedContainerClass : TClass; virtual; abstract;
class function Name : string; virtual;
class function SupportsWrite : Boolean; virtual;
class function SupportsIndexOf : Boolean; virtual;
property Container : TObject read fContainer;
property Wrapper : TPyDelphiWrapper read fWrapper;
end;
TContainerAccessClass = class of TContainerAccess;
{
Abstract sequence relying on the container access protocol.
This will help us support the VCL way to access elements,
for instance: form.Components[i]
Note that we could simply write form[i], but then we might use it for
form.Controls[i] (as Components would be the default sequence).
As the sequence supports iterators, you can also write:
for i in form: pass
for i in form.Components: pass
for i in form.Controls: pass
}
TPyDelphiContainer = class(TPyObject)
private
fContainerAccess: TContainerAccess;
fPyDelphiWrapper: TPyDelphiWrapper;
public
destructor Destroy; override;
procedure Setup(APyDelphiWrapper : TPyDelphiWrapper; AContainerAccess : TContainerAccess);
class procedure SetupType( PythonType : TPythonType ); override;
function Repr : PPyObject; override;
function Iter : PPyObject; override;
// Sequence services
function SqLength : NativeInt; override;
function SqItem( idx : NativeInt ) : PPyObject; override;
function SqAssItem( idx : NativeInt; obj : PPyObject) : Integer; override;
function SqContains( obj: PPyObject): integer; override;
// Properties
property ContainerAccess : TContainerAccess read fContainerAccess;
property PyDelphiWrapper : TPyDelphiWrapper read fPyDelphiWrapper;
end;
{
Abstract iterator relying on the container access protocol.
}
TPyDelphiIterator = class(TPyObject)
private
fPosition: Integer;
fContainerAccess: TContainerAccess;
public
destructor Destroy; override;
procedure Setup(AContainerAccess : TContainerAccess);
class procedure SetupType( PythonType : TPythonType ); override;
function Repr : PPyObject; override;
function Iter : PPyObject; override;
function IterNext : PPyObject; override;
// Properties
property Position : Integer read fPosition;
property ContainerAccess : TContainerAccess read fContainerAccess;
end;
{
Base class allowing us to implement interfaces.
}
TPyInterfacedObject = class(TPyObject, IInterface)
private
// implementation of interface IInterface
{$IFDEF FPC_HAS_CONSTREF}
function QueryInterface(constref IID: TGUID; out Obj): HResult; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
function _AddRef: Integer; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
function _Release: Integer; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
{$ELSE}
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{$ENDIF}
end;
{
PyObject wrapping TObject
Exposes published properties and methods
Also exposes the property ClassName and methods InheritesFrom and Free
Do not create TPyDelphi or its subclasses directly - Instead use
PyDelphiWrapper.Wrap
}
TPyDelphiObject = class (TPyInterfacedObject, IFreeNotificationSubscriber)
private
fDelphiObject: TObject;
fContainerAccess: TContainerAccess;
function GetContainerAccess: TContainerAccess;
procedure SetDelphiObject(const Value: TObject);
protected
function CheckBound : Boolean;
function HasContainerAccessClass : Boolean;
procedure SubscribeToFreeNotification; virtual;
procedure UnSubscribeToFreeNotification; virtual;
class function GetTypeName : string; virtual;
// Exposed Methods
function Free_Wrapper(args : PPyObject) : PPyObject; cdecl;
function InheritsFrom_Wrapper(args : PPyObject) : PPyObject; cdecl;
function ToTuple_Wrapper(args : PPyObject) : PPyObject; cdecl;
function ToList_Wrapper(args : PPyObject) : PPyObject; cdecl;
function SetProps(args, keywords : PPyObject) : PPyObject; cdecl;
function Dir_Wrapper(args: PPyObject): PPyObject; cdecl;
// Exposed Getters
function Get_ClassName(Acontext : Pointer) : PPyObject; cdecl;
function Get_Owned(Acontext : Pointer) : PPyObject; cdecl;
function Set_Owned(AValue: PPyObject; AContext: Pointer): Integer;
function Get_Bound(Acontext : Pointer) : PPyObject; cdecl;
// implementation of interface IFreeNotificationSubscriber
procedure Notify(ADeletedObject : TObject);
{$IFDEF EXTENDED_RTTI}
class function ExcludedExposedMembers(APythonType: TPythonType): TArray<string>; virtual;
class procedure ExposeMethods(AClass: TClass; NearestAncestorClass: TClass;
APythonType: TPythonType; APyDelphiWrapper: TPyDelphiWrapper;
AExcludedMethodNames: TArray<string> = nil);
class procedure ExposeFields(AClass: TClass; NearestAncestorClass: TClass;
APythonType: TPythonType; APyDelphiWrapper: TPyDelphiWrapper;
AExcludedFieldNames: TArray<string> = nil);
class procedure ExposeProperties(AClass: TClass; NearestAncestorClass: TClass;
APythonType: TPythonType; APyDelphiWrapper: TPyDelphiWrapper;
AExcludedPropertyNames: TArray<string> = nil);
class procedure ExposeIndexedProperties(AClass: TClass; NearestAncestorClass: TClass;
APythonType: TPythonType; APyDelphiWrapper: TPyDelphiWrapper;
AExcludedPropertyNames: TArray<string> = nil);
{$ENDIF EXTENDED_RTTI}
public
PyDelphiWrapper : TPyDelphiWrapper;
Owned: Boolean;
constructor Create( APythonType : TPythonType ); override;
// CreateWith raises a python TypeError 'Cannot create instances..'
// Subclasses that can be instantiated need to overwrite this method and
// a) Call the virtual constructor Create
// b) Create the pascal object and assign it to DelphiObject
constructor CreateWith(APythonType: TPythonType; args, kwds: PPyObject); override;
destructor Destroy; override;
function GetAttrO( key: PPyObject) : PPyObject; override;
function SetAttrO( key, value: PPyObject) : Integer; override;
// Objects are equal when they refer to the same DelphiObject
function Compare( obj: PPyObject) : Integer; override;
function Repr : PPyObject; override;
// automatic iterator support when the wrapper implements IContainerAccessProvider
function Iter : PPyObject; override;
// Sequence services
function SqLength : NativeInt; override;
function SqItem( idx : NativeInt ) : PPyObject; override;
function SqContains( obj: PPyObject): integer; override;
function SqAssItem( idx : NativeInt; obj : PPyObject) : Integer; override;
// Mapping services
{$IFDEF EXTENDED_RTTI}
function MpSubscript(obj: PPyObject) : PPyObject; override;
function MpAssSubscript(obj1, obj2: PPyObject) : Integer; override;
{$ENDIF EXTENDED_RTTI}
class function DelphiObjectClass : TClass; virtual;
class procedure RegisterMethods( PythonType : TPythonType ); override;
class procedure RegisterGetSets( PythonType : TPythonType ); override;
class procedure SetupType(APythonType: TPythonType ); override;
// if the class is a container (TStrings, TComponent, TCollection...),
// then return the class implementing the access to the contained items.
class function GetContainerAccessClass : TContainerAccessClass; virtual;
// creates a container access object using the class returned by GetContainerAccess.
function CreateContainerAccess : TContainerAccess; virtual;
// helper methods
function Wrap(AObject : TObject; AOwnership: TObjectOwnership = soReference) : PPyObject;
// Properties
property DelphiObject: TObject read fDelphiObject write SetDelphiObject;
property ContainerAccess : TContainerAccess read GetContainerAccess;
end;
TPyDelphiObjectClass = class of TPyDelphiObject;
{
Generic wrapper for pascal classes
Can be used from unit wrappers as follows:
PyDelphiWrapper1.RegisterDelphiWrapper(TPyClassWrapper<TMyClass>);
or at runtime (e.g. inside the FormCreate handler:
PyDelphiWrapper1.RegisterDelphiWrapper(TPyClassWrapper<TMyClass>).Initialize;
if you want your class to capable of being instantiated from python then do:
TMyClassWrapper = class(TPyClassWrapper<TMyClass>)
constructor CreateWith(APythonType: TPythonType; args, kwds: PPyObject); overload; override;
end;
constuctor TMyClassWrapper.CreateWith(APythonType: TPythonType; args, kwds: PPyObject);
begin
Create(APythonType);
DelphiObject := TMyClass.Create;
end;
PyDelphiWrapper1.RegisterDelphiWrapper(TMyClassWrapper).Initialize;
}
TPyClassWrapper<T: class> = class(TPyDelphiObject)
private
function GetDelphiObject: T;
procedure SetDelphiObject(const Value: T);
public
class function DelphiObjectClass : TClass; override;
class procedure RegisterMethods( PythonType : TPythonType ); override;
// Properties
property DelphiObject: T read GetDelphiObject write SetDelphiObject;
end;
{ This class will simply hold a Python object in its Value property.
This is required for Delphi var parameters because Python won't let you
replace a parameter value with another one, so, we will provide a container
and you'll be able to change its content. }
TPyDelphiVarParameter = class(TPyObject)
private
fValue: PPyObject;
procedure SetValue(const Value: PPyObject);
protected
// Exposed Getters
function Get_Value(Acontext : Pointer) : PPyObject; cdecl;
// Exposed Setters
function Set_Value(AValue : PPyObject; AContext : Pointer) : Integer; cdecl;
public
destructor Destroy; override;
function RichCompare( obj : PPyObject; Op : TRichComparisonOpcode) : PPyObject; override;
function Repr : PPyObject; override;
class procedure RegisterGetSets( PythonType : TPythonType ); override;
class procedure SetupType( PythonType : TPythonType ); override;
property Value : PPyObject read fValue write SetValue;
end;
{$IFDEF EXTENDED_RTTI}
{ Base class for exposing Records and Interfaces when Extended RTTI is available }
TPyRttiObject = class (TPyObject)
private
fCopy: TValue;
fAddr: Pointer;
fRttiType: TRttiStructuredType;
function GetValue: TValue; virtual; abstract;
protected
// Exposed Methods
function SetProps(args, keywords : PPyObject) : PPyObject; cdecl;
function Dir_Wrapper(args: PPyObject): PPyObject; cdecl;
public
PyDelphiWrapper : TPyDelphiWrapper;
constructor Create( APythonType : TPythonType ); override;
procedure SetAddrAndType(Address: Pointer; Typ: TRttiStructuredType);
procedure SetupFromTValue(const AValue: TValue);
function GetAttrO( key: PPyObject) : PPyObject; override;
function SetAttrO( key, value: PPyObject) : Integer; override;
property Addr: Pointer read fAddr;
property RttiType: TRttiStructuredType read fRttiType;
property Value: TValue read GetValue;
//
class procedure RegisterMethods( PythonType : TPythonType ); override;
class procedure SetupType( PythonType : TPythonType ); override;
end;
TPyPascalRecord = class(TPyRttiObject)
private
function GetValue: TValue; override;
public
function Repr : PPyObject; override;
class procedure SetupType( PythonType : TPythonType ); override;
end;
TPyPascalInterface = class(TPyRttiObject)
private
function GetValue: TValue; override;
public
function Repr : PPyObject; override;
class procedure SetupType( PythonType : TPythonType ); override;
end;
{$ENDIF}
TBaseEventHandler = class
private
fComponent: TObject;
public
PyDelphiWrapper : TPyDelphiWrapper;
PropertyInfo : PPropInfo;
Callable : PPyObject;
// connects to the event on creation
constructor Create(PyDelphiWrapper : TPyDelphiWrapper; Component : TObject;
PropertyInfo : PPropInfo; Callable : PPyObject); virtual;
// Disconnects from the event on destruction
destructor Destroy; override;
// Disconnects from the free notification event now
procedure Unsubscribe;
// properties
property Component : TObject read fComponent;
end;
TEventHandler = class(TBaseEventHandler)
public
// returns the type info of the supported event
class function GetTypeInfo : PTypeInfo; virtual; abstract;
end;
TEventHandlerClass = class of TEventHandler;
TEventHandlers = class
private
fItems : TObjectList;
fRegisteredClasses : TClassList;
fPyDelphiWrapper: TPyDelphiWrapper;
function GetCount: Integer;
function GetItem(AIndex: Integer): TBaseEventHandler;
function GetRegisteredClass(AIndex: Integer): TEventHandlerClass;
function GetRegisteredClassCount: Integer;
protected
function FindHandler(ATypeInfo : PTypeInfo) : TEventHandlerClass;
property RegisteredClasses[AIndex : Integer] : TEventHandlerClass read GetRegisteredClass;
property RegisteredClassCount : Integer read GetRegisteredClassCount;
public
constructor Create(APyDelphiWrapper : TPyDelphiWrapper);
destructor Destroy; override;
function Add(AEventHandler : TBaseEventHandler) : Boolean;
procedure Clear;
procedure Delete(AIndex : Integer);
function GetCallable(AComponent : TObject; APropInfo : PPropInfo) : PPyObject; overload;
function GetCallable(AComponent : TObject; const APropName : string) : PPyObject; overload;
function Link(AComponent : TObject; APropInfo : PPropInfo;
ACallable : PPyObject; out ErrMsg: string) : Boolean;
function IndexOf(AComponent : TObject; APropInfo : PPropInfo) : Integer;
procedure RegisterHandler(AEventHandlerClass : TEventHandlerClass);
function Unlink(AComponent : TObject; APropInfo : PPropInfo) : Boolean;
property Count : Integer read GetCount;
property Items[AIndex : Integer] : TBaseEventHandler read GetItem; default;
property PyDelphiWrapper : TPyDelphiWrapper read fPyDelphiWrapper;
end;
TNotifyEventHandler = class(TEventHandler)
protected
procedure DoEvent(Sender: TObject);
public
constructor Create(PyDelphiWrapper : TPyDelphiWrapper; Component : TObject;
PropertyInfo : PPropInfo; Callable : PPyObject); override;
class function GetTypeInfo : PTypeInfo; override;
end;
{ Subclass TRegisteredUnit to register your wrappers for a specific unit.
See WrapDelphiForms which will wrapp some of the classes of the Forms.pas unit.
type
TFormsRegistration = class(TRegisteredUnit)
public
function Name : string; override;
procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;
procedure DefineVars(APyDelphiWrapper : TPyDelphiWrapper); override;
end;
procedure TFormsRegistration.DefineVars(APyDelphiWrapper: TPyDelphiWrapper);
begin
inherited;
// Singletons
APyDelphiWrapper.DefineVar('Application', Application);
APyDelphiWrapper.DefineVar('Screen', Screen);
// MessageBox flags
APyDelphiWrapper.DefineVar('MB_ABORTRETRYIGNORE', MB_ABORTRETRYIGNORE);
APyDelphiWrapper.DefineVar('MB_OK', MB_OK);
end;
function TFormsRegistration.Name: string;
begin
Result := 'Forms';
end;
procedure TFormsRegistration.RegisterWrappers(APyDelphiWrapper: TPyDelphiWrapper);
begin
inherited;
APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiCustomForm);
APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiApplication);
APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiScreen);
APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiMonitor);
APyDelphiWrapper.EventHandlers.RegisterHandler(TCloseQueryEventHandler);
end;
You must also register this class to the RegisteredUnits singleton like this:
initialization
RegisteredUnits.Add(TFormsRegistration.Create);
}
TRegisteredUnit = class
public
function Name : string; virtual; abstract;
procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); virtual;
procedure DefineVars(APyDelphiWrapper : TPyDelphiWrapper); virtual;
procedure DefineFunctions(APyDelphiWrapper : TPyDelphiWrapper); virtual;
end;
{ Singleton containing all registered units.
This will be used by TPyDelphiWrapper for registering the wrappers of
classes contained in those units.
The advantage is that we can select what we want to wrap simply by
including the Wrapped units into the project, and thus avoid code bloating
if we don't need those units.
}
TRegisteredUnits = class
private
fItems : TObjectList;
function GetCount: Integer;
function GetItem(AIndex: Integer): TRegisteredUnit;
public
constructor Create;
destructor Destroy; override;
procedure Add(ARegisteredModule : TRegisteredUnit);
property Count : Integer read GetCount;
property Items[AIndex : Integer] : TRegisteredUnit read GetItem; default;
end;
{
The main component of this unit.
Method Wrap wraps Delphi objects into Python objects
Method RegisterDelphiWrapper can be used to extend its functionality.
Method EventHandlers.RegisterHandler can be used to add event handling functionality
}
{$IF not Defined(FPC) and (CompilerVersion >= 23)}
[ComponentPlatformsAttribute(pidSupportedPlatforms)]
{$IFEND}
TPyDelphiWrapper = class(TEngineClient, IFreeNotificationSubscriber)
private
// Stores Delphi class registration information
fClassRegister : TObjectList;
// Stores registration for Helper Types (do not correspond to Delphi classes)
fHelperClassRegister : TStringList;
// Stores Created Event Handlers
fEventHandlerList : TEventHandlers;
// Stores created exposed class members
fExposedMembers: TObjectList;
fVarParamType: TPythonType;
{$IFNDEF FPC}
fDelphiMethodType: TPythonType;
{$ENDIF}
{$IFDEF EXTENDED_RTTI}
fRecordType: TPythonType;
fInterfaceType: TPythonType;
{$ENDIF}
// Exposed Module level function CreateComponent(ComponentClass, Owner)
function CreateComponent( pself, args : PPyObject ) : PPyObject; cdecl;
// Implementation of interface IFreeNotificationSubscriber
procedure Notify(ADeletedObject : TObject);
protected
FModule : TPythonModule;
fDefaultIterType: TPythonType;
fDefaultContainerType: TPythonType;
procedure CreateWrappers; virtual;
procedure CreateModuleVars; virtual;
procedure CreateModuleFunctions; virtual;
procedure SetEngine(Value : TPythonEngine ); override;
procedure SetModule(const Value: TPythonModule);
procedure Notification( AComponent: TComponent;
Operation: TOperation); override;
procedure ModuleReady(Sender : TObject); override;
procedure UnsubscribeFreeNotifications;
procedure CreatePyFunc(AModule : TPythonModule; AMethodDef : PPyMethodDef);
public
constructor Create( AOwner : TComponent ); override;
destructor Destroy; override;
procedure Initialize; override;
procedure Finalize; override;
procedure DefineVar(const AName : string; const AValue : Variant); overload;
procedure DefineVar(const AName : string; AValue : TObject; AOwnership: TObjectOwnership = soReference); overload;
procedure DefineVar(const AName : string; AValue : TClass); overload;
function RegisterDelphiWrapper(AWrapperClass : TPyDelphiObjectClass): TPythonType;
function RegisterHelperType(APyObjectClass : TPyObjectClass) : TPythonType;
function RegisterFunction(AFuncName : PAnsiChar; AFunc : PyCFunction; ADocString : PAnsiChar ): PPyMethodDef; overload;
function RegisterFunction(AFuncName : PAnsiChar; AFunc : TDelphiMethod; ADocString : PAnsiChar ): PPyMethodDef; overload;
function GetHelperType(const TypeName : string) : TPythonType;
// Function that provides a Python object wrapping an object
function Wrap(AObj : TObject; AOwnership: TObjectOwnership = soReference) : PPyObject;
function WrapClass(AClass: TClass): PPyObject;
{$IFDEF EXTENDED_RTTI}
// Functions that provides a Python object wrapping a record
// The first overload wraps the record itself and the record needs to be kept alive.
// The second overload wraps a copy of the record contained in a TValue
function WrapRecord(Address: Pointer; Typ: TRttiStructuredType): PPyObject; overload;
function WrapRecord(const AValue: TValue): PPyObject; overload;
// Function that provides a Python object wrapping an interface
// Note the the interface must be compiled in {$M+} mode and have a guid
// The interface will be kept alive as long as python has areference to it.
// Usage: WrapInterface(TValue.From(YourInterfaceReference))
function WrapInterface(const IValue: TValue): PPyObject;
procedure DefineVar(const AName: string; AValue: TValue); overload;
{$ENDIF}
// properties
property EventHandlers : TEventHandlers read fEventHandlerList;
// Helper types
property DefaultContainerType : TPythonType read fDefaultContainerType;
property DefaultIterType : TPythonType read fDefaultIterType;
{$IFNDEF FPC}
property DelphiMethodType : TPythonType read fDelphiMethodType;
{$ENDIF}
property VarParamType : TPythonType read fVarParamType;
published
property Module : TPythonModule read FModule write SetModule;
end;
{$IFDEF EXTENDED_RTTI}
// Documentation hook interface
// Implement to customize the creation of docstrings for exposed class members
IDocServer = interface
['{4AF0D319-47E9-4F0A-9C71-97B8CBB559FF}']
function ReadTypeDocStr(ATypeInfo: PTypeInfo; out ADocStr: string): Boolean;
function ReadMemberDocStr(AMember: TRttiMember; out ADocStr: string): Boolean;
procedure Initialize;
procedure Finalize;
function Initialized: Boolean;
end;
var
PyDocServer: IDocServer = nil;
{$ENDIF}
{ Singletons }
function RegisteredUnits : TRegisteredUnits;
function GlobalDelphiWrapper: TPyDelphiWrapper;
{ Helper Functions }
function CheckIndex(AIndex, ACount : Integer; const AIndexName : string = 'Index') : Boolean;
function CheckIntAttribute(AAttribute : PPyObject; const AAttributeName : string; out AValue : Integer) : Boolean;
function CheckFloatAttribute(AAttribute : PPyObject; const AAttributeName : string; out AValue : Double) : Boolean;
function CheckBoolAttribute(AAttribute : PPyObject; const AAttributeName : string; out AValue : Boolean) : Boolean;
function CheckStrAttribute(AAttribute : PPyObject; const AAttributeName : string; out AValue : string) : Boolean;
function CheckObjAttribute(AAttribute : PPyObject; const AAttributeName : string;
AExpectedClass : TClass;
out AValue : TObject) : Boolean;
function CheckCallableAttribute(AAttribute : PPyObject; const AAttributeName : string) : Boolean;
function CheckEnum(const AEnumName : string; AValue, AMinValue, AMaxValue: Integer) : Boolean;
function CreateVarParam(PyDelphiWrapper : TPyDelphiWrapper; const AValue: Variant) : PPyObject; overload;
function CreateVarParam(PyDelphiWrapper : TPyDelphiWrapper; AObject: TObject) : PPyObject; overload;
function CreateVarParam(PyDelphiWrapper: TPyDelphiWrapper; AClass: TClass): PPyObject; overload;
function SetToPython(ATypeInfo: PTypeInfo; AValue : Integer) : PPyObject; overload;
function SetToPython(APropInfo: PPropInfo; AValue : Integer) : PPyObject; overload;
function SetToPython(AInstance: TObject; APropInfo: PPropInfo) : PPyObject; overload;
function PythonToSet(APropInfo: PPropInfo; ASet : PPyObject) : Integer; overload;
function PythonToSet(ATypeInfo: PTypeInfo; ASet : PPyObject) : Integer; overload;
function SupportsFreeNotification(AObject : TObject) : Boolean;
procedure RaiseNotifyEvent(PyDelphiWrapper : TPyDelphiWrapper; ACallable : PPyObject; Sender: TObject);
{Sets mulptiple properties of PyObject from keywords argument}
function SetProperties(PyObject: PPyObject; keywords: PPyObject): PPyObject;
function ValidateClassRef(PyValue: PPyObject; RefClass: TClass;
out ClassRef: TClass; out ErrMsg: string): Boolean;
procedure InvalidArguments(const MethName, ErrMsg : string);
{$IFDEF EXTENDED_RTTI}
function CreateVarParam(PyDelphiWrapper : TPyDelphiWrapper;
const AValue: TValue) : PPyObject; overload;
function SimpleValueToPython(const Value: TValue;
out ErrMsg: string): PPyObject;
function TValueToPyObject(const Value: TValue;
DelphiWrapper: TPyDelphiWrapper; out ErrMsg: string): PPyObject;
function PyObjectToTValue(PyArg : PPyObject; ArgType: TRttiType;
out Arg: TValue; out ErrMsg: string): Boolean;