{"id":95265,"date":"2017-01-26T07:00:00","date_gmt":"2017-01-26T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=95265"},"modified":"2019-03-13T01:05:12","modified_gmt":"2019-03-13T08:05:12","slug":"20170126-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20170126-00\/?p=95265","title":{"rendered":"How can I control the directory from which my delay-loaded DLL is loaded?"},"content":{"rendered":"<p>A customer had a DLL that is a COM in-process server. This DLL gets loaded by arbitrary client applications, and it also uses the <code>\/DELAYLOAD<\/code> linker flag to delay-load many of the DLLs which it depends upon. The customer observed that these delay-loaded DLLs were being loaded according to the standard DLL-loading search algorithm.&sup1; The problem is that the DLL which they are dependent upon is not in the standard search path; it&#8217;s in its own private directory. <\/p>\n<p>The customer explained that they are working around this problem by providing a custom <a HREF=\"https:\/\/msdn.microsoft.com\/library\/09t6x5ds.aspx\">delay-load helper function<\/a> which calls <code>Set&shy;Dll&shy;Directory<\/code> to add the private directory to the DLL search path. <\/p>\n<p>The customer wanted to know whether <code>Set&shy;Dll&shy;Directory<\/code> affects the DLL search path for the entire process. The customer doesn&#8217;t want to affect the DLL search path for the entire process because the DLL is <a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20110803-00\/?p=9983\">a guest in the host process<\/a>, so it shouldn&#8217;t be <a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20091202-00\/?p=15823\">changing the carpet<\/a>. (Hey, at least they&#8217;re not <a HREF=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/\">calling in a demolition team<\/a>.) &#8220;If it affects only the DLL we need to load, then it looks like <code>Set&shy;Dll&shy;Directory<\/code> is what we need. But if it affects the entire process, then we would have to switch to <code>Add&shy;Dll&shy;Directory<\/code>.&#8221; <\/p>\n<p>Yes, the <code>Set&shy;Dll&shy;Directory<\/code> function affects the DLL search path for the entire process. It&#8217;s not clear what the customer&#8217;s mental model is for &#8220;affects only the DLL we need to load&#8221;, seeing as you don&#8217;t actually pass <code>Set&shy;Dll&shy;Directory<\/code> the name of the DLL you need to load, so it has no idea which DLL to apply this path to. <\/p>\n<p>The customer&#8217;s proposed alternative of using <code>Add&shy;Dll&shy;Directory<\/code> doesn&#8217;t solve the problem, because it too affects the DLL search path for the entire process. Maybe they were thinking of calling <code>Add&shy;Dll&shy;Directory<\/code> to add the private directory, then calling <code>Remove&shy;Dll&shy;Directory<\/code> to remove it at some unspecified point in the future. But that creates a window in which the process DLL path has the private directory, and if another thread also calls <code>Load&shy;Library<\/code>, it will see that other private directory, which is presumably unwanted. <\/p>\n<p>The customer is making things too hard for themselves by manipulating the DLL search paths. Since they know what directory the DLL is in, they don&#8217;t need to do any searching at all. When the notification handler is called, it is given <a HREF=\"https:\/\/msdn.microsoft.com\/library\/f0fkfy9y.aspx\">a few pieces of information<\/a>. <\/p>\n<ul>\n<li>The reason why the handler is being called.<\/li>\n<li>Information about the DLL being loaded.<\/li>\n<\/ul>\n<p>Since the customer already has a custom handler, they can just write their custom handler like this: <\/p>\n<pre>\nFARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)\n{\n if (dliNotify == dliNotePreLoadLibrary &amp;&amp;\n     StrCmpIC(pdli-&gt;szDll, \"special.dll\") == 0)\n {\n  return LoadTheSpecialDll();\n }\n ...\n}\n\nHMODULE LoadTheSpecialDll()\n{\n .. calculate the full path to the special DLL in its\n .. private directory\n return LoadLibrary(fullPathToSpecialDll);\n}\n<\/pre>\n<p>If the notification handler is being told that we are about to load <code>special.dll<\/code>, then load the special DLL using whatever custom algorithm you need, and return that handle. The delay-load library will use that module instead of trying to load via the standard DLL search directory. There&#8217;s no need to mess around with <code>Get<\/code>\/<code>Set&shy;Dll&shy;Directory<\/code>, which is a good thing, since that avoids applying a global solution to a local problem. <\/p>\n<p>&sup1; This is explained in the documentation, because it says that the default delay-load helper function calls the <code>Load&shy;Library<\/code> function, which is subject to the standard DLL search path. Though technically, it calls <code>Load&shy;Library&shy;Ex<\/code> and passes a flags value of 0, which is functionally equivalent. You can see this and more in the file <code>delayhlp.cpp<\/code> in the <code>VC\\Include<\/code> directory. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Use the hook.<\/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-95265","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Use the hook.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/95265","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=95265"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/95265\/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=95265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=95265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=95265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}