{"id":2313,"date":"2013-12-20T07:00:00","date_gmt":"2013-12-20T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/12\/20\/wouldnt-the-recycle-bin-sample-program-have-been-simpler-without-com\/"},"modified":"2013-12-20T07:00:00","modified_gmt":"2013-12-20T07:00:00","slug":"wouldnt-the-recycle-bin-sample-program-have-been-simpler-without-com","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20131220-00\/?p=2313","title":{"rendered":"Wouldn&#039;t the Recycle Bin sample program have been simpler without COM?"},"content":{"rendered":"<p>\nSteve Wolf suggests that\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/08\/31\/10203215.aspx#10203669\">\nthe sample program would have been much simpler had the shell\nextension model been a flat Win32 interface<\/a>.\n<\/p>\n<p>\nOkay, let&#8217;s try it.\n<\/p>\n<p>\nSince this is an extension model, each extension needs to specify\nthe callbacks for each namespace operation.\nPerhaps it could have been done like this:\n<\/p>\n<pre>\nHRESULT (CALLBACK *SHELLFOLDER_EXTENDHANDLER)(\n    void *lpContext,\n    OBJECTTYPE type, void **phObject);\nHRESULT (CALLBACK *SHELLFOLDER_PARSEDISPLAYNAMEHANDLER)(\n    void *lpContext,\n    HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,\n    ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes);\nHRESULT (CALLBACK *SHELLFOLDER_ENUMOBJECTSHANDLER)(\n    void *lpContext,\n    HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl);\nHRESULT (CALLBACK *SHELLFOLDER_BINDTOOBJECTHANDLER)(\n    void *lpContext,\n    PCUIDLIST_RELATIVE pidl, LPBINDCTX pbc,\n    OBJECTTYPE type, void **phObject);\nHRESULT (CALLBACK *SHELLFOLDER_BINDTOSTRAGEHANDLER)(\n    void *lpContext,\n    PCUIDLIST_RELATIVE pidl, LPBINDCTX pbc,\n    OBJECTTYPE type, void **phObject);\nHRESULT (CALLBACK *SHELLFOLDER_COMPAREIDSHANDLER)(\n    void *lpContext,\n    LPARAM lParam, PCUIDLIST_RELATIVE pidl1,\n    PCUIDLIST_RELATIVE pidl2);\n... (etc) ...\nHFOLDER CreateShellFolderImplementation(\n    SHELLFOLDER_EXTENDHANDLER pfnExtend,\n    SHELLFOLDER_PARSEDISPLAYNAMEHANDLER pfnParseDisplayName,\n    SHELLFOLDER_ENUMOBJECTSHANDLER pfnEnumObjects,\n    SHELLFOLDER_BINDTOOBJECTHANDLER pfnBindToObject,\n    SHELLFOLDER_BINDTOSTRAGEHANDLER pfnBindToStorage,\n    SHELLFOLDER_COMPAREIDSHANDLER pfnCompareIDs,\n    ... (etc) ...\n    void *lpContext);\n<\/pre>\n<p>\nThis would be the function that allows a third party to create a\nshell folder implementation.\nYou pass it a bunch of flat callback functions, one for each\noperation that a shell folder supports,\nso that when the application tries to perform that operation on your\ncustom folder, the operating system can ask your custom implementation\nto do that thing.\n<\/p>\n<p>\nIf additional shell folder operations are added in the future,\nthe operating system needs to know how to ask your shell extension\nwhether it knows how to do  those extended things.\nThat&#8217;s what the <code>Extend<\/code> method is for.\nThe operating system could ask to extend your object to one\nthat supports <code>HFOLDER2<\/code> operations.\n<\/p>\n<p>\nActually, if you look at it, these are exactly the same as\nCOM methods.\nThe first parameter says what object you are operating on\n(&#8220;<code>this<\/code>&#8220;),\nand the rest are the parameters.\n<\/p>\n<p>\nOkay, so I&#8217;m setting up a straw man that looks just like COM.\nSo let&#8217;s do something that looks very different from COM.\nWe could use the window procedure paradigm:\n<\/p>\n<pre>\nHRESULT (CALLBACK *SHELLFOLDER_INVOKE)(\n    void *lpContext,\n    FOLDERCOMMAND cmd, void *parameters);\nHFOLDER CreateShellFolderImplementation(\n    SHELLFOLDER_INVOKE pfnInvoke,\n    void *lpContext);\n<\/pre>\n<p>\nYour invoke function receives a <code>FOLDER&shy;COMMAND<\/code>\nenumeration which specifies what command the client is trying to\nperform,\nand then switches on the command to perform the command,\nor returns <code>E_NOT&shy;IMPL<\/code> if you don&#8217;t handle the command.\nSince each of the methods takes different parameters,\nwe have to do some work to pack them up into a generic parameter\nblock, and then unpack it on the receiving end.\nLet&#8217;s assume some helper functions that do this packing and unpacking.\n<\/p>\n<pre>\nHRESULT UnpackParseDisplayName(\n    void *parameters,\n    HWND *phwnd,\n    LPBINDCTX *ppbc,\n    LPWSTR *ppszDisplayName,\n    ULONG **ppchEaten,\n    PIDLIST_RELATIVE **ppidl,\n    ULONG **ppdwAttributes);\n);\nHRESULT UnpackEnumObjects(\n    void *parameters,\n    HWND *phwnd,\n    SHCONTF *pgrfFlags,\n    HENUMIDLIST **ppheidl);\nHRESULT AwesomeShellFolderInvoke(\n    void *lpContext,\n    FOLDERCOMMAND cmd,\n    void *parameters)\n{\n  HRESULT hr = E_NOTIMPL;\n  CAwesome *self = reinterpret_cast&lt;CAwesome*&gt;(lpContext);\n  switch (cmd) {\n  case FOLDERCOMMAND_PARSEDISPLAYNAME:\n    {\n      HWND hwnd;\n      LPBINDCTX pbc;\n      LPWSTR pszDisplayName;\n      ULONG *ppchEaten;\n      PIDLIST_RELATIVE *pidl;\n      ULONG *pdwAttributes;\n      hr = UnpackParseDisplayName(parameters, &amp;hwnd, &amp;pbc,\n              &amp;pszDisplayName, &amp;ppchEaten, &amp;pidl,\n              &amp;pdwAttributes);\n      if (SUCCEEDED(hr)) {\n        hr = ... do the actual work ...\n      }\n    }\n    break;\n  case FOLDERCOMMAND_ENUMOBJECTS:\n    {\n      HWND hwnd;\n      SHCONTF grfFlags;\n      HENUMIDLIST *pheidl;\n      hr = UnpackEnumObjects(parameters, &amp;hwnd, &amp;grfFlags,\n              &amp;pheidl);\n      if (SUCCEEDED(hr)) {\n        hr = ... do the actual work ...\n      }\n    }\n    break;\n    ... (etc) ...\n  }\n  return hr;\n}\n<\/pre>\n<p>\nThis could be made a lot simpler with the addition of some helper\nfunctions.\n<\/p>\n<pre>\nHRESULT DispatchParseDisplayName(\n  HRESULT (CALLBACK *)(\n    void *lpContext,\n    HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,\n    ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes),\n  void *lpContext,\n  void *parameters);\nHRSEULT DispatchEnumObjects(\n  HRESULT (CALLBACK *)(\n    void *lpContext,\n    HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl),\n  void *lpContext,\n  void *parameters);\n<\/pre>\n<p>\nThe implementation would then go like this:\n<\/p>\n<pre>\nHRESULT AwesomeParseDisplayName(\n    void *lpContext,\n    HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,\n    ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)\n{\n  CAwesome *self = reinterpret_cast&lt;CAwesome*&gt;(lpContext);\n  HRESULT hr;\n  ... do the actual work ...\n  return hr;\n}\nHRESULT AwesomeEnumObjects(\n    void *lpContext,\n    HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl),\n{\n  CAwesome *self = reinterpret_cast&lt;CAwesome*&gt;(lpContext);\n  HRESULT hr;\n  ... do the actual work ...\n  return hr;\n}\nHRESULT AwesomeShellFolderInvoke(\n    void *lpContext,\n    FOLDERCOMMAND cmd,\n    void *parameters)\n{\n  switch (cmd) {\n  case FOLDERCOMMAND_PARSEDISPLAYNAME:\n    return DispatchParseDisplayName(AwesomeParseDisplayName,\n              lpContext, parameters);\n  case FOLDERCOMMAND_ENUMOBJECTS:\n    return DispatchEnumObjects(AwesomeEnumObjects,\n              lpContext, parameters);\n    ... (etc) ...\n  }\n  return E_NOTIMPL;\n}\n<\/pre>\n<p>\nYou might decide to make the parameter packing transparent\ninstead of opaque, so that they are passed as, say,\nan array of generic types like <code>VARIANT<\/code>s.\n(Note that I&#8217;m abusing <code>VARIANT<\/code>s here.\nThese are not valid <code>VARIANT<\/code>s,\nbut it saves me from having to declare my own generic type.\nThis is just a design discussion, not an actual implementation.)\n<\/p>\n<pre>\nHRESULT (CALLBACK *SHELLFOLDER_INVOKE)(\n    void *lpContext,\n    FOLDERCOMMAND cmd,\n    VARIANT *rgvarArgs,\n    UINT cArgs);\n\/\/ error checking elided for expository purposes\n\/\/ In real life, you would have to validate cArgs\n\/\/ and the variant types.\nHRESULT AwesomeShellFolderInvoke(\n    void *lpContext,\n    FOLDERCOMMAND cmd,\n    VARIANT *rgvarArgs,\n    UINT cArgs)\n{\n  CAwesome *self = reinterpret_cast&lt;CAwesome*&gt;(lpContext);\n  switch (cmd) {\n  case FOLDERCOMMAND_PARSEDISPLAYNAME:\n    return self-&gt;ParseDisplayName(\n      reinterpret_cast&lt;HWND&gt;(rgvarArgs[0]-&gt;byref),\n      reinterpret_cast&lt;LPBINDCTX&gt;(rgvarArgs[1]-&gt;byref),\n      reinterpret_cast&lt;LPWSTR&gt;(rgvarArgs[2]-&gt;byref),\n      reinterpret_cast&lt;ULONG*&gt;(rgvarArgs[3]-&gt;byref),\n      reinterpret_cast&lt;PIDLIST_RELATIVE*&gt;(rgvarArgs[4]-&gt;byref),\n      reinterpret_cast&lt;ULONG**&gt;(rgvarArgs[5]-&gt;byref));\n  case FOLDERCOMMAND_ENUMOBJECTS:\n    return self-&gt;EnumObjects(\n      reinterpret_cast&lt;HWND&gt;(rgvarArgs[0]-&gt;byref),\n      reinterpret_cast&lt;SHCONTF&gt;(rgvarArgs[1]-&gt;lVal),\n      reinterpret_cast&lt;HENUMIDLIST *&gt;(rgvarArgs[2]-&gt;byref));\n    ... (etc) ...\n  }\n  return E_NOTIMPL;\n}\n<\/pre>\n<p>\n(This is basically the plug-in model that\n<a HREF=\"https:\/\/developer.mozilla.org\/en-US\/docs\/NPClass\">\nsome people have chosen to pursue<\/a>.\nIt is also basically the same as\n<code>IDispatch::Invoke<\/code>.)\n<\/p>\n<p>\nOkay, that&#8217;s how you implement the plug-in.\nNow how do you call it?\n<\/p>\n<p>\nYou would have to pack the parameters, then call through the\n<code>Invoke<\/code> method with your command ID.\nFor example, a call to\n<code>FOLDER&shy;COMMAND_ENUM&shy;OBJECTS<\/code>\nwould go like this:\n<\/p>\n<pre>\n\/\/ was: hr = psf-&gt;EnumObjects(hwnd, shcontf, &amp;peidl);\n\/\/ now:\nHENUMIDLIST heidl;\nVARIANT args[3];\nargs[0].vt = VT_BYREF;\nargs[0].byref = hwnd;\nargs[1].vt = VT_I4;\nargs[1].lVal = shcontf;\nargs[2].vt = VT_BYREF;\nargs[2].byref = &amp;heidl;\nhr = InvokeShellFolder(hsf, FOLDERCOMMAND_ENUMOBJECTS, args, 3);\n<\/pre>\n<p>\nYuck.\n<\/p>\n<p>\nLet&#8217;s assume that the shell provides helper functions that do all\nthis parameter packing for you.\n(This is\n<a HREF=\"https:\/\/developer.mozilla.org\/en-US\/docs\/NPN_Invoke\">\nmore than certain plug-in models give you<\/a>.)\n<\/p>\n<pre>\nHRESULT ShellFolder_ParseDisplayName(\n    HSHELLFOLDER hsf,\n    HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,\n    ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)\n{\n  VARIANT args[6];\n  args[0].vt = VT_BYREF;\n  args[0].byref = hwnd;\n  args[1].vt = VT_BYREF;\n  args[1].byref = pbc;\n  args[2].vt = VT_BYREF;\n  args[2].byref = pszDisplayName;\n  args[3].vt = VT_BYREF;\n  args[3].byref = pchEaten;\n  args[4].vt = VT_BYREF;\n  args[4].byref = ppidl;\n  args[5].vt = VT_BYREF;\n  args[5].byref = pdwAttributes;\n  return InvokeShellFolder(hsf, FOLDERCOMMAND_PARSEDISPLAYNAME,\n                           args, 6);\n}\nHRESULT ShellFolder_EnumObjects(\n    HSHELLFOLDER hsf,\n    HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl)\n{\n  VARIANT args[3];\n  args[0].vt = VT_BYREF;\n  args[0].byref = hwnd;\n  args[1].vt = VT_I4;\n  args[1].lVal = shcontf;\n  args[2].vt = VT_BYREF;\n  args[2].byref = &amp;heidl;\n  return InvokeShellFolder(hsf, FOLDERCOMMAND_ENUMOBJECTS, args, 3);\n}\n... (etc) ...\n<\/pre>\n<p>\nThe naming convention above is kind of awkward, so let&#8217;s give them\na bit less clumsy names.\n<\/p>\n<pre>\nHRESULT ParseShellFolderDisplayName(\n    HSHELLFOLDER hsf,\n    HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,\n    ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes);\nHRESULT EnumShellFolderObjects(\n    HSHELLFOLDER hsf,\n    HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl);\n... (etc) ...\n<\/pre>\n<p>\nOkay, now that we have a flat API,\nlet&#8217;s convert\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/08\/30\/10202076.aspx\">\nthe original code<\/a>.\nThe first function now goes like this:\n<\/p>\n<pre>\nHRESULT BindToCsidl(int csidl,\n    \/\/ <font COLOR=\"red\"><strike>REFIID riid, void **ppv<\/strike><\/font>\n    <font COLOR=\"blue\">HSHELLFOLDER<\/font> *phsf)\n{\n HRESULT hr;\n PIDLIST_ABSOLUTE pidl;\n hr = SHGetSpecialFolderLocation(NULL, csidl, &amp;pidl);\n if (SUCCEEDED(hr)) {\n  \/\/ <font COLOR=\"red\"><strike>IShellFolder *psfDesktop;<\/strike><\/font>\n  <font COLOR=\"blue\">HSHELLFOLDER hsfDesktop;<\/font>\n  hr = SHGetDesktopFolder(&amp;hsfDesktop);\n  if (SUCCEEDED(hr)) {\n   if (pidl-&gt;mkid.cb) {\n    \/\/ hr = <font COLOR=\"red\"><strike>psfDesktop-&gt;BindToObject(<\/strike><\/font>pidl, NULL, <font COLOR=\"red\"><strike>riid, ppv<\/strike><\/font>);\n    hr = <font COLOR=\"blue\">BindToShellFolderObject(hsfDesktop,<\/font> pidl, NULL, <font COLOR=\"blue\">phsf<\/font>);\n   } else {\n    \/\/ <font COLOR=\"red\"><strike>hr = psfDesktop-&gt;QueryInterface(riid, ppv);<\/strike><\/font>\n    <font COLOR=\"blue\">*phsf = hsfDesktop;\n    hsfDesktop = nullptr; \/\/ transfer to owner\n    hr = S_OK;<\/font>\n   }\n   \/\/ <font COLOR=\"red\"><strike>psfDesktop-&gt;Release();<\/strike><\/font>\n   <font COLOR=\"blue\">if (hsfDesktop) ShellFolder_Destroy(hsfDesktop);<\/font>\n  }\n  CoTaskMemFree(pidl);\n }\n return hr;\n}\n<\/pre>\n<p>\nWhat happened here?\nThe <code>IShell&shy;Folder<\/code>\ninterface was replaced by a\n<code>HSHELL&shy;FOLDER<\/code> flat handle.\nFlat APIs use handles to refer to objects instead of interface pointers.\n<\/p>\n<p>\nA method call on an interface pointer becomes a flat API call.\nIn general,\n<code>pInterface-&gt;VerbNoun(args)<\/code> gets flattened to\n<code>VerbInterfaceNoun(h, args)<\/code>.\nBut that&#8217;s just renaming and doesn&#8217;t change the underlying complexity\nof the issue.\n<\/p>\n<p>\nI could&#8217;ve added reference counting to these flat objects,\nbut then I would be accused of intentionally making it look like COM,\nso let&#8217;s say that these flat objects are not reference-counted.\nTherefore, we have to be more careful about not destroying the object\nwe plan on returning.\n<\/p>\n<p>\nOn to the next two functions:\n<\/p>\n<pre>\nvoid PrintDisplayName(\n    \/\/ <font COLOR=\"red\"><strike>IShellFolder *psf,<\/strike><\/font>\n    <font COLOR=\"blue\">HSHELLFOLDER hsf<\/font>,\n    PCUITEMID_CHILD pidl, SHGDNF uFlags, PCTSTR pszLabel)\n{\n <a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/08\/23\/218837.aspx\">STRRET<\/a> sr;\n \/\/ HRESULT hr = <font COLOR=\"red\"><strike>psf-&gt;GetDisplayNameOf(<\/strike><\/font>pidl, uFlags, &amp;sr);\n HRESULT hr = <font COLOR=\"blue\">GetShellFolderDisplayNameOf(hsf,<\/font> pidl, uFlags, &amp;sr);\n if (SUCCEEDED(hr)) {\n  PTSTR pszName;\n  hr = StrRetToStr(&amp;sr, pidl, &amp;pszName);\n  if (SUCCEEDED(hr)) {\n   _tprintf(TEXT(\"%s = %s\\n\"), pszLabel, pszName);\n   CoTaskMemFree(pszName);\n  }\n }\n}\nvoid PrintDetail(\n    \/\/ <font COLOR=\"red\"><strike>IShellFolder2 *psf,<\/strike><\/font>\n    <font COLOR=\"blue\">HSHELLFOLDER hsf<\/font>,\n    PCUITEMID_CHILD pidl,\n    const SHCOLUMNID *pscid, PCTSTR pszLabel)\n{\n VARIANT vt;\n \/\/ HRESULT hr = <font COLOR=\"red\"><strike>psf-&gt;GetDetailsEx(<\/strike><\/font>pidl, pscid, &amp;vt);\n HRESULT hr = <font COLOR=\"blue\">GetShellFolderDetailsEx(hsf,<\/font> pidl, pscid, &amp;vt);\n if (SUCCEEDED(hr)) {\n  hr = VariantChangeType(&amp;vt, &amp;vt, 0, VT_BSTR);\n  if (SUCCEEDED(hr)) {\n   _tprintf(TEXT(\"%s: %ws\\n\"), pszLabel, V_BSTR(&amp;vt));\n  }\n  VariantClear(&amp;vt);\n }\n}\n<\/pre>\n<p>\nNot really all that different.\nLast function:\n<\/p>\n<pre>\nint __cdecl _tmain(int argc, PTSTR *argv)\n{\n HRESULT hr = CoInitialize(NULL);\n if (SUCCEEDED(hr)) {\n  \/\/ <font COLOR=\"red\"><strike>IShellFolder2 *psfRecycleBin;<\/strike><\/font>\n  <font COLOR=\"blue\">HSHELLFOLDER hsfRecycleBin;<\/font>\n  hr = BindToCsidl(CSIDL_BITBUCKET, <font COLOR=\"blue\">&amp;hsfRecycleBin<\/font>);\n  if (SUCCEEDED(hr)) {\n   \/\/ <font COLOR=\"red\"><strike>IEnumIDList *peidl;<\/strike><\/font>\n   <font COLOR=\"blue\">HENUMIDLIST heidl;<\/font>\n   \/\/ hr = <font COLOR=\"red\"><strike>psfRecycleBin-&gt;EnumObjects(<\/strike><\/font>NULL,\n   hr = <font COLOR=\"blue\">EnumShellFolderObjects(hsfRecycleBin,<\/font> NULL,\n     <a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2010\/04\/02\/9989235.aspx\">SHCONTF_FOLDERS | SHCONTF_NONFOLDERS<\/a>, &amp;heidl);\n   if (hr == S_OK) {\n    PITEMID_CHILD pidlItem;\n    \/\/ while (<font COLOR=\"red\"><strike>peidl-&gt;Next(<\/strike><\/font>1, &amp;pidlItem, NULL) == S_OK) {\n    while (<font COLOR=\"blue\">EnumerateNextShellFolderObject(heidl,<\/font> 1, &amp;pidlItem, NULL) == S_OK) {\n     _tprintf(TEXT(\"------------------\\n\"));\n     PrintDisplayName(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                      SHGDN_INFOLDER, TEXT(\"InFolder\"));\n     PrintDisplayName(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                      SHGDN_NORMAL, TEXT(\"Normal\"));\n     PrintDisplayName(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                      SHGDN_FORPARSING, TEXT(\"ForParsing\"));\n     PrintDetail(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                 &amp;SCID_OriginalLocation, TEXT(\"Original Location\"));\n     PrintDetail(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                 &amp;SCID_DateDeleted, TEXT(\"Date deleted\"));\n     PrintDetail(<font COLOR=\"blue\">hsfRecycleBin<\/font>, pidlItem,\n                 &amp;PKEY_Size, TEXT(\"Size\"));\n     CoTaskMemFree(pidlItem);\n    }\n   }\n   \/\/ <font COLOR=\"red\"><strike>psfRecycleBin-&gt;Release();<\/strike><\/font>\n   <font COLOR=\"blue\">DestroyShellFolder(hsfRecycleBin);<\/font>\n  }\n  CoUninitialize();\n }\n return 0;\n}\n<\/pre>\n<p>\nSo we see that flattening the API didn&#8217;t really change the code\nat all.\nYou&#8217;re still invoking methods on objects.\nWhether you use a flat API to do it or an object-based API\nis just changing the decorations.\nThe underlying logic doesn&#8217;t change.\n<\/p>\n<p>\nOne disadvantage of the flat version is that it requires everything\nto be mediated by the shell.\nInstead of invoking a method directly on the object,\nyou have to call the flat function in the shell,\nwhich then packages up the call and dispatches it,\nand the recipient then needs to unpack the parameters\n(possibly with help from the shell)\nbefore finally getting around to doing the actual work.\n<\/p>\n<p>\nIt also means that any interface change requires an operating system\nupgrade,\nsince the mediator (the shell) needs to understand the new interface.\n<\/p>\n<p>\nBut if this whole object-oriented syntax\nreally annoys you and you want a flat API,\nthen feel free to add the line\n<\/p>\n<pre>\n#define CINTERFACE\n<\/pre>\n<p>\nbefore including COM header files.\nIf you do that, then\nyou get the old flat C-style version of COM.\nInstead of the\n<code>p-&gt;Method(args)<\/code> new hotness,\nyou can stick to the old trustworthy\n<code>p-&gt;lpVtbl-&gt;Method(p, args)<\/code> version,\nor use the\n<code>InterfaceName_MethodName(p, args)<\/code> helper macro.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Steve Wolf suggests that the sample program would have been much simpler had the shell extension model been a flat Win32 interface. Okay, let&#8217;s try it. Since this is an extension model, each extension needs to specify the callbacks for each namespace operation. Perhaps it could have been done like this: HRESULT (CALLBACK *SHELLFOLDER_EXTENDHANDLER)( void [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[26],"class_list":["post-2313","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-other"],"acf":[],"blog_post_summary":"<p>Steve Wolf suggests that the sample program would have been much simpler had the shell extension model been a flat Win32 interface. Okay, let&#8217;s try it. Since this is an extension model, each extension needs to specify the callbacks for each namespace operation. Perhaps it could have been done like this: HRESULT (CALLBACK *SHELLFOLDER_EXTENDHANDLER)( void [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2313","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=2313"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2313\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=2313"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=2313"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=2313"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}