Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ModuleScope.SaveAssembly produces incorrect / incomplete assemblies when proxying types from a weak-named assembly #327

Closed
stakx opened this issue Dec 7, 2017 · 3 comments
Labels
Milestone

Comments

@stakx
Copy link
Member

stakx commented Dec 7, 2017

TL;DR: I am either misunderstanding how ModuleScope.SaveAssembly works, or DynamicProxy — when it produces proxies for types in weak-named assemblies — puts generated proxy types in a weak-named module and proxy method invocation types in a strong-named module, resulting in failure or incomplete output by ModuleScope.SaveAssembly.

The generated assemblies mentioned below can be found in CastleDynProxy2_strong_and_weak.zip.

Saving with ModuleScope.SaveAssembly(strongNamed: false)

Suppose I'm trying to save the dynamic assembly generated by DynamicProxy as follows:

using Castle.DynamicProxy;

public interface IFoo
{
    void Method();
}

public abstract class Bar
{
    public abstract void Method();
}

class Program
{
    public static void Main()
    {
        var proxyGenerator = new ProxyGenerator(new PersistentProxyBuilder());
        var foo = proxyGenerator.CreateInterfaceProxyWithoutTarget<IFoo>();
        var bar = proxyGenerator.CreateClassProxy<Bar>();
        proxyGenerator.ProxyBuilder.ModuleScope.SaveAssembly(strongNamed: false);
    }
}

Note that I am not strong-naming the assembly containing above code—if I did, then everything would work as expected.

