0

I'm writing an Excel plugin using ExcelDna. I want to locate the directory the assembly was installed in so that I can open files from that directory. I have successfully used this approach in the past, but it does not work after upgrading to .NET Framework 4.7.1. I'm trying to figure out why.

Here's what is currently in my plugin:

var codePath = Assembly.GetExecutingAssembly().CodeBase;

When I hover over it in the debugger, it gives the right path. After I execute the statement codePath has the location of mscorlib (and hovering over it with the debugger still has the right location). I've also tried variants using typeof(<typeWithNoIneritance>).Assembly.CodeBase and using Location instead of CodeBase.

What should I be looking at next?

UPDATE The problem was likely not .NET Framework update, but an ExcelDna update that I did at the same time. It was loading the file from a byte array, which cannot give file location information. I was able to disable it and it is now working.

tyson
  • 166
  • 1
  • 9
  • can you use something other than `GetExecutingAssembly`? – Daniel A. White Feb 02 '18 at 19:53
  • From MSDN `CodeBase` documentation: [`If the assembly was loaded as a byte array, using an overload of the Load method that takes an array of bytes, this property returns the location of the caller of the method, not the location of the loaded assembly.`](https://msdn.microsoft.com/en-us/library/system.reflection.assembly.location(v=vs.110).aspx) Could be worth checking out how ExcelDna loads plugins – Jan-Peter Vos Feb 02 '18 at 20:11

2 Answers2

4

UPDATE

After digging further (see comments to this answer), it looks like Excel, or whatever mechanism is loading your plugin at runtime, is using the Assembly.Load method that takes a byte[] instead of a file. (In other words, it's first loading the assembly into memory as a byte[], then loading it into the AppDomain). Because of this, the .NET runtime doesn't actually know the file location, so will return an empty string for .Location (doc) or the caller's codebase for .CodeBase (doc).

Unfortunately, that means there's not really a good way to determine where the file came from via Reflection.

Previous Answer

It sounds like your code is getting JIT inline optimized, so I would try pulling the lookup into a separate method and adding an attribute to instruct the CLR not to inline that method.

    [MethodImpl(MethodImplOptions.NoInlining)]
    public string GetLocation()
    {
        return this.GetType().Assembly.Location;
    }

If you're looking for the path of the assembly that contains that line of code, you'll want to use

this.GetType().Assembly.Location

For more on the difference between CodeBase and Assembly, checkout https://blogs.msdn.microsoft.com/suzcook/2003/06/26/assembly-codebase-vs-assembly-location/

John M. Wright
  • 4,477
  • 1
  • 43
  • 61
  • I've tried this. This gives me mscorlib as I referenced in the description. – tyson Feb 02 '18 at 20:01
  • @tyson Sounds like JIT inlining is your problem. Check out my updated answer – John M. Wright Feb 02 '18 at 20:05
  • Thanks for the suggestion. I tried it. Now I get back an empty string. – tyson Feb 02 '18 at 20:17
  • what do you get for `this.GetType().Assembly.FullName`? – John M. Wright Feb 02 '18 at 20:19
  • FYI: That implies that the assembly is being loaded via a bytes instead of directly from the file. (see: https://msdn.microsoft.com/en-us/library/system.reflection.assembly.location%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396). "If the assembly is loaded from a byte array, such as when using the Load(Byte[]) method overload, the value returned is an empty string ("").)" – John M. Wright Feb 02 '18 at 20:22
  • It's likely that `.CodeBase` will return the wrong thing too, since it has similar behavior when the assembly is loaded from bytes: "If the assembly was loaded as a byte array, using an overload of the Load method that takes an array of bytes, this property returns the location of the caller of the method, not the location of the loaded assembly." – John M. Wright Feb 02 '18 at 20:24
  • Given that, I don't know of a way to find out the original path of the assembly, since it was loaded first into a byte[], then that was used to load the assembly into the AppDomain. From the CLR's point of view, this assembly didn't exist on the filesystem. – John M. Wright Feb 02 '18 at 20:25
  • This is what I get, which is what I'm expecting. "Epa.Plugin.Excel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=null" It may be that the package that's acting as the interface between my C# code and Excel is doing something like this. – tyson Feb 02 '18 at 20:26
  • 1
    I found there's an option called "LoadFromBytes" that was set to true. Now that I've disabled that, it seems to work. I looked back and realized I had updated ExcelDna at the same time as the framework. If you'd like to update your answer, I'll accept it as the solution. Thanks for the help. – tyson Feb 02 '18 at 20:30
1

Use Assembly.Location instead.

var codePath = Assembly.GetExecutingAssembly().Location;

The CodeBase is a URL to the place where the file was found, while the Location is the path from where it was actually loaded.

Quoted from here.

Lucas
  • 413
  • 1
  • 7
  • 15
  • This is not a question about whether to use `CodeBase` or `Location` as I have tried both as I mention in the description. I can parse the URL. This is a question about why I'm getting mscorlib instead of my assembly. – tyson Feb 02 '18 at 20:02
  • Ah, my bad. My guess then is, that the executing assembly is not your assembly. I would try John's answer then. – Lucas Feb 02 '18 at 20:06