.net assembly versioning woes


  • Discourse touched me in a no-no place

    Check this out. I think I may have discussed this last year on these or the old forums, but I'm not sure. I have a Progress[1] program that calls an intermediate .Net DLL that calls another .Net application. The intermediate DLL links against certain assemblies in the other application to retrieve objects back. ("Please give me product xyz". "Sure, here is the product record, a data set representing the parts list, which assembly lines it can be built on, what plants currently build it, blah blah blah whatever". The problem is that when I build and link the intermediate DLL, the references are to the specific versions of the application's assemblies. I have a test environment with, let's say, version 5.0.133 of the application. But they have since released 5.0.300, 5.1.117, and 5.2.200, and will, presumably, release other versions in the future. Is there a reasonable way to link my assembly against the other assemblies so that when it's used, it doesn't care what (minor) versions of the application it tries to connect to? That is, I need my DLL to be able to work with any 5.x version of the other application. To date, they have built the DLL for me against 3 minor versions, but they won't do that forever.

    [1] that bit's probably not relevant.


  • Winner of the 2016 Presidential Election

    a) your posts reads like a spam bots best work to date
    b) wrong category? Try Coding Help for (friendly) advice and maybe even help :D
    c) [1]

    [1] this bullet point is probably not relevant.

    Filed Under: Can't help you, though



  • Can you link to non-specific versions? Obviously yes! If nothing else you can remove the version attribute in the project file...




  • Discourse touched me in a no-no place

    @Magus said:

    Can you link to non-specific versions? Obviously yes! If nothing else you can remove the version attribute in the project file...

    That's what they told me, and they sent me a build that they claimed did that. But it didn't.

    I do have the full solution--can you tell me how, in VS2013, to "remove the version attribute in the project file?" My DLL's version assemblyinfo.cs is 1.0.0.0, which is entirely unrelated to the other dlls that mine references.



  • I'd open the .csproj in notepad or something. That's where the references are. Ideally, you can just go to the properties of a reference in the solution explorer and turn off 'require specific version'. I know you generally have to do that with test projects that use Fakes, if your company upgrades your build server and your devs' VS separately.

    That should be enough, but there could be more to it.


  • FoxDev

    • open project
    • find reference
    • view properties of reference
    • make sure specific version is false
    • ???
    • win!

  • Discourse touched me in a no-no place

    I've seen that link before, @ragnax, but my day job isn't as a .net programmer, and I'm not sure how to apply it to my situation. I would like my dll to be able to bind against any 5.x version of the dlls I need to bind against. I guess the <bindingRedirect> option is what I want but I'm not 100% sure where to apply it; I guess in the Progress executable[1]'s manifest? That's not really optimal for raisins.

    [1] which is where my dll will get loaded.



  • @FrostCat said:

    but I'm not 100% sure where to apply it; I guess in the Progress executable[1]'s manifest? That's not really optimal for raisins.

    In the app.config or web.config file (depending on what kind of application you're building). It's just plaintext editable XML; no fuss editing it, and it's completely local to that one application.


  • Discourse touched me in a no-no place

    I'm building a DLL and it doesn't have an app.config. The application that would be loading my dll doesn't have one either, although there's a manifest.

    I noticed my project has "specific version" set to false. When I build it and load it in IL DASM, I can see that the references still look like this:

    {
      .publickeytoken = ( ...)
      .ver 5:0:133:0
    }```
    
    Does that matter?  Unfortunately I have only one version of the target application to test with, so I actually have to find a customer who's willing to be a guinea pig to test any of this against.
    
    I'm actually considering scrapping this entire approach and just ODBCing directly into the MSSQL database.  The drawback is that's going to require me to rewrite about 50 files, and I will be ignoring the target application's security model.


  • @FrostCat said:

    The application that would be loading my dll doesn't have one either

    Then make one. The framework will load the app.config and honor binding redirects even if the author of the program has done nothing to enable it.



  • @FrostCat said:

    I'm building a DLL and it doesn't have an app.config. The application that would be loading my dll doesn't have one either, although there's a manifest.

    Ok. Didn't realize you were the library producer and not the consumer.

    Assembly redirects are under the control of the process that loads the app domain, i.e. , the application executable, and not under the control of the assemblies loaded into the app domain. You could try using a publisher policy, but I think that requires your assemblies (or atleast their policies) to be GAC-ed.

    Are you delivering an assembly to be used with third-party programs; as in a plugin, or are you delivering them to be used with third-party code; as in being used to build an application. In case of the latter; VS2013 and up should already add binding redirects when it detects versioning conflicts related to assemblies built against multiple versions of other assemblies within the same project.

    (This actually is all still in the same segment of MSDN documentation that I already linked you to...)



  • So what's the WTF? This looks like Coding Help! GASP!


  • Discourse touched me in a no-no place

    @Jaime said:

    Then make one. The framework will load the app.config and honor binding redirects even if the author of the program has done nothing to enable it.

    TIL. I've long known that bindingRedirect looks like exactly what I want to know but didn't know how to use it.

    My .Net experience mostly comes from before the Express versions of VS, so how I learned was by using csc from the Framework SDK. With nothing like MSBuild, you didn't get exposed to AssemblyInfo.cs, app.config files, and so on. I'm trying to rectify that--the place I've worked for years was very limited in their outlook and pretty hidebound, so my knowledge is stale in places.


  • Discourse touched me in a no-no place

    This post is deleted!

  • Discourse touched me in a no-no place

    @blakeyrat said:

    So what's the WTF?

    Uh, the WTF is the environment I'm operating under? 😄 (And I put it accidentally in the wrong category and didn't notice, but it's fixed now.)


  • Discourse touched me in a no-no place

    The environment, btw, is weird. Let me explain it a bit to see if that helps.

    Progress is an interpreted language. All your source code resides in files with a .p extension. You run a compiler that actually produces an IL--something like Java bytecode or MSIL, although it's proprietary. This is stored in a .r file. The Progress executable, prowin32.exe, will read a .r (or a .p, which it will compile on demand right before running, but there's a bit of a performance penalty for that) and then execute the IL.

    While it's not a .Net language, it can consume .Net objects essentially as if they were ActiveX controls. For the purpose of this discussion, you can think of the language as loosely analogous to Java, and .Net objects as if they were also Java objects.

    So my program, frostcat.r, which is run from prowin32.exe (kind of like how a Java program runs java.exe), loads a class from intermediate.dll. Intermediate.dll is linked against library1.dll (and several other things) which is part of an external application. The people who wrote the external application originally wrote intermediate.dll, but then gave me the source and said "you deal with it from now on." The application contract is that any version 10.x of library1.dll will be compatible with any other (that is, no breaking changes). But, as I said, when I compile intermediate.dll, it's bound to the version of library1.dll I have, and if a client has a different version of library1.dll, my program dies with the equivalent of a missing DLL error, and obtaining every version of external application so I can build the DLL against it is not feasible.

    So do I make a prowin32.app.config file with the <bindingRedirect> in it, and put that file in prowin32.exe's directory? I don't think a DLL can have an app config, but I'll admit I don't know.



  • @FrostCat said:

    So do I make a prowin32.app.config file with the <bindingRedirect> in it, and put that file in prowin32.exe's directory?

    If prowin32.exe is a .Net app and it loads the dll into its own AppDomain, then yes. If it uses some other process to run extensions, then no.

    Turn on assembly binding logging and see what's going on.


  • Discourse touched me in a no-no place

    @Jaime said:

    If prowin32.exe is a .Net app and it loads the dll into its own AppDomain, then yes.

    I assume it is--how else would it be able to consume .Net objects?

    @Jaime said:

    Turn on assembly binding logging

    Oh god, that. (That's how I eventually confirmed the version-binding problem last year, but it took a lot of effort to figure out how to do it, which was made worse by the fact that a .Net program that can't load an assembly due to a version mismatch will throw an exception, but prowin32 will just say it can't find the class (as if it were simply missing an assembly reference.))

    I'll look into that again, though.



  • @FrostCat said:

    but prowin32 will just say it can't find the class (as if it were simply missing an assembly reference.

    Excellent error handling, right here people.
    I presume you have no way to fix that?


  • Discourse touched me in a no-no place

    You [p]resume correctly. I can't fix that any more than I could fix a bug in java.exe.

    I can, of course, write a stub c# program or something, as necessary, to get beyond the Progress limits.



  • @FrostCat said:

    java.exe

    Open source Jada Jada.. @FrostCat said:

    can, of course, write a stub c# program or something, as necessary,

    This smells of inner platform. But an assembly checker might be the least insane way forward.


  • Discourse touched me in a no-no place

    @swayde said:

    Open source Jada Jada..

    Well, you download the latest JRE and fix a bug in it only with it, if you know what I mean.


  • Discourse touched me in a no-no place

    @swayde said:

    This smells of inner platform.

    No, I mean, my progress program goes
    define variable MyInstance as class intermediate.blah

    I try to compile that if the intermediate.blah assembly is compiled against the wrong version of the external application, and I get the same exact error as if I had tried to use a nonexistent class, like so (the one line of code you see is the entire program):

    If I then replace the intermediate assembly dll with a different copy that was compiled against the version of external application on my machine, the same code compiles.

    IIRC if I make a minimal C# class which does the same thing, it will compile, but at runtime I get a FileLoadException (Could not load file or assembly '[long name here]', or one of its dependencies, etc.,etc.) the [long name here] assembly is part of external application, which lets me see what the proximate error is without needing the binding logger.

    That's how I originally figured out what the problem was, about a year ago. I had a functional workaround until the external application came out with another new point release that I can't build my intermediate DLL against.



  • @FrostCat said:

    IIRC if I make a minimal C# class which does the same thing, it will compile, but at runtime I get a FileLoadException (Could not load file or assembly '[long name here]', or one of its dependencies, etc.,etc.) the [long name here] assembly is part of external application.

    I see.
    I thought you could be clever and make an intermediate class that just referenced the real class without the assembly requirement. (Or a generic "stupid" class/loader)

    Why the hell have such a 'language' instead of just using c#?

    Whyyyy?


  • Discourse touched me in a no-no place

    @swayde said:

    I thought you could be clever and make an intermediate class that just referenced the real class without the assembly requirement.

    Sadly, my intermediate dll is exactly that! Progress' .Net support has one glaring limit that applies here: you can't use generics, and I need to make one generic method call on a class in the external application. I suspect it's because of limits in the language parser.

    @swayde said:

    Why the hell have such a 'language' instead of just using c#?

    Because Raymond Chen's time machine didn't exist in the 80s?



  • @FrostCat said:

    Sadly, my intermediate dll is exactly that!

    Curses.

    Their documentation hints that's you have to have a version number. (And there is no column for ignoring version)

    Have you tried hand editing the XML?

    Otherwise the solution might be to force your intermediate file to a constant version (say 1.0.0.0) and do some kind of runtime check. Say a property of the class, to enforce major/minor version checks.
    This might be impossible to backport, and is very much a cludge.



  • @FrostCat said:

    Because Raymond Chen's time machine didn't exist in the 80s?

    Didn't see the history there, I focused on the examples. What do you use it for,and is it as bad as it looks?


  • Discourse touched me in a no-no place

    @swayde said:

    Have you tried hand editing the XML?

    If you mean assemblies.xml I don't think that will help. Even a C# program will exhibit the same problem when trying to use intermediate.dll against the wrong version of the external application assemblies; this is why I am thinking the bindingRedirect is the way to go; I just wasn't sure where to apply it until recently. I should be able to test that tomorrow.


  • Discourse touched me in a no-no place

    @swayde said:

    What do you use it for,and is it as bad as it looks?

    Essentially anything you can hit a database with. Think of it like Oracle or something, only with a proprietary 4GL instead of Java.

    There're are number of major MRP and ERP packages built in Progress. I know of some major retailers and casinos that have used it in the past for, things like payroll; I worked at a subsidiary of WebMD that had a pair of applications written in Progress that were each responsible for about $10 million a month in revenue.

    It's got warts to be sure--in particular, the GUI functionality is essentially frozen at Windows 95-style UI--but in the places where it makes sense to use, it's actually quite good.

    I'm trying to transition out of that world into Java/.Net, probably, but mainly because the market for Progress programmers is small, in large part because nobody's ever heard of it. ;)


  • Discourse touched me in a no-no place

    I think I have a minimal repro that I will try to post tonight or tomorrow. It's about 5 small source files, and you can use the command line (if you have VS or add a Framework directory to your path) to build everything if anyone wants to see it and see if they can help me figure out how to get this to work, or an alternative.


  • Discourse touched me in a no-no place

    Ok, if anyone cares, here's my repro. Next I have to see if this works for non-trivial programs.

    ext.cs:

    using System.Reflection;
    using System.Windows.Forms;
    
    [assembly: AssemblyVersionAttribute("1.0.0.1")]
    [assembly: AssemblyDelaySign(false)]
    [assembly: AssemblyKeyFile("keyfile.snk")]
    namespace External
    {
        public class ext
        {
            public string thing()
            {
                return "External.ext.thing(): [" + Assembly.GetExecutingAssembly().FullName + "]";
            }
        }
    }
    

    use sn -k keyfile.snk to generate a key pair. Compile ext.cs with csc /t:library ext.cs.

    Here is int.cs:

    using External;
    
    namespace Intermediate
    {
    public class Int
    {
      public string GetExt()
      {
         ext e = new ext();
         return e.thing();
      }
    }
    }
    

    Compile int.cs with csc /t:library int.cs /r:ext.dll. Next, create driver.cs:

    using Intermediate;
    using System;
    
    public class driver
    {
      public static void Main(string[] args)
      {
        Int i = new Int();
        Console.WriteLine("Asked for version, got [" + i.GetExt() + "]");
      }
    }
    

    Compile it with csc driver.cs /r:int.dll and notice that ext.dll wasn't mentioned on that command line. Run driver.exe; you should get output something like this:
    Asked for version, got [External.ext.thing(): [ext, Version=1.0.0.1, Culture=neutral, PublicKeyToken=ebdf360c9c4270fe]]

    Next, recompile (only) ext.dll and run driver.cs. You will get an error:

    Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'ext, Version=1.0.0.1, Culture=neutral, PublicKeyToken=ebdf360c9c4270fe' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
       at Intermediate.Int.GetExt()
       at driver.Main(String[] args)
    

    finally, create driver.exe.config:

    <configuration>
       <runtime>
          <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
             <dependentAssembly>
                <assemblyIdentity name="ext"
                                  publicKeyToken="ebdf360c9c4270fe"
                                  culture="neutral" />
                <bindingRedirect oldVersion="1.0.0.1-1.9.9.9" newVersion="1.0.1.5"/>
             </dependentAssembly>
          </assemblyBinding>
       </runtime>
    </configuration>
    

    If for some reason your public key token is different, make sure you change it in the .config file.

    Finally, run driver.exe one more time:
    Asked for version, got [External.ext.thing(): [ext, Version=1.0.1.5, Culture=neutral, PublicKeyToken=ebdf360c9c4270fe]]

    Profit! (One hopes.)

    I put this out there because when you search, you only find pieces of it. For example, the docs for bindingRedirect don't talk about how to generate a strongly-named assembly. This gives you a complete set of instructions.

    Yes, this could probably be done more simply in Visual Studio, but I didn't have that originally, and I'm not sure how to modify the existing project to work, as previous advice didn't seem to work.


  • Discourse touched me in a no-no place

    Oh god. I don't know discobblmarktml well enough to know how to fix the code blocks.



  • For the love of God, use github or zip the files. You can upload them here...


  • Discourse touched me in a no-no place

    Here you go, Captain Cranky:

    That was the GitHub-suggested name for My First Repository.


  • Discourse touched me in a no-no place

    @FrostCat said:

    Oh god. I don't know discobblmarktml well enough to know how to fix the code blocks.

    Fixed.

    • Ensure each code fence (triple backtick) has a blank line before it, and
    • Ensure nothing else is on that line.

  • Discourse touched me in a no-no place

    Ah, format bikeshedding. Thanks for fixing it.



  • Could you possibly fix this with DLL injection?


  • Discourse touched me in a no-no place

    I was too busy to test it for real today but I think the bindingRedirect thing will work.



  • I was thinking of the case where the program didn't load the .config file. Should've said that...


  • Discourse touched me in a no-no place

    Oh, gotcha. I guess I could look into that if it becomes necessary.


  • Discourse touched me in a no-no place

    As far as I can tell, using bindingRedirect worked. I have two versions of the intermediate DLL; one compiled against my version of the external app, another compiled against a newer version I don't have. If I use the working DLL, the application loads find and obtains information from the external app. If I switch out DLLs, my application used to throw an error on startup (it just said that the connection to the external app isn't available.) Now it connects but when I try to do something involving reading data from the external app, I get a runtime error because the person who built that version of the DLL changed the source a little ( :facepalm: ) to call a new function my version of the external application doesn't have. So you get a System.MissingMethodException.

    I think that proves the concept works, though.


Log in to reply