{"id":6543,"date":"2012-09-21T07:00:00","date_gmt":"2012-09-21T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/09\/21\/how-can-i-implement-safearray-tostring-without-going-insane\/"},"modified":"2012-09-21T07:00:00","modified_gmt":"2012-09-21T07:00:00","slug":"how-can-i-implement-safearray-tostring-without-going-insane","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120921-00\/?p=6543\/","title":{"rendered":"How can I implement SAFEARRAY.ToString() without going insane?"},"content":{"rendered":"<p>\nA colleague needed some help with manipulating <code>SAFEARRAY<\/code>s.\n<\/p>\n<blockquote class=\"q\"><p>\nI have some generic code to execute WMI queries and store the\nresult as strings.\nNormally,\n<code>Variant&shy;Change&shy;Type(VT_BSTR)<\/code> does the work,\nbut\n<code>Variant&shy;Change&shy;Type<\/code> doesn&#8217;t know how to\nconvert arrays (e.g. <code>VT_ARRAY&nbsp;| VT_INT<\/code>).\nAnd there doesn&#8217;t seem to be an easy way to convert the array\nelement-by-element because <code>Safe&shy;Array&shy;Get&shy;Element<\/code>\nexpects a pointer to an object of the\nunderlying type, so I&#8217;d have to write a switch statement\nfor each variant type.\nSurely there&#8217;s an easier way?\n<\/p><\/blockquote>\n<p>\nOne suggestion was to use\nthe ATL <code>CComSafeArray<\/code> template,\nbut since it&#8217;s a template, the underlying type of the array\nneeds to be known at compile time,\nbut we don&#8217;t know the underlying type until run time,\nwhich is exactly the problem.\n<\/p>\n<p>\nLet&#8217;s start with the big switch statement and then do some\noptimization.\nAll before we start typing,\nbecause after all the goal of this exercise is to avoid having\nto type out the massive switch statement.\n(Except that I have to actually type it so you have something to read.)\n<\/p>\n<p>\nHere&#8217;s the version we&#8217;re trying to avoid having to type:\n<\/p>\n<pre>\nHRESULT SafeArrayGetElementAsString(\n    SAFEARRAY *psa,\n    long *rgIndices,\n    LCID lcid, \/\/ controls conversion to string\n    unsigned short wFlags, \/\/ controls conversion to string\n    BSTR *pbstrOut)\n{\n  *pbstrOut = nullptr;\n  VARTYPE vt;\n  HRESULT hr = SafeArrayGetVartype(psa, &amp;vt);\n  if (SUCCEEDED(hr)) {\n    switch (vt) {\n    case VT_I2:\n      {\n        SHORT iVal;\n        hr = SafeArrayGetElement(psa, rgIndices, &amp;iVal);\n        if (SUCCEEDED(hr)) {\n          hr = VarBstrFromI2(iVal, lcid, wFlags, pbstrOut);\n        }\n      }\n      break;\n    case VT_I4:\n      {\n        LONG lVal;\n        hr = SafeArrayGetElement(psa, rgIndices, &amp;lVal);\n        if (SUCCEEDED(hr)) {\n          hr = VarBstrFromI4(lVal, lcid, wFlags, pbstrOut);\n        }\n      }\n      break;\n    ... etc for another dozen or so cases ...\n    ... and then special cases for things that need special handling ...\n    case VT_VARIANT:\n      {\n        VARIANT varVal;\n        hr = SafeArrayGetElement(psa, rgIndices, &amp;varVal);\n        if (SUCCEEDED(hr)) {\n          hr = VariantChangeTypeEx(&amp;varVal, &amp;varVal,\n                                   lcid, wFlags, VT_BSTR);\n          if (SUCCEEDED(hr)) {\n            *pbstrOut = varVal.bstrVal;\n          } else {\n            VariantClear(&amp;varVal);\n          }\n        }\n      }\n      break;\n    case VT_UNKNOWN:\n    case VT_DISPATCH:\n    case VT_BSTR: \/\/ other cases where we need to release the object\n      ... more special cases ...\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>\nThe first observation is that you can make\n<code>Variant&shy;Change&shy;Type<\/code> do the heavy lifting.\nJust read everything (whatever it is) into a variant, and then let\n<code>Variant&shy;Change&shy;Type<\/code> do the string conversion.\n<\/p>\n<pre>\nHRESULT SafeArrayGetElementAsString(\n    SAFEARRAY *psa,\n    long *rgIndices,\n    LCID lcid, \/\/ controls conversion to string\n    unsigned short wFlags, \/\/ controls conversion to string\n    BSTR *pbstrOut)\n{\n  *pbstrOut = nullptr;\n  VARTYPE vt;\n  HRESULT hr = SafeArrayGetVartype(psa, &amp;vt);\n  if (SUCCEEDED(hr)) {\n    VARIANT var;\n    switch (vt) {\n    case VT_I2:\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var.iVal);\n      if (SUCCEEDED(hr)) {\n        var.vt = vt;\n      }\n      break;\n    case VT_I4:\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var.lVal);\n      if (SUCCEEDED(hr)) {\n        var.vt = vt;\n      }\n      break;\n    case VT_R4:\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var.fltVal);\n      if (SUCCEEDED(hr)) {\n        var.vt = vt;\n      }\n      break;\n    ... etc for another dozen or so cases ...\n    ... there is just one special case now ...\n    case VT_VARIANT:\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var);\n      break;\n    default:\n      \/\/ an invalid array base type somehow snuck through\n      hr = E_INVALIDARG;\n      break;\n    }\n    if (SUCCEEDED(hr)) {\n      hr = VariantChangeTypeEx(&amp;var, &amp;var,\n                               lcid, wFlags, VT_BSTR);\n      if (SUCCEEDED(hr)) {\n        *pbstrOut = var.bstrVal;\n      } else {\n        VariantClear(&amp;var);\n      }\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>\nWe can get rid of the special cases for\n<code>VT_UNKNOWN<\/code>,\n<code>VT_DISPATCH<\/code>,\n<code>VT_RECORDINFO<\/code>,\nand\n<code>VT_BSTR<\/code>,\nsince <code>Variant&shy;Clear<\/code> will do the appropriate\ncleanup for us.\n<\/p>\n<p>\nYou can actually stop there, since the compiler will perform\nthe next optimization for us.\nBut since the goal is to save typing, we can perform the optimization\nmanually to save us from having to write out all those\n<code>Safe&shy;Array&shy;Get&shy;Element<\/code> calls.\n<\/p>\n<p>\nObserve that all the <code>var.iVal<\/code>,\n<code>var.lVal<\/code>,\n<code>var.fltVal<\/code>, etc., members\nare all unioned on top of each other.\nIn other words, the address of all the members is the same.\nWe can therefore merge all the cases together.\n(As noted, this is something the compiler will already do,\nso the goal here is not to create more efficient code but\njust to reduce typing.)<\/p>\n<p><pre>\nHRESULT SafeArrayGetElementAsString(\n    SAFEARRAY *psa,\n    long *rgIndices,\n    LCID lcid, \/\/ controls conversion to string\n    unsigned short wFlags, \/\/ controls conversion to string\n    BSTR *pbstrOut)\n{\n  *pbstrOut = nullptr;\n  VARTYPE vt;\n  HRESULT hr = SafeArrayGetVartype(psa, &amp;vt);\n  if (SUCCEEDED(hr)) {\n    VARIANT var;\n    switch (vt) {\n    case VT_I2:\n    case VT_I4:\n    case VT_R4:\n    case ... etc ...:\n      \/\/ All of the above cases store their data in the same place\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var.iVal);\n      if (SUCCEEDED(hr)) {\n        var.vt = vt;\n      }\n      break;\n    case VT_DECIMAL:\n      \/\/ Decimals are stored in a funny place.\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var.decVal);\n      if (SUCCEEDED(hr)) {\n        var.vt = vt;\n      }\n      break;\n    case VT_VARIANT:\n      \/\/ Variants too, because it obvious isn't a member of itself.\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;var);\n      break;\n    default:\n      \/\/ an invalid array base type somehow snuck through\n      hr = E_INVALIDARG;\n      break;\n    }\n    if (SUCCEEDED(hr)) {\n      hr = VariantChangeTypeEx(&amp;var, &amp;var,\n                               lcid, wFlags, VT_BSTR);\n      if (SUCCEEDED(hr)) {\n        *pbstrOut = var.bstrVal;\n      } else {\n        VariantClear(&amp;var);\n      }\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>\nAnd then you can generalize this function so it returns\na <code>VARIANT<\/code>,\nso that it becomes the caller&#8217;s responsibility to do the\n<code>Variant&shy;Change&shy;Type(VT_BSTR)<\/code>.\nThis also allows the caller to figure out how to deal\nwith things like <code>VT_UNKNOWN<\/code>, which\n<code>Variant&shy;Change&shy;Type<\/code> doesn&#8217;t know\nhow to handle.\n(Perhaps it should be converted to the string <code>\"[object]\"<\/code>.)\nOr maybe the caller might want to use this function to convert\nall <code>SAFEARRAY<\/code>s to\n<code>VT_ARRAY | VT_FIXEDBASETYPE<\/code>.\n<\/p>\n<pre>\nHRESULT SafeArrayGetElementAsVariant(\n    SAFEARRAY *psa,\n    long *rgIndices,\n    VARIANT *pvarOut)\n{\n  VariantInit(pvarOut);\n  VARTYPE vt;\n  HRESULT hr = SafeArrayGetVartype(psa, &amp;vt);\n  if (SUCCEEDED(hr)) {\n    switch (vt) {\n    case VT_I2:\n    case VT_I4:\n    case VT_R4:\n    case ...:\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;pvarOut-&gt;iVal);\n      if (SUCCEEDED(hr)) {\n        pvarOut-&gt;vt = vt;\n      }\n      break;\n    case VT_DECIMAL:\n      \/\/ Decimals are stored in a funny place.\n      hr = SafeArrayGetElement(psa, rgIndices, &amp;pvarOut-&gt;decVal);\n      if (SUCCEEDED(hr)) {\n        pvarOut-&gt;vt = vt;\n      }\n      break;\n    case VT_VARIANT:\n      \/\/ Variants too, because it obvious isn't a member of itself.\n      hr = SafeArrayGetElement(psa, rgIndices, pvarOut);\n      break;\n    default:\n      \/\/ an invalid array base type somehow snuck through\n      hr = E_INVALIDARG;\n      break;\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>\n<b>Exercise<\/b>:\nSince <code>decVal<\/code> is unioned against the <code>tagVARIANT<\/code>,\ncan we also collapse the <code>VT_DECIMAL<\/code> and <code>VT_VARIANT<\/code>\ncases together?\n<\/p>\n<p>\n<b>Exercise<\/b>:\nWhy is the final typing-saver (collapsing the case statements)\nvalid?\nDon&#8217;t we have to worry about the\npossibility that the <code>VARIANT<\/code> type may change in the future?\n<\/p>\n<p>\n<b>Exercise<\/b>: What defensive actions could be taken to protect\nagainst that possibility raised by the previous exercise?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A colleague needed some help with manipulating SAFEARRAYs. I have some generic code to execute WMI queries and store the result as strings. Normally, Variant&shy;Change&shy;Type(VT_BSTR) does the work, but Variant&shy;Change&shy;Type doesn&#8217;t know how to convert arrays (e.g. VT_ARRAY&nbsp;| VT_INT). And there doesn&#8217;t seem to be an easy way to convert the array element-by-element because Safe&shy;Array&shy;Get&shy;Element [&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":[25],"class_list":["post-6543","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A colleague needed some help with manipulating SAFEARRAYs. I have some generic code to execute WMI queries and store the result as strings. Normally, Variant&shy;Change&shy;Type(VT_BSTR) does the work, but Variant&shy;Change&shy;Type doesn&#8217;t know how to convert arrays (e.g. VT_ARRAY&nbsp;| VT_INT). And there doesn&#8217;t seem to be an easy way to convert the array element-by-element because Safe&shy;Array&shy;Get&shy;Element [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6543","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=6543"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6543\/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=6543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=6543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=6543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}