{"id":112303,"date":"2026-05-06T07:00:00","date_gmt":"2026-05-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112303"},"modified":"2026-05-06T09:42:49","modified_gmt":"2026-05-06T16:42:49","slug":"20260506-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260506-00\/?p=112303","title":{"rendered":"Why not have changes in API behavior depend on the SDK you link against?"},"content":{"rendered":"<p>Some time ago, I noted that <a title=\"The Co\u00adInitialize\u00adSecurity function demands an absolute security descriptor\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240902-00\/?p=110201\"> the <code>Co\u00adInitialize\u00adSecurity<\/code> function demands an absolute security descriptor<\/a>, even though many functions in Windows produce self-relative security descriptors, forcing you to perform a relative-to-absolute conversion, even though the function internally just converts it back from absolute to relative.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240902-00\/?p=110201&amp;commentid=141890#comment-141890\"> Commenter tbodt wrote<\/a>,<\/p>\n<blockquote class=\"q\"><p>This one seems easy enough to fix by Apple&#8217;s technique of giving the function the old behavior when the program is linked against the old SDK.<\/p><\/blockquote>\n<p>This sure sounds easy. If your program links with the newer SDK, then it gets the new behavior of accepting self-relative security descriptors. But if it links with the old SDK, then it gets the old behavior of requiring absolute security descriptors. If you want the new behavior, then you link with the new SDK.<\/p>\n<p>This does create a subtlety that if you choose the wrong SDK to link against, everything still builds, but the results are different. Traditionally, Windows SDKs are forward-compatible: You can take an old program and link it against a newer SDK, and it will work exactly the same because the old program uses only the backward-compatible subset of the newer SDK. If you change behavior based on the SDK version that you link with, then it may not be obvious that the change in behavior you are experiencing is due to having upgraded the SDK libraries.<\/p>\n<p>Also, what if a program is linked with one version of the SDK, but a DLL that it uses is linked with a different version of the SDK? Maybe you&#8217;re using a UI framework library that hasn&#8217;t seen any need to update to the newer SDK. Or maybe your program is the one using an old version of the SDK, but the UI framework library is using the newer one. Do you let the main program&#8217;s SDK version dictate the behavior of the function, even though the DLL is expecting different behavior? The poor DLL is going to call <code>Co\u00adInitialize\u00adSecurity<\/code>, and it won&#8217;t behave the way it expects.<\/p>\n<p>Okay, so maybe you decide that the function changes its behavior not based on the program&#8217;s linked SDK version but rather the version of the calling DLL. But how does a function know which DLL called it? You might say, &#8220;Well, you can look at which DLL the return address belongs to.&#8221; But that doesn&#8217;t work in the case of tail call optimization.<\/p>\n<pre>\/\/ some function in a DLL\r\nHRESULT InitializeWidgets(\r\n    UINT maxWidgets,\r\n    const WIDGET_ID* ownerId,\r\n    PCWSTR ownerDescription,\r\n    PCWSTR countainerName,\r\n    PCWSTR containerDescription,\r\n    COLORREF defaultColor,\r\n    UINT defaultWidth,\r\n    UINT defaultHeight,\r\n    bool isRemoteAccessible,\r\n    bool isPersistent)\r\n{\r\n    \u27e6 various initialization steps \u27e7\r\n\r\n    static BYTE sd[] = { 0x01, \u27e6 hard-coded values \u27e7 };\r\n\r\n    return CoInitializeSecurity(sd, -1, nullptr, nullptr,\r\n                                RPC_C_AUTHN_LEVEL_DEFAULT,\r\n                                RPC_C_IMP_LEVEL_IDENTIFY,\r\n                                nullptr, EOAC_NONE, nullptr);\r\n}\r\n<\/pre>\n<p>That final call to <code>Co\u00adInitialize\u00adSecurity<\/code> could be optimized into a tail call, in which case the subroutine call instruction changes to an unconditional branch, with the return address being the address of <code>Initialize\u00adWidget<\/code>&#8216;s caller. If <code>Co\u00adInitialize\u00adSecurity<\/code> snooped at its return address, it would be checking the SDK version of the wrong DLL.<\/p>\n<p>Conversely, what if the function in the DLL is just a wrapper?<\/p>\n<pre>HRESULT CoInitializeSecuritywithLogging(\r\n    _In_opt_ PSECURITY_DESCRIPTOR pSecDesc,\r\n    _In_ LONG cAuthSvc,\r\n    _In_reads_opt_(cAuthSvc) SOLE_AUTHENTICATION_SERVICE* asAuthSvc,\r\n    _In_opt_ void* pReserved1,\r\n    _In_ DWORD dwAuthnLevel,\r\n    _In_ DWORD dwImpLevel,\r\n    _In_opt_ void* pAuthList,\r\n    _In_ DWORD dwCapabilities,\r\n    _In_opt_ void* pReserved3)\r\n{\r\n    if (dwCapabilities &amp; EOAC_APPID) {\r\n        LogUuid(\"CoInitializeSecurity with APPID\", (UUID*)pSecDesc);\r\n    } else if (dwCapabilities &amp; EOAC_ACCESS_CONTROL) {\r\n        Log(\"CoInitializeSecurity with IAccessControl\");\r\n    } else {\r\n        LogSecurityDescriptor(\"CoInitializeSecurity with security descriptor\", pSecDesc);\r\n    }\r\n    HRESULT hr = CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1,\r\n                        dwAuthnLevel, dwImpLevel, pAuthList, dwCapabilities, pReserved3);\r\n    Log(\"CoInitializeSecurity returned\", hr);\r\n}\r\n<\/pre>\n<p>If you look at the return address, you will find the wrapper function and change your behavior to match the version that the wrapper function was built with, but that wrapper function is just passing through the parameters from its caller. It&#8217;s really the caller whose behavior we want to match, not the wrapper.<\/p>\n<p>And what if the library is a static library rather than a DLL? It was written for one version of the SDK, but you link to another, and the behavior changes, and even if the function checks the return address, it will get the DLL&#8217;s address and see the DLL&#8217;s SDK version rather than the version the library wanted.<\/p>\n<p>Changing behavior based on the SDK version you link to works only if programs are monolithic.<\/p>\n<p><b>Bonus chatter<\/b>: Changing to a newer SDK&#8217;s <i>header files<\/i> do create behavioral changes because, for example, structures with an explicit size member might get extended to contain additional fields, and the API uses the value of the size member to decide which version of the SDK the caller is using. But this is not dependent on the SDK that the caller links to, which is a good thing, because it lets you take static libraries which use different versions of the SDK header files and link them all together into a single program or DLL, and they will still work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Static libraries don&#8217;t stand a chance.<\/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-112303","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Static libraries don&#8217;t stand a chance.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112303","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=112303"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112303\/revisions"}],"predecessor-version":[{"id":112304,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112303\/revisions\/112304"}],"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=112303"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112303"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112303"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}