{"id":961,"date":"2012-08-02T22:37:28","date_gmt":"2012-08-02T10:37:28","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=961"},"modified":"2012-08-02T22:37:28","modified_gmt":"2012-08-02T10:37:28","slug":"thinking-creatively-with-queryinterface","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/961\/","title":{"rendered":"Thinking Creatively with QueryInterface()"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>I soon hope to be releasing &#8220;Smoketest&#8221;, a testing framework that I have developed over the past few years. It has actually been in production use for most of that time (albeit by my own good self) but also continues to develop and evolve. &nbsp;On the occasions when I have mentioned it, people have asked me to publish it, but I have been reluctant to do so up to now for a number of reasons, not least that it needs a bit of polishing to make it suitable for public scrutiny.<\/p>\n<p><!--more--><\/p>\n<p>There were also a number of things about the framework that I was not happy with and I knew that fixing them would demand &#8220;breaking changes&#8221;.  I don&#8217;t mind inconveniencing myself but I did not want to release something only to then have to break a lot of other people&#8217;s code written on the back of it (or end up tied in to less than desirable ways of working for the sake of NOT breaking anything).<\/p>\n<p>The biggest problem holding me back has been that I wanted the framework to be extensible.<\/p>\n<p>Not only did I want people using it to be able to test things that the framework knew how to test, but to also for people to be able to extend the framework itself with further fundamental or higher level tests that could essentially become part of the framework without having to modify the framework code to achieve this.<\/p>\n<p>The good news is, I finally found a solution to this problem, and once I realised how to do it I can&#8217;t quite understand how I failed to see it before!<\/p>\n<p>The trick was to subvert <strong>QueryInterface()<\/strong> &#8211; that part of <strong>IUnknown<\/strong> that allows us to obtain an interface reference for any interface supported by some object to which we already have some other reference:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  type\r\n    TSomeObject = class(TInterfacedObject, ISomeInterface,\r\n                                           IAnotherInterface)\r\n    end;\r\n\r\n  var\r\n    some: ISomeInterface;\r\n    other: IAnotherInterface;\r\n  begin\r\n    some  := TSomeObject.Create as ISomeInterface;\r\n    other := some as IAnotherInterface;\r\n  end;\r\n<\/pre>\n<p>It occurred to me that this entire mechanism hinges on the implementation of <strong>QueryInterface()<\/strong> inherited by <strong>TSomeObject<\/strong>.  It is this inherited implementation which determines whether or not the object supports a requested interface as declared in the class itself, and returns the appropriate reference by what-ever voodoo is involved in obtaining such things at runtime.<\/p>\n<p>But I could replace that implementation with something else.  Something that didn&#8217;t use compiler generated information to lookup the required reference, but did so in a far more ordinary fashion.<\/p>\n<p>I should note at this point that what I was contemplating was only possible in this very limited instance due to two things:<\/p>\n<ol>\n<li>The lifetime of the objects involved was being explicitly managed by my framework, so confused reference counting was not an issue\n<li>The interface references that would result would themselves be very short lived and be long gone when the underlying objects were disposed of so there would be no danger arising from mixing object and interface references\n<\/ol>\n<p>These two conditions are not at all common, so what I am about to describe is absolutely <strong>not<\/strong> suitable for wider use, but it demonstrates just what is possible with a little thinking &#8220;outside the box&#8221; when called for, without having to resort (or rush) to generics or anonymous methods, or other crimes against Pascal syntax.  \ud83d\ude09<\/p>\n<p>Fundamentally, the job of <strong>QueryInterface()<\/strong> is to take some specified interface ID and return a valid reference of the appropriate interface type if supported.  Nobody said that the reference had to be to the same object that was being asked for the interface in the first place! (<em>well, actually, this is more-or-less implicit in the reference counting model that underpins COM, but this isn&#8217;t COM and as I said before, reference counting  specifically does not play any part in this exercise<\/em>)<\/p>\n<p>So, what I have in <strong>Smoketest<\/strong> is a <strong>TTest<\/strong> class that can satisfy requests for interfaces not by giving up a reference to an interface that it implements itself, but by consulting a list maintained by the framework itself, associating specific interface ID&#8217;s with specific implementation classes:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\ninterface\r\n\r\n  \/\/ Not complete... intended to illustrate QueryInterface() \r\n  \/\/  declaration and implementation only ...\r\n\r\n  type\r\n    TTest = class(TIncident, IUnknown)\r\n    protected\r\n      function QueryInterface(const aIID: TGUID; out aObj): HRESULT; stdcall;\r\n    end;\r\n\r\nimplementation\r\n\r\n  { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }\r\n  function TTest.QueryInterface(const aIID: TGUID; out aObj): HRESULT;\r\n  var\r\n    extClass: TExpectationClass;\r\n    expectation: TExpectation;\r\n  begin\r\n    if Smoketest.FindExtension(aIID, extClass) then\r\n    begin\r\n      result      := 0;\r\n      expectation := extClass.Create(self);\r\n\r\n      if NOT expectation.GetInterface(aIID, aObj) then\r\n        result := E_NOINTERFACE;\r\n    end\r\n    else\r\n      result := inherited QueryInterface(aIID, aObj);\r\n  end;\r\n<\/pre>\n<p>The <strong>IUnknown<\/strong> interface has to be redeclared in the class declaration otherwise the <strong>QueryInterface()<\/strong> implementation provided by the class will not be used by that interface.  Effectively this achieves the &#8220;override&#8221; of the inherited <strong>QueryInterface()<\/strong> implementation (which is not <strong>virtual<\/strong> and so cannot be more directly overridden in the normal fashion).<\/p>\n<p>If <strong>TTest.QueryInterface()<\/strong> fails to locate an extension for the specified interface ID it simply calls inherited, in case the interface is one that the <strong>TTest<\/strong> object itself implements (currently this is the least likely outcome, hence extensions are looked for first).<\/p>\n<p><em>Note: The extension classes are derived from a <strong>TExpectation<\/strong> class &#8211; &#8220;expectation&#8221; in the above code is not a typo for &#8220;extension&#8221;.<\/em>  \ud83d\ude42<\/p>\n<p>If an extension class is identified then an instance of that class is instantiated and it is a reference to <strong>that<\/strong> <em>newly created<\/em> object that is then returned.<\/p>\n<p>i.e. the following code actually creates a new object:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n   var\r\n     test: TTest;\r\n     some: ISomeInterface;\r\n   begin\r\n       :\r\n     some := test as ISomeInterface;\r\n       :\r\n   end;\r\n<\/pre>\n<p>I am normally strongly averse to side-effects, but this is one of the exceptions that proves the rule.  The side-effect is entirely contained within the <strong>Smoketest<\/strong> framework, which also deals with all the consequences of the resulting side-effects.<\/p>\n<p>All will become clear when I publish more details of <strong>Smoketest<\/strong> itself.<\/p>\n<p>But for now, I thought I would share what I thought was a creative [sic] technique involving <strong>QueryInterface()<\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> I soon hope to be releasing &#8220;Smoketest&#8221;, a testing framework that I have developed over the past few years. It has actually been in production use for most of that time (albeit by my own good self) but also continues to develop and evolve. &nbsp;On the occasions when I have mentioned it, people have asked [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[292,29,51,167,295],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-fv","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":284,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/284\/","url_meta":{"origin":961,"position":0},"title":"This Week&#8217;s Poll, Delphi at Tech Ed and 2009 Ship Date","date":"01 Sep 2008","format":false,"excerpt":"Since I'm preparing a series of posts about (and eventual publication of) Smoketest, my own testing framework, I thought I'd test the water with this weeks poll and see what - if anything - people are already using as far as unit testing goes. Also I thought I'd briefly mention\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2956,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2956\/","url_meta":{"origin":961,"position":1},"title":"Introducing: Smoketest 2.0","date":"24 Sep 2019","format":false,"excerpt":"Meet the new Smoketest. Nothing like the old Smoketest. A new, ultra-lightweight unit testing framework with xUnit 2.x result output (and the ability to support other output formats as needed).","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/vape_2.jpg?fit=1200%2C307&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2095,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\/","url_meta":{"origin":961,"position":2},"title":"Extending Smoketest (Part 1) &#8211; An Inspector Calls","date":"05 Nov 2013","format":false,"excerpt":"In the soon to be released Smoketest framework it is sometimes useful to create new test types to supplement the tests built-in to the framework. In this and the next post I will walk through the process of implementing and registering a custom test with the Smoketest framework. In a\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2111,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2111\/","url_meta":{"origin":961,"position":3},"title":"Extending Smoketest (Part 2) &#8211; Great(er) Expectations","date":"07 Nov 2013","format":false,"excerpt":"In my previous post on Smoketest I showed how you can extend the inspections framework to work with complex types in your code. As promised, I shall now show how you can do much the same thing to extend the framework with entirely new tests. \u201cI must be taken as\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":961,"position":4},"title":"Smoketest 1.0 &#8211; Release and Be Damned!","date":"14 Nov 2013","format":false,"excerpt":"As I have been promising for some time (quite literally 5 years (!), I am ashamed to admit) I am finally unclenching and releasing the Smoketest framework into the wild, ready or not. The code is published and will continue to be updated in a github repository. Documentation is still\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-14-at-19.51.57-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":375,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/375\/","url_meta":{"origin":961,"position":5},"title":"Delphi 2009 &#8211; StringPerformance Redux","date":"22 Sep 2008","format":false,"excerpt":"It looks like I may have jumped the gun with my conclusions from the previous exercise to benchmark string performance in Delphi 2009.\u00a0 Following a useful exchange in the comments with Kryvich I corrected a small discrepancy in the tests and made some changes to the performance testing subsystem within\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/delphi2009-stringperformance-chart.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/961"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=961"}],"version-history":[{"count":10,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/961\/revisions"}],"predecessor-version":[{"id":987,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/961\/revisions\/987"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=961"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=961"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=961"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}