locked
XLL Add-in cannot find DLLs at start-up but Add-in manager fine RRS feed

  • Question

  • I'm developing an XLL-based add-in and it's a multi-module project made up of around 6 different DLLs (that I have written).  This Add-in is supposed to be XCOPY-installable, i.e. not require any changes to the registry, PATH etc. so it can be deployed and installed from network drives by users without Administrator permissions.  To achieve this I have used, with mixed success, Side-by-side assemblies and embedded manifests (i.e. registration-free COM) in all my DLLs.  

    My problem is that although my Add-in works fine after adding it via the Add-in Manager, subsequently starting Excel misidentifies the XLL as having an incorrect extension and offers to load it anyway, at which point it proceeds to load it as a spreadsheet (with the junk cells including a message about it not being a DOS-mode application or something similar).

    My research has concluded this is due to the Add-in Manager setting the current directory to the directory containing the XLL prior to registering the XLL, meaning the DLL search path finds my files because the chain includes the current directory.  When I open Excel after installation, it does NOT set the current directory to my install directory, so any referred to DLLs are not found.  What I'm expecting to happen, but doesn't appear to work, is that the calls to LoadLibrary would be intercepted by the SxS Activation Context (which is supposed to be implicitly created and populated from the embedded manifest), but that doesn't appear to be happening.  I'm currently exploring all kinds of hacks to try and get around this problem, but I really want a proper solution.

    Hacky solutions include:

    1. Turning on delay-DLL loading and setting the CWD and calling SetDllDirectory in xlAutoOpen with the full path of the XLL (which works at least some of the time, but I'm getting exceptions from the delay load helper, presumably as Excel can change the working directory back at any time).
    2. Adding a hook to LoadLibrary to search my path using one of the popular hooks libraries - I don't like this idea as I thought hooks don't work on never versions of windows, although the libs might get around that.  It's horrible anyway.
    3. Trying to change the working directory/DLL directory in DllMain, but I'm not sure if the DLL load by the stubs happens prior to that (in the preferred case when delay loading is turned off).
    4. Doing some manual thing with the activation context?  Most of my DLL loads would be first used from a background thread, perhaps that doesn't use the same activation context?  I have tried creating a context before, but without any success - from the documents I read it's not necessary if you know what you're linking to at compile-time.
    5. Creating a stub XLA that sets the CWD before registering the XLL.  I want to avoid this if possible as it could be confusing to the user (as they never read the installation instructions).

    Things I've tried:

    1. Checking the Event log for SxS errors (there were some but are not now).
    2. Running sxstrace, appears to find all my dependencies fine, but says nothing about DLL loading.
    3. Running procmon - no obvious errors, some reference to the DLLs, but nothing about failing to load the DLLs.
    4. Running DependencyWalker - reports a few things deep inside windows DLLs, but looks like a red herring and apparently doesn't support SxS anyway.
    5. Adding my directory to the PATH - this makes things start working (it blows up later because I use relative paths somewhere, but that's solvable), which is why I think it's a DLL loading issue.
    6. Starting Excel in safe-mode.

    Any pointers as to what I'm missing here, or suggestions for a work-around, would be greatly appreciated!

    Background information

    My top level manifest looks like this:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <assemblyIdentity type="win32" name="client" version="1.0.0.0" />
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="core" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="helper" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="jni" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="local" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
       <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="settings" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
      <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="utils" version="1.0.0.0" />
        </dependentAssembly>
      </dependency>
      <file name="excel.xll"></file>
    </assembly>

    For each of the dependent assemblies there is then a file like this:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <assemblyIdentity name="jni" type="win32" version="1.0.0.0"></assemblyIdentity>
      <file name="jni.dll"></file>
    </assembly>

    All the DLLs are in the same deployment directory as the XLL.

    One of my DLLs provides a range of COM interfaces and other types (UDTs/structs also, more on that later) and so has a more complex manifest than the others.  

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <assemblyIdentity name="core" type="win32" version="1.0.0.0"></assemblyIdentity>
      <file name="core.dll" hashalg="SHA1">
        <comClass clsid="{34B9DE97-D756-4D18-A65C-E7A688CDF3E7}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" description="Library entry point"></comClass>
        <typelib tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" version="1.0" helpdir="" flags="HASDISKIMAGE"></typelib>
      </file>
      <comInterfaceExternalProxyStub name="IClasspathEntry" iid="{9E95046B-1A36-4D05-838E-E44F11213503}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{9E95046B-1A36-4D05-838E-E44F11213503}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IUnknown" iid="{00000000-0000-0000-C000-000000000046}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{00000000-0000-0000-C000-000000000046}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IClasspath" iid="{F0F716D2-27D9-4679-8D05-7889F7959FF4}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{F0F716D2-27D9-4679-8D05-7889F7959FF4}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IClassFolder" iid="{B646B5BB-26E9-4AC2-ABC3-863EAF4F5348}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{B646B5BB-26E9-4AC2-ABC3-863EAF4F5348}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IDirectoryWriter" iid="{1F894BF1-99A4-4C25-8DAB-E212D9D46AE3}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{1F894BF1-99A4-4C25-8DAB-E212D9D46AE3}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IFileWriter" iid="{E0A6756D-B91A-497F-9980-7DE1213B9243}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{E0A6756D-B91A-497F-9980-7DE1213B9243}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJarFile" iid="{E9FEE3F8-F1B7-47C7-B85E-4E40259FC43F}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{E9FEE3F8-F1B7-47C7-B85E-4E40259FC43F}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IClasspathEntries" iid="{7087CF73-6691-4A27-B54A-B1AE4A9FAB35}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{7087CF73-6691-4A27-B54A-B1AE4A9FAB35}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJniSequence" iid="{BF027FD9-F1B6-4B85-80CD-8DFB2CBD681F}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{BF027FD9-F1B6-4B85-80CD-8DFB2CBD681F}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJvm" iid="{CF3B6BCA-AFC3-4337-9529-E340A9B7808E}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{CF3B6BCA-AFC3-4337-9529-E340A9B7808E}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJvmTemplate" iid="{9C3C1F6F-EB49-4B4D-B57C-2EAA66ED2920}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{9C3C1F6F-EB49-4B4D-B57C-2EAA66ED2920}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJvmConnector" iid="{E41AFC88-0617-485A-9DE8-393619D921B9}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{E41AFC88-0617-485A-9DE8-393619D921B9}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJvmContainer" iid="{249D1804-C433-4E15-A1C8-F7158E316E1E}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{249D1804-C433-4E15-A1C8-F7158E316E1E}"></comInterfaceExternalProxyStub>
      <comInterfaceExternalProxyStub name="IJvmSupport" iid="{9D2641B2-579D-4A15-8B63-3CB01DDCC0C4}" tlbid="{0E07A0B8-0FA3-4497-BC66-6D2AF2A0B9C8}" proxyStubClsid32="{9D2641B2-579D-4A15-8B63-3CB01DDCC0C4}"></comInterfaceExternalProxyStub>
    </assembly>

    I was somewhat dismayed to find that the assembly manifest format doesn't support the specification of UDTs (structs) exported as part of a type library, which I am using (I need to get an IRecordInfo for each struct so they can be part of a SAFEARRAY).  To get around this I have manually loaded the type library tlb file and searched it for the appropriate metadata.  That works now, even if it's a little inconvenient.

    All manifests appear as resource 2 in their files (checked with ResourceHacker).





    • Edited by Jim Moores Wednesday, July 13, 2016 4:41 PM mistake
    Wednesday, July 13, 2016 4:36 PM

Answers

  • I suggest that in the xlAutoOpen you call LoadLibrary yourself, passing in the full path of each library, which you construct from the .xll path you have at runtime. (In the debugger you'll easily see whether this worked).

    You can either load a fixed list, or load all the .dll files in the same directory.

    Once LoadLibrary has fond and loaded the .dll, any later implicit load will find it already in the process, and not go looking again.

    That way you don't have to fiddle with the fragile current directory.

    -Govert

    Excel-DNA - Free and easy .NET for Excel

    • Marked as answer by Jim Moores Friday, August 19, 2016 2:41 PM
    Sunday, July 17, 2016 7:46 PM

All replies

  • Hi Jim Moores,

    Since this issue is complex, I'm trying to involve some senior engineers into this issue and it will take some time. Your patience will be greatly appreciated.

    Sorry for any inconvenience and have a nice day!

    Thursday, July 14, 2016 9:36 AM
  • Thanks for the update, look forward to hearing more.
    Thursday, July 14, 2016 2:12 PM
  • I suggest that in the xlAutoOpen you call LoadLibrary yourself, passing in the full path of each library, which you construct from the .xll path you have at runtime. (In the debugger you'll easily see whether this worked).

    You can either load a fixed list, or load all the .dll files in the same directory.

    Once LoadLibrary has fond and loaded the .dll, any later implicit load will find it already in the process, and not go looking again.

    That way you don't have to fiddle with the fragile current directory.

    -Govert

    Excel-DNA - Free and easy .NET for Excel

    • Marked as answer by Jim Moores Friday, August 19, 2016 2:41 PM
    Sunday, July 17, 2016 7:46 PM
  • I assume you mean to do with along with using delay-loading of DLLs.  That's a pretty good suggestion, thanks!  

    I have been pondering why the manifests don't let you control the path relative to the containing file, but I suppose that it's more designed for a full application where you can control everything rather than an extension DLL, which is what an XLL really is.

    I'll give it a shot and report back, but I don't foresee any problems with that solution.

    Monday, July 18, 2016 12:03 PM
  • Apologies for the delay, but I can confirm that your suggestion fixed my issue, thanks so much.

    Best Regards,

    Jim

    Friday, August 19, 2016 2:43 PM