This generates an assembly CastleDynProxy2, which appears to have two problems:

  1. It is missing types Castle.Proxies.Invocations.IFoo_Method and Castle.Proxies.Invocations.Bar_Method. PEVerify is a bit cryptic about it, but seems to tell me the same thing:

    [IL]: Error: [C:\...\CastleDynProxy2.dll : Castle.Proxies.IFooProxy::Method]
    [HRESULT 0x80070002] - The system cannot find the file specified.
    
    [IL]: Error: [C:\...\CastleDynProxy2.dll : Castle.Proxies.BarProxy::Method
    [HRESULT 0x80070002] - The system cannot find the file specified.
    
    2 Error(s) Verifying CastleDynProxy2.dll
    
  2. There is an assembly reference to a strong-named DynamicProxyGenAssembly2, which is confusing, because the generated assembly already has the exact same name (except for being weak-named).

If I try to run code that references and uses the CastleDynProxy2.dll as produced above, I will get a FileNotFoundException (perhaps due to the second issue above):

class Program
{
    static void Main()
    {
        var bar = new BarProxy();
    }
}
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
   at Program.Main()

Saving with ModuleScope.SaveAssembly(strongNamed: true)

When I change the last line of the original program (at the top) to:

        proxyGenerator.ProxyBuilder.ModuleScope.SaveAssembly(strongNamed: true);
        //                                                                ^^^^

and inspect the generated assembly CastleDynProxy2.dll (which is now strong-named), I see this:

  1. The assembly has the invocation types Castle.Proxies.Invocations.IFoo_Method and Castle.Proxies.Invocations.Bar_Method, but it is missing the proxy types Castle.Proxies.IFooProxy and Castle.Proxies.BarProxy.

Saving with ModuleScope.SaveAssembly()

If I try to save the assembly without specifying strongNamed: false, nor strongNamed: true, I get an exception right away:

Unhandled Exception: System.InvalidOperationException: Both a strong-named and a weak-named assembly have been generated.
   at Castle.DynamicProxy.ModuleScope.SaveAssembly()
   at Program.Main() in ...\Program.cs:line 20

Possible conclusions

  • I am making a beginner mistake (hinted at by the last exception shown just above). What I don't understand in this case is, how would I let DynamicProxy save a correct, weak-named dynamic assembly referencing types IFoo, Bar from my own weak-named assembly? SaveAssembly(strongNamed: false) doesn't seem to do the trick.

  • When generating proxies for types in weak-named assemblies, DynamicProxy puts the proxy types in a weak-named assembly and the invocation types in a strong-named assembly. Therefore it will only be able to save either of those at a time, but fails to save both. This results in incomplete output assembly either way.

What is going wrong here?

@stakx stakx changed the title Question: Does ModuleScope.SaveAssembly produce incorrect / incomplete assemblies? Question: Does ModuleScope.SaveAssembly produce incorrect / incomplete assemblies when proxying types from a weak-named assembly? Dec 7, 2017
@jonorossi
Copy link
Member

@stakx firstly thanks for such a detailed explanation of what you are seeing.

This generates an assembly CastleDynProxy2, which appears to have two problems

You'll get a file named CastleDynProxy2.dll but the contained assembly is DynamicProxyGenAssembly2. I don't know the history why the defaults for these were set to different names.

I am making a beginner mistake (hinted at by the last exception shown just above). What I don't understand in this case is, how would I let DynamicProxy save a correct, weak-named dynamic assembly referencing types IFoo, Bar from my own weak-named assembly? SaveAssembly(strongNamed: false) doesn't seem to do the trick.

I don't think this is a beginner mistake at all, it sounds like a defect.

If I try to save the assembly without specifying strongNamed: false, nor strongNamed: true, I get an exception right away

The no args SaveAssembly method throws that InvalidOperationException "Both a strong-named and a weak-named assembly have been generated." because it doesn't know which one you want saved.

When generating proxies for types in weak-named assemblies, DynamicProxy puts the proxy types in a weak-named assembly and the invocation types in a strong-named assembly. Therefore it will only be able to save either of those at a time, but fails to save both. This results in incomplete output assembly either way.

This sounds like a longstanding bug that hasn't hurt anyone because few people save assemblies and those that have have likely strong named their own assemblies (as you said that case works).

Obviously using a weak named assembly everything works at runtime because there are two DynamicProxyGenAssembly2 assemblies loaded in the CLR. One with PublicKeyToken=null the other with PublicKey=a621a9e7e5c32e69, which have a different identity. The reference from the weak named to the strong named works fine while loaded in memory (I don't think the C# compiler allows this), but falls down because it is hard to save and load up again.

I added the following at the bottom of your Main method:

foreach (System.Reflection.Assembly asm in System.AppDomain.CurrentDomain.GetAssemblies())
{
    System.Console.WriteLine(asm);
}

And get the expected result of two DynamicProxyGenAssembly2 assemblies:

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
[snip: the console app assembly]
Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc
System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=a621a9e7e5c32e69

Did you want to dig in to see if you can fix the assembly that the invocation types gets generated into.

@jonorossi jonorossi added this to the vNext milestone Dec 7, 2017
@jonorossi jonorossi added the bug label Dec 7, 2017
@stakx
Copy link
Member Author

stakx commented Dec 7, 2017

@jonorossi, thank you for the analysis and confirming that this is a defect.

This sounds like a longstanding bug that hasn't hurt anyone because few people save assemblies and those that have have likely strong named their own assemblies (as you said that case works).

To tell the truth, it doesn't really hurt me, either, but...

Did you want to dig in to see if you can fix the assembly that the invocation types gets generated into.

I'm happy to look into this.

(PS: I haven't forgotten about the PR for some new documentation regarding DynamicProxy's handling of by-ref parameters, I just got delayed. It's still in the pipeline, though. :-)

@stakx stakx changed the title Question: Does ModuleScope.SaveAssembly produce incorrect / incomplete assemblies when proxying types from a weak-named assembly? ModuleScope.SaveAssembly produces incorrect / incomplete assemblies when proxying types from a weak-named assembly Dec 7, 2017
@jonorossi
Copy link
Member

jonorossi commented Apr 24, 2018

Fixed by #346. Many thanks @stakx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants