/MVCBr.InterfaceHelper.pas

https://github.com/amarildolacerda/MVCBr · Pascal · 311 lines · 246 code · 36 blank · 29 comment · 27 complexity · 24dbcbc1acd8bb3437d4bb78d723e1cc MD5 · raw file

  1. {
  2. Auth: http://stackoverflow.com/questions/39584234/how-to-obtain-rtti-from-an-interface-reference-in-delphi
  3. }
  4. unit MVCBr.InterfaceHelper;
  5. interface
  6. {$DEFINE DUNITX}
  7. {$IFDEF VER330}
  8. {$UNDEF DUNITX}
  9. {$ENDIF}
  10. uses System.Rtti, System.TypInfo, System.Generics.Collections, System.SysUtils;
  11. {$IFNDEF BPL}
  12. type
  13. TInterfaceHelper = record
  14. strict private
  15. type
  16. TInterfaceTypes = TDictionary<TGUID, TRttiInterfaceType>;
  17. class var FInterfaceTypes: TInterfaceTypes;
  18. class var Cached: Boolean;
  19. class var Caching: Boolean;
  20. class procedure WaitIfCaching; static;
  21. class procedure CacheIfNotCachedAndWaitFinish; static;
  22. class constructor Create;
  23. class destructor Destroy;
  24. public
  25. // refresh cached RTTI in a background thread (eg. when new package is loaded)
  26. class procedure RefreshCache; static;
  27. // get RTTI from interface
  28. class function GetType(AIntf: IInterface): TRttiInterfaceType;
  29. overload; static;
  30. class function GetType(AGUID: TGUID): TRttiInterfaceType; overload; static;
  31. class function GetType(AIntfInTValue: TValue): TRttiInterfaceType;
  32. overload; static;
  33. // get type name from interface
  34. class function GetTypeName(AIntf: IInterface): String; overload; static;
  35. class function GetTypeName(AGUID: TGUID): String; overload; static;
  36. class function GetQualifiedName(AIntf: IInterface): String;
  37. overload; static;
  38. class function GetQualifiedName(AGUID: TGUID): String; overload; static;
  39. // get methods
  40. class function GetMethods(AIntf: IInterface): TArray<TRttiMethod>; static;
  41. class function GetMethod(AIntf: IInterface; const MethodName: String)
  42. : TRttiMethod; static;
  43. // Invoke method
  44. class function InvokeMethod(AIntf: IInterface; const MethodName: String;
  45. const Args: array of TValue): TValue; overload; static;
  46. class function InvokeMethod(AIntfInTValue: TValue; const MethodName: String;
  47. const Args: array of TValue): TValue; overload; static;
  48. end;
  49. {$ENDIF}
  50. implementation
  51. {$IFNDEF BPL}
  52. uses System.Classes,
  53. System.SyncObjs {$IFDEF DUNITX}, DUnitX.Utils{$ENDIF};
  54. { TInterfaceHelper }
  55. class function TInterfaceHelper.GetType(AIntf: IInterface): TRttiInterfaceType;
  56. var
  57. ImplObj: TObject;
  58. LGUID: TGUID;
  59. LIntfType: TRttiInterfaceType;
  60. TempIntf: IInterface;
  61. begin
  62. Result := nil;
  63. try
  64. // As far as I know, the cast will fail only when AIntf is obatined from OLE Object
  65. // Is there any other cases?
  66. ImplObj := AIntf as TObject;
  67. except
  68. // for interfaces obtained from OLE Object
  69. Result := TRttiContext.Create.GetType(TypeInfo(System.IDispatch))
  70. as TRttiInterfaceType;
  71. Exit;
  72. end;
  73. {$IFDEF DUNITX}
  74. // for interfaces obtained from TRawVirtualClass (for exmaple IOS & Android & Mac interfaces)
  75. if ImplObj.ClassType.InheritsFrom(TRawVirtualClass) then
  76. begin
  77. LGUID := ImplObj.GetField('FIIDs').GetValue(ImplObj).AsType < TArray <
  78. TGUID >> [0];
  79. Result := GetType(LGUID);
  80. end
  81. // for interfaces obtained from TVirtualInterface
  82. else if ImplObj.ClassType.InheritsFrom(TVirtualInterface) then
  83. begin
  84. LGUID := ImplObj.GetField('FIID').GetValue(ImplObj).AsType<TGUID>;
  85. Result := GetType(LGUID);
  86. end
  87. else {$ENDIF}
  88. // for interfaces obtained from Delphi object
  89. // 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/
  90. begin
  91. for LIntfType in (TRttiContext.Create.GetType(ImplObj.ClassType)
  92. as TRttiInstanceType).GetImplementedInterfaces do
  93. begin
  94. if ImplObj.GetInterface(LIntfType.GUID, TempIntf) then
  95. begin
  96. if AIntf = TempIntf then
  97. begin
  98. Result := LIntfType;
  99. Exit;
  100. end;
  101. end;
  102. end;
  103. end;
  104. end;
  105. class constructor TInterfaceHelper.Create;
  106. begin
  107. if not assigned(FInterfaceTypes) then
  108. FInterfaceTypes := TInterfaceTypes.Create;
  109. Cached := False;
  110. Caching := False;
  111. RefreshCache;
  112. end;
  113. class destructor TInterfaceHelper.Destroy;
  114. begin
  115. if assigned(FInterfaceTypes) then
  116. FInterfaceTypes.Free;
  117. FInterfaceTypes := nil;
  118. end;
  119. class function TInterfaceHelper.GetQualifiedName(AIntf: IInterface): String;
  120. var
  121. LType: TRttiInterfaceType;
  122. begin
  123. Result := string.Empty;
  124. LType := GetType(AIntf);
  125. if assigned(LType) then
  126. Result := LType.QualifiedName;
  127. end;
  128. class function TInterfaceHelper.GetMethod(AIntf: IInterface;
  129. const MethodName: String): TRttiMethod;
  130. var
  131. LType: TRttiInterfaceType;
  132. begin
  133. Result := nil;
  134. LType := GetType(AIntf);
  135. if assigned(LType) then
  136. Result := LType.GetMethod(MethodName);
  137. end;
  138. class function TInterfaceHelper.GetMethods(AIntf: IInterface)
  139. : TArray<TRttiMethod>;
  140. var
  141. LType: TRttiInterfaceType;
  142. begin
  143. Result := [];
  144. LType := GetType(AIntf);
  145. if assigned(LType) then
  146. Result := LType.GetMethods;
  147. end;
  148. class function TInterfaceHelper.GetQualifiedName(AGUID: TGUID): String;
  149. var
  150. LType: TRttiInterfaceType;
  151. begin
  152. Result := string.Empty;
  153. LType := GetType(AGUID);
  154. if assigned(LType) then
  155. Result := LType.QualifiedName;
  156. end;
  157. class function TInterfaceHelper.GetType(AGUID: TGUID): TRttiInterfaceType;
  158. begin
  159. CacheIfNotCachedAndWaitFinish;
  160. Result := FInterfaceTypes.Items[AGUID];
  161. end;
  162. class function TInterfaceHelper.GetTypeName(AGUID: TGUID): String;
  163. var
  164. LType: TRttiInterfaceType;
  165. begin
  166. Result := string.Empty;
  167. LType := GetType(AGUID);
  168. if assigned(LType) then
  169. Result := LType.Name;
  170. end;
  171. class function TInterfaceHelper.InvokeMethod(AIntfInTValue: TValue;
  172. const MethodName: String; const Args: array of TValue): TValue;
  173. var
  174. LMethod: TRttiMethod;
  175. LType: TRttiInterfaceType;
  176. begin
  177. LType := GetType(AIntfInTValue);
  178. if assigned(LType) then
  179. LMethod := LType.GetMethod(MethodName);
  180. if not assigned(LMethod) then
  181. raise Exception.Create('Method not found');
  182. Result := LMethod.Invoke(AIntfInTValue, Args);
  183. end;
  184. class function TInterfaceHelper.InvokeMethod(AIntf: IInterface;
  185. const MethodName: String; const Args: array of TValue): TValue;
  186. var
  187. LMethod: TRttiMethod;
  188. begin
  189. LMethod := GetMethod(AIntf, MethodName);
  190. if not assigned(LMethod) then
  191. raise Exception.Create('Method not found');
  192. Result := LMethod.Invoke(TValue.From<IInterface>(AIntf), Args);
  193. end;
  194. class function TInterfaceHelper.GetTypeName(AIntf: IInterface): String;
  195. var
  196. LType: TRttiInterfaceType;
  197. begin
  198. Result := string.Empty;
  199. LType := GetType(AIntf);
  200. if assigned(LType) then
  201. Result := LType.Name;
  202. end;
  203. class procedure TInterfaceHelper.RefreshCache;
  204. var
  205. LTypes: TArray<TRttiType>;
  206. begin
  207. WaitIfCaching;
  208. FInterfaceTypes.Clear;
  209. Cached := False;
  210. Caching := True;
  211. {$IFNDEF SERVICE}
  212. TThread.CreateAnonymousThread(
  213. procedure
  214. var
  215. LType: TRttiType;
  216. LIntfType: TRttiInterfaceType;
  217. begin
  218. LTypes := TRttiContext.Create.GetTypes;
  219. try
  220. for LType in LTypes do
  221. begin
  222. if TThread.Current.CheckTerminated = False then
  223. begin
  224. if LType.TypeKind = TTypeKind.tkInterface then
  225. begin
  226. LIntfType := (LType as TRttiInterfaceType);
  227. if TIntfFlag.ifHasGuid in LIntfType.IntfFlags then
  228. begin
  229. FInterfaceTypes.AddOrSetValue(LIntfType.GUID, LIntfType);
  230. end;
  231. end;
  232. end;
  233. end;
  234. except
  235. end;
  236. Caching := False;
  237. Cached := True;
  238. end).Start;
  239. {$ENDIF}
  240. end;
  241. class procedure TInterfaceHelper.WaitIfCaching;
  242. begin
  243. if Caching then
  244. TSpinWait.SpinUntil(
  245. function: Boolean
  246. begin
  247. Result := Cached;
  248. end);
  249. end;
  250. class procedure TInterfaceHelper.CacheIfNotCachedAndWaitFinish;
  251. begin
  252. if Cached then
  253. Exit
  254. else if not Caching then
  255. begin
  256. RefreshCache;
  257. WaitIfCaching;
  258. end
  259. else
  260. WaitIfCaching;
  261. end;
  262. class function TInterfaceHelper.GetType(AIntfInTValue: TValue)
  263. : TRttiInterfaceType;
  264. var
  265. LType: TRttiType;
  266. begin
  267. Result := nil;
  268. {$ifdef DUNITX}
  269. LType := AIntfInTValue.RttiType;
  270. {$ENDIF}
  271. if LType is TRttiInterfaceType then
  272. Result := LType as TRttiInterfaceType;
  273. end;
  274. {$ENDIF}
  275. end.