{"id":36853,"date":"2004-12-29T07:00:00","date_gmt":"2004-12-29T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2004\/12\/29\/using-fibers-to-simplify-enumerators-part-1-when-life-is-easier-for-the-enumerator\/"},"modified":"2004-12-29T07:00:00","modified_gmt":"2004-12-29T07:00:00","slug":"using-fibers-to-simplify-enumerators-part-1-when-life-is-easier-for-the-enumerator","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20041229-00\/?p=36853","title":{"rendered":"Using fibers to simplify enumerators, part 1: When life is easier for the enumerator"},"content":{"rendered":"<p><P>\nThe COM model for enumeration (enumeration objects)\nis biased towards making\nlife easy for the consumer and hard for the producer.\nThe enumeration object (producer)\nneeds to be structured as a state machine,\nwhich can be quite onerous for complicated enumerators,\nfor example, tree walking or composite enumeration.\n<\/P>\n<P>\nOn the other hand, the callback model for producer\n(used by most Win32 functions) is\nbiased towards making life easy for the enumerator and hard\nfor the consumer.\nThis time, it is the consumer that needs to be structured as\na state machine, which is more work if the consumer is doing\nsomething complicated with each callback.\n(And even if not, you have to create a context structure\nto pass state from the caller, through the enumerator,\nto the callback.)\n<\/P>\n<P>\nFor example, suppose we want to write a routine that\nwalks a directory structure, allowing the caller to\nspecify what to do at each decision point.\nLet&#8217;s design this first using the callback paradigm:\n<\/P>\n<PRE>\n#include &lt;windows.h&gt;\n#include &lt;shlwapi.h&gt;\n#include &lt;stdio.h&gt;<\/p>\n<p>enum FERESULT {\n FER_CONTINUE,      \/\/ continue enumerating\n                    \/\/ (if directory: recurse into it)\n FER_SKIP,          \/\/ skip this file\/directory\n FER_STOP,          \/\/ stop enumerating\n};<\/p>\n<p>enum FEOPERATION {\n FEO_FILE,          \/\/ found a file\n FEO_DIR,           \/\/ found a directory\n FEO_LEAVEDIR,      \/\/ leaving a directory\n};<\/p>\n<p>typedef FERESULT (CALLBACK *FILEENUMCALLBACK)\n    (FEOPERATION feo,\n     LPCTSTR pszDir, LPCTSTR pszPath,\n     const WIN32_FIND_DATA* pwfd,\n     void *pvContext);<\/p>\n<p>FERESULT EnumDirectoryTree(LPCTSTR pszDir,\n    FILEENUMCALLBACK pfnCB, void* pvContext);\n<\/PRE>\n<P>\nThe design here is that the caller calls\n<CODE>EnumDirectoryTree<\/CODE> and provides a callback function\nthat is informed of each file found and can decide\nhow the enumeration should proceed.\n<\/P>\n<P>\nDesigning this as a callback makes life much simpler\nfor the implementation of <CODE>EnumDirectoryTree<\/CODE>.\n<\/P>\n<PRE>\nFERESULT EnumDirectoryTree(\n    LPCTSTR pszDir,\n    FILEENUMCALLBACK pfnCB, void *pvContext)\n{\n FERESULT fer = FER_CONTINUE;\n TCHAR szPath[MAX_PATH];\n if (PathCombine(szPath, pszDir, TEXT(&#8220;*.*&#8221;))) {\n  WIN32_FIND_DATA wfd;\n  HANDLE hfind = FindFirstFile(szPath, &amp;wfd);\n  if (hfind != INVALID_HANDLE_VALUE) {\n   do {\n    if (lstrcmp(wfd.cFileName, TEXT(&#8220;.&#8221;)) != 0 &amp;&amp;\n        lstrcmp(wfd.cFileName, TEXT(&#8220;..&#8221;)) != 0 &amp;&amp;\n        PathCombine(szPath, pszDir, wfd.cFileName)) {\n     FEOPERATION feo = (wfd.dwFileAttributes &amp;\n                     FILE_ATTRIBUTE_DIRECTORY) ?\n                     FEO_DIR : FEO_FILE;\n     fer = pfnCB(feo, pszDir, szPath, &amp;wfd, pvContext);\n     if (fer == FER_CONTINUE) {\n      if (feo == FEO_DIR) {\n       fer = EnumDirectoryTree(szPath, pfnCB, pvContext);\n       if (fer == FER_CONTINUE) {\n        fer = pfnCB(FEO_LEAVEDIR, pszDir, szPath,\n                    &amp;wfd, pvContext);\n       }\n      }\n     } else if (fer == FER_SKIP) {\n      fer = FER_CONTINUE;\n     }\n    }\n   } while (FindNextFile(hfind, &amp;wfd));\n   FindClose(hfind);\n  }\n }\n return fer;\n}\n<\/PRE>\n<P>\nNote: I made no attempt to make this function at all efficient\nsince that&#8217;s not my point here.\nIt&#8217;s highly wasteful of stack space (which can cause problems\nwhen walking deep directory trees).\nThis function also doesn&#8217;t like paths deeper than <CODE>MAX_PATH<\/CODE>;\nfixing this is beyond the scope of this series.\nNor do I worry about\n<A HREF=\"\/oldnewthing\/archive\/2004\/12\/27\/332704.aspx\">\nreparse points<\/A>, which can induce infinite loops if you&#8217;re not\ncareful.\n<\/P>\n<P>\nWell, that wasn&#8217;t so hard to write. But that&#8217;s because we made\nlife hard for the consumer.  The consumer needs to maintain state\nacross each callback. For example, suppose you wanted to build\na list of directories and their sizes (both including and\nexcluding subdirectories).\n<\/P>\n<PRE>\nclass EnumState {\npublic:\n EnumState()\n   : m_pdirCur(new Directory(NULL)) { }\n ~EnumState() { Dispose(); }\n FERESULT Callback(FEOPERATION feo,\n    LPCTSTR pszDir, LPCTSTR pszPath,\n    const WIN32_FIND_DATA* pwfd);\n void FinishDir(LPCTSTR pszDir);<\/p>\n<p>private:<\/p>\n<p> struct Directory {\n  Directory(Directory* pdirParent)\n   : m_pdirParent(pdirParent)\n   , m_ullSizeSelf(0)\n   , m_ullSizeAll(0) { }\n  Directory* m_pdirParent;\n  ULONGLONG m_ullSizeSelf;\n  ULONGLONG m_ullSizeAll;\n };\n Directory* Push();\n void Pop();\n void Dispose();<\/p>\n<p> Directory* m_pdirCur;\n};<\/p>\n<p>EnumState::Directory* EnumState::Push()\n{\n Directory* pdir = new Directory(m_pdirCur);\n if (pdir) {\n  m_pdirCur = pdir;\n }\n return pdir;\n}<\/p>\n<p>void EnumState::Pop()\n{\n Directory* pdir = m_pdirCur-&gt;m_pdirParent;\n delete m_pdirCur;\n m_pdirCur = pdir;\n}<\/p>\n<p>void EnumState::Dispose()\n{\n while (m_pdirCur) {\n  Pop();\n }\n}<\/p>\n<p>void EnumState::FinishDir(LPCTSTR pszDir)\n{\n  m_pdirCur-&gt;m_ullSizeAll +=\n    m_pdirCur-&gt;m_ullSizeSelf;\n  printf(&#8220;Size of %s is %I64d (%I64d)\\n&#8221;,\n   pszDir, m_pdirCur-&gt;m_ullSizeSelf,\n   m_pdirCur-&gt;m_ullSizeAll);\n}<\/p>\n<p>ULONGLONG FileSize(const WIN32_FIND_DATA *pwfd)\n{\n  return \n    ((ULONGLONG)pwfd-&gt;nFileSizeHigh &lt;&lt; 32) +\n    pwfd-&gt;nFileSizeLow;\n}<\/p>\n<p>FERESULT EnumState::Callback(FEOPERATION feo,\n    LPCTSTR pszDir, LPCTSTR pszPath,\n    const WIN32_FIND_DATA* pwfd)\n{\n if (!m_pdirCur) return FER_STOP;<\/p>\n<p> switch (feo) {\n case FEO_FILE:\n  m_pdirCur-&gt;m_ullSizeSelf += FileSize(pwfd);\n  return FER_CONTINUE;<\/p>\n<p> case FEO_DIR:\n  if (Push()) {\n   return FER_CONTINUE;\n  } else {\n   return FER_SKIP;\n  }<\/p>\n<p> case FEO_LEAVEDIR:\n  FinishDir(pszPath);<\/p>\n<p> \/* Propagate size into parent *\/\n  m_pdirCur-&gt;m_pdirParent-&gt;m_ullSizeAll +=\n    m_pdirCur-&gt;m_ullSizeAll;\n  Pop();\n  return FER_CONTINUE;<\/p>\n<p> default:\n  return FER_CONTINUE;\n }\n \/* notreached *\/\n}<\/p>\n<p>FERESULT CALLBACK EnumState_Callback(\n    FEOPERATION feo,\n    LPCTSTR pszDir, LPCTSTR pszPath,\n    const WIN32_FIND_DATA* pwfd,\n    void* pvContext)\n{\n EnumState* pstate =\n    reinterpret_cast&lt;EnumState*&gt;(pvContext);\n return pstate-&gt;Callback(feo, pszDir,\n            pszPath, pwfd);\n}<\/p>\n<p>int __cdecl main(int argc, char **argv)\n{\n EnumState state;\n if (EnumDirectoryTree(TEXT(&#8220;.&#8221;),\n        EnumState_Callback,\n        &amp;state) == FER_CONTINUE) {\n  state.FinishDir(TEXT(&#8220;.&#8221;));\n }\n return 0;\n}\n<\/PRE>\n<P>\nBoy that sure was an awful lot of typing, and what&#8217;s\nworse, the whole structure of the program has\nbeen obscured by the explicit state management.\nIt sure is hard to tell at a glance what this\nchunk of code is trying to do.\nInstead, you have to stare at the <CODE>EnumState<\/CODE> class\nand reverse-engineer what&#8217;s going on.\n<\/P>\n<P>\n(Yes, I could have simplified this code a little by\nusing a built-in stack class, but as I have already noted\nin the context of smart pointers,\nI try to present these articles in\n&#8220;pure&#8221; C++ so people won&#8217;t get into arguments about which\nclass library is best.)\n<\/P>\n<P>\nTomorrow, we&#8217;ll look at how the world would be\nif the function <CODE>EnumDirectoryTree<\/CODE> were spec&#8217;d out\nby the caller rather than the enumerator!\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The COM model for enumeration (enumeration objects) is biased towards making life easy for the consumer and hard for the producer. The enumeration object (producer) needs to be structured as a state machine, which can be quite onerous for complicated enumerators, for example, tree walking or composite enumeration. On the other hand, the callback model [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-36853","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The COM model for enumeration (enumeration objects) is biased towards making life easy for the consumer and hard for the producer. The enumeration object (producer) needs to be structured as a state machine, which can be quite onerous for complicated enumerators, for example, tree walking or composite enumeration. On the other hand, the callback model [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36853","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=36853"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36853\/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=36853"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=36853"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=36853"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}