-
Notifications
You must be signed in to change notification settings - Fork 36
/
MVCBr.InterfaceHelper.pas
311 lines (275 loc) · 8.23 KB
/
MVCBr.InterfaceHelper.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
{
Auth: http://stackoverflow.com/questions/39584234/how-to-obtain-rtti-from-an-interface-reference-in-delphi
}
unit MVCBr.InterfaceHelper;
interface
{$DEFINE DUNITX}
{$IFDEF VER330}
{$UNDEF DUNITX}
{$ENDIF}
uses System.Rtti, System.TypInfo, System.Generics.Collections, System.SysUtils;
{$IFNDEF BPL}
type
TInterfaceHelper = record
strict private
type
TInterfaceTypes = TDictionary<TGUID, TRttiInterfaceType>;
class var FInterfaceTypes: TInterfaceTypes;
class var Cached: Boolean;
class var Caching: Boolean;
class procedure WaitIfCaching; static;
class procedure CacheIfNotCachedAndWaitFinish; static;
class constructor Create;
class destructor Destroy;
public
// refresh cached RTTI in a background thread (eg. when new package is loaded)
class procedure RefreshCache; static;
// get RTTI from interface
class function GetType(AIntf: IInterface): TRttiInterfaceType;
overload; static;
class function GetType(AGUID: TGUID): TRttiInterfaceType; overload; static;
class function GetType(AIntfInTValue: TValue): TRttiInterfaceType;
overload; static;
// get type name from interface
class function GetTypeName(AIntf: IInterface): String; overload; static;
class function GetTypeName(AGUID: TGUID): String; overload; static;
class function GetQualifiedName(AIntf: IInterface): String;
overload; static;
class function GetQualifiedName(AGUID: TGUID): String; overload; static;
// get methods
class function GetMethods(AIntf: IInterface): TArray<TRttiMethod>; static;
class function GetMethod(AIntf: IInterface; const MethodName: String)
: TRttiMethod; static;
// Invoke method
class function InvokeMethod(AIntf: IInterface; const MethodName: String;
const Args: array of TValue): TValue; overload; static;
class function InvokeMethod(AIntfInTValue: TValue; const MethodName: String;
const Args: array of TValue): TValue; overload; static;
end;
{$ENDIF}
implementation
{$IFNDEF BPL}
uses System.Classes,
System.SyncObjs {$IFDEF DUNITX}, DUnitX.Utils{$ENDIF};
{ TInterfaceHelper }
class function TInterfaceHelper.GetType(AIntf: IInterface): TRttiInterfaceType;
var
ImplObj: TObject;
LGUID: TGUID;
LIntfType: TRttiInterfaceType;
TempIntf: IInterface;
begin
Result := nil;
try
// As far as I know, the cast will fail only when AIntf is obatined from OLE Object
// Is there any other cases?
ImplObj := AIntf as TObject;
except
// for interfaces obtained from OLE Object
Result := TRttiContext.Create.GetType(TypeInfo(System.IDispatch))
as TRttiInterfaceType;
Exit;
end;
{$IFDEF DUNITX}
// for interfaces obtained from TRawVirtualClass (for exmaple IOS & Android & Mac interfaces)
if ImplObj.ClassType.InheritsFrom(TRawVirtualClass) then
begin
LGUID := ImplObj.GetField('FIIDs').GetValue(ImplObj).AsType < TArray <
TGUID >> [0];
Result := GetType(LGUID);
end
// for interfaces obtained from TVirtualInterface
else if ImplObj.ClassType.InheritsFrom(TVirtualInterface) then
begin
LGUID := ImplObj.GetField('FIID').GetValue(ImplObj).AsType<TGUID>;
Result := GetType(LGUID);
end
else {$ENDIF}
// for interfaces obtained from Delphi object
// The code is taken from Remy Lebeau's answer at http://stackoverflow.com/questions/39584234/how-to-obtain-rtti-from-an-interface-reference-in-delphi/
begin
for LIntfType in (TRttiContext.Create.GetType(ImplObj.ClassType)
as TRttiInstanceType).GetImplementedInterfaces do
begin
if ImplObj.GetInterface(LIntfType.GUID, TempIntf) then
begin
if AIntf = TempIntf then
begin
Result := LIntfType;
Exit;
end;
end;
end;
end;
end;
class constructor TInterfaceHelper.Create;
begin
if not assigned(FInterfaceTypes) then
FInterfaceTypes := TInterfaceTypes.Create;
Cached := False;
Caching := False;
RefreshCache;
end;
class destructor TInterfaceHelper.Destroy;
begin
if assigned(FInterfaceTypes) then
FInterfaceTypes.Free;
FInterfaceTypes := nil;
end;
class function TInterfaceHelper.GetQualifiedName(AIntf: IInterface): String;
var
LType: TRttiInterfaceType;
begin
Result := string.Empty;
LType := GetType(AIntf);
if assigned(LType) then
Result := LType.QualifiedName;
end;
class function TInterfaceHelper.GetMethod(AIntf: IInterface;
const MethodName: String): TRttiMethod;
var
LType: TRttiInterfaceType;
begin
Result := nil;
LType := GetType(AIntf);
if assigned(LType) then
Result := LType.GetMethod(MethodName);
end;
class function TInterfaceHelper.GetMethods(AIntf: IInterface)
: TArray<TRttiMethod>;
var
LType: TRttiInterfaceType;
begin
Result := [];
LType := GetType(AIntf);
if assigned(LType) then
Result := LType.GetMethods;
end;
class function TInterfaceHelper.GetQualifiedName(AGUID: TGUID): String;
var
LType: TRttiInterfaceType;
begin
Result := string.Empty;
LType := GetType(AGUID);
if assigned(LType) then
Result := LType.QualifiedName;
end;
class function TInterfaceHelper.GetType(AGUID: TGUID): TRttiInterfaceType;
begin
CacheIfNotCachedAndWaitFinish;
Result := FInterfaceTypes.Items[AGUID];
end;
class function TInterfaceHelper.GetTypeName(AGUID: TGUID): String;
var
LType: TRttiInterfaceType;
begin
Result := string.Empty;
LType := GetType(AGUID);
if assigned(LType) then
Result := LType.Name;
end;
class function TInterfaceHelper.InvokeMethod(AIntfInTValue: TValue;
const MethodName: String; const Args: array of TValue): TValue;
var
LMethod: TRttiMethod;
LType: TRttiInterfaceType;
begin
LType := GetType(AIntfInTValue);
if assigned(LType) then
LMethod := LType.GetMethod(MethodName);
if not assigned(LMethod) then
raise Exception.Create('Method not found');
Result := LMethod.Invoke(AIntfInTValue, Args);
end;
class function TInterfaceHelper.InvokeMethod(AIntf: IInterface;
const MethodName: String; const Args: array of TValue): TValue;
var
LMethod: TRttiMethod;
begin
LMethod := GetMethod(AIntf, MethodName);
if not assigned(LMethod) then
raise Exception.Create('Method not found');
Result := LMethod.Invoke(TValue.From<IInterface>(AIntf), Args);
end;
class function TInterfaceHelper.GetTypeName(AIntf: IInterface): String;
var
LType: TRttiInterfaceType;
begin
Result := string.Empty;
LType := GetType(AIntf);
if assigned(LType) then
Result := LType.Name;
end;
class procedure TInterfaceHelper.RefreshCache;
var
LTypes: TArray<TRttiType>;
begin
WaitIfCaching;
FInterfaceTypes.Clear;
Cached := False;
Caching := True;
{$IFNDEF SERVICE}
TThread.CreateAnonymousThread(
procedure
var
LType: TRttiType;
LIntfType: TRttiInterfaceType;
begin
LTypes := TRttiContext.Create.GetTypes;
try
for LType in LTypes do
begin
if TThread.Current.CheckTerminated = False then
begin
if LType.TypeKind = TTypeKind.tkInterface then
begin
LIntfType := (LType as TRttiInterfaceType);
if TIntfFlag.ifHasGuid in LIntfType.IntfFlags then
begin
FInterfaceTypes.AddOrSetValue(LIntfType.GUID, LIntfType);
end;
end;
end;
end;
except
end;
Caching := False;
Cached := True;
end).Start;
{$ENDIF}
end;
class procedure TInterfaceHelper.WaitIfCaching;
begin
if Caching then
TSpinWait.SpinUntil(
function: Boolean
begin
Result := Cached;
end);
end;
class procedure TInterfaceHelper.CacheIfNotCachedAndWaitFinish;
begin
if Cached then
Exit
else if not Caching then
begin
RefreshCache;
WaitIfCaching;
end
else
WaitIfCaching;
end;
class function TInterfaceHelper.GetType(AIntfInTValue: TValue)
: TRttiInterfaceType;
var
LType: TRttiType;
begin
Result := nil;
{$ifdef DUNITX}
LType := AIntfInTValue.RttiType;
{$ENDIF}
if LType is TRttiInterfaceType then
Result := LType as TRttiInterfaceType;
end;
{$ENDIF}
end.