LinFu and .NET type problems

3 views
Skip to first unread message

Brian Chavez

unread,
Jul 18, 2008, 7:18:30 PM7/18/08
to linfufr...@googlegroups.com

Hi Philip,

 

Sorry to bother you again....  Been really pushing LinFu on some edge cases lately... but I've come across a serious problem.

 

For some reason, the types created by LinFu seem to be somewhat different than the types used by the by running code in the .NET framework.

 

I'm not sure why this is happening, and the only way I can really explain this issue is through some code.

 

So, I've posted a Unit Test that, in my view, should pass, but fails when using LinFu.

 

http://www.bitarmory.com/temp/TestLinFu.zip

 

A type set from a constructor of a constructed object (by LinFu) is different than the type retrieved from the typeof() operator.

 

The TypeHandle.Value on both types (from Container.GetService<>()->.ctor():this.GetType() and typeof()) return different pointers to type metadata structures..  I'm not sure why when they should be the same types.

 

The unit test in the TestLinFu.zip illustrates this point.  I look forward to your insight.  It's quite possible I'm overlooking something minor here, but from what I can see, they should be both the same exact type and the test should pass.

 

It's possible, that the Loader is loading the .dlls in different load contexts?  I found this issue when I was working with Dictionary<Type>, and noticed that the types created from the Container were not correctly hashing with types created from typeof() when they should have been the same.

 

Environment:

Visual Studio 2008 SP1 Beta

.NET 3.5 SP1 Beta

 

I've also tested in VS 2005, the same unit test fails in .NET 2.0.

 

Thanks,

Brian

 

 


----------------------------------------------
Brian Chavez
Bit Armory, Inc.
http://www.bitarmory.com

 

Philip Laureano

unread,
Jul 18, 2008, 7:43:29 PM7/18/08
to linfufr...@googlegroups.com

Hi Brian,

For some reason, I'm not able to reproduce the error on my end--the unit test you gave me just passed without any problems. Based on what I've seen in the test code, the only way that you can have two different types is if you have the container itself to return some sort of dynamic proxy instance.

You can get around this issue by changing the following line in the AreEqual getter for your TestHarness class:

get
{
    return typeInConstructor.Equals( typeSetFromUnitTest );
}

to:

get
{
    return typeInConstructor.Equals( typeSetFromUnitTest) || typeInConstructor.IsAssignableFrom(typeSetFromUnitTest);
}

Once you've made the changes, the unit test won't make a distinction between a proxied type or its derived classes. The only thing you have to watch out for here is if you're going to dynamically generate a proxy directly from IUserDao instead of following the IUserDao->UserDao->YourProxy heirarchy--if you derive it directly from the IUserDao, the AreEqual getter will always return false. If you dynamically derive it from UserDao, however, you should get a 'true' result.

Hope that helps!

Regards,

Philip Laureano

Brian Chavez

unread,
Jul 18, 2008, 7:56:18 PM7/18/08
to linfufr...@googlegroups.com

Hi Philip,

 

Thanks for the clarification.  But this is a really weird situation..

 

http://www.bitarmory.com/temp/fail.jpg

 

and I'm not crazy......the unit test is failing on my end..  haha. What happened to Unit Tests and repeatability?!?!

 

I'll try on my laptop.

 

Thanks,

Brian

 


----------------------------------------------
Brian Chavez
Bit Armory, Inc.
http://www.bitarmory.com

 

Brian Chavez

unread,
Jul 18, 2008, 8:32:19 PM7/18/08
to LinFu.Framework
I'm completely blank. Both my main dev machine, my laptop, and our
production server (without Visual Studio) all fail the test using
NUnit GUI test runner.

Are you sure your unit test passes on your machine? ! ?

I've posted the binaries online:

http://www.bitarmory.com/temp/Debug.zip

It includes NUnit GUI just in case.

Something is seriously wrong. Reflector should show that it's the
same exact code as in the Source / Unit tests I gave you at the
beginning of this post.

:/

Thanks again for your help!

Brian Chavez

unread,
Jul 18, 2008, 10:07:48 PM7/18/08
to LinFu.Framework
Sorry for the forum noise. I think I solved it. But this is one
gotcha that might helps someone out.

The problem was, the UnitTest DLLs were being shadow copied into a
temp directory... so, when you run Loader.LoadDirectory("") you end up
getting 2 of the same DLL modules loading. One copy in the \Temp
directory where the tests are currently running, and the other copy in
the \bin\Debug (AppDomain.BaseDirectory). Both modules were loaded
(once by the .NET framework, and again by AssemblyLoader.LoadFile-
>Assembly.LoadFile()). Effectively, both DLLs had different
TypeHandles pointers. So, when you call Container.GetService< T >(),
the type from the latter load is returned, and when typeof() is called
in your code is called, the former load module is used. Hence, giving
rise to different TypeHandle.Values.

The issue becomes problematic when using ASP.NET with the LinFu
container because of ASP.NET's built in compiling / file shadow
copying into TemporaryASP.NET folders.

So, the moral of the story is, be mindful and careful of the DLLs and
your load directory. That one had me :)

-cowboy

Philip Laureano

unread,
Jul 18, 2008, 10:27:53 PM7/18/08
to linfufr...@googlegroups.com

I'm absolutely sure. I'm running this right out of TestDriven.NET, and it passes every time. I suspect that this might have something to do with the test runner itself rather than LinFu. I've run into a similar situation before, and I can't quite put my finger on it, but what happens is that the Resharper test runner (and possibly the NUnit GUI) loads two different versions of the same assembly at once, and when you do your type comparison between the type loaded into the container versus the type you're comparing against in the unit test, the two will never match.

To verify this behavior, I even added the following lines to your sample unit test:

            var firstLocation = TestHarness.typeInConstructor.Assembly.Location;
            var secondLocation = TestHarness.typeSetFromUnitTest.Assembly.Location;
           
            Console.WriteLine("TypeInConstructor Assembly Location: ", firstLocation);
            Console.WriteLine("TypeSetFromUniTest Assembly Location: ", secondLocation);

            Assert.AreEqual(firstLocation, secondLocation, "The two assemblies don't match!");

As strange as it seems, the unit test runner actually has to load a different copy of OtherLibrary.dll into memory before it runs the test. What makes it more complicated is that the "other" library is actually shadow copied to another directory so when LinFu loads the original version of OtherLibrary.dll, the two will never match.

Go ahead and run that additional test code above--at first, one would think that it would always pass, but as you will see, the results can be quite revealing.

Regards,

Philip Laureano

Brian Chavez

unread,
Jul 18, 2008, 10:41:22 PM7/18/08
to LinFu.Framework
Actually, this is a real problem using Simple.IoC in an ASP.NET
environment....

ASP.NET dynamically compiles websites into really wacky directories
like:

C:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
\PROJECT.WEB\268221a5\e6b72edf\assembly\dl3\2eeb6877\90691b2f_81e8c801

If you take a look at the directory structure that the ASP.NET
compiler creates, you'll find that the directory per assembly consists
of:

\assemblyfolder\__AssemblyInfo__.ini
\assemblyfolder\YourLibrary.dll
\assemblyfolder\YourLibrary.pdb

\otherassemblyfolder\__AssemblyInfo__.ini
\otherassemblyfolder\OtherLibrary.dll
\otherassemblyfolder\OtherLibrary.pdb

All assemblies in your \Bin folder are copied into their own
directory.... then loaded into memory from their own respective temp
directory...

So, doing something like:

Loader.LoadDirectory( CurrentDomain.BaseDirectory\RelativeSearchPath )

wont work because your Website\Bin directory isn't used as the load
point for the modules that are loaded in memory (the ASP.NET temp
directory is). If you use the Loader.LoadDirectory strategy you'll
end up with two of the same assemblies loaded in memory in your
ASP.NET runtime environment. Effectively, the type you get from
Container.GetService<> will be different than the type returned from
typeof() :/

Any ideas on a workaround???


Brian Chavez

unread,
Jul 18, 2008, 10:52:45 PM7/18/08
to LinFu.Framework
Most definitely, it's a shadow copy problem.

But how do we prevent duplicate DLLs from loading in an ASP.NET
environment when the runtime itself shadow copies the \Bin assemblies
to dynamically compile the website?

I think we need to pickup on a different Loader strategy for ASP.NET.

Philip Laureano

unread,
Jul 18, 2008, 11:01:49 PM7/18/08
to linfufr...@googlegroups.com
This one is an interesting case, and I have a couple of questions for you. If ASP.NET loads AssemblyA and AssemblyB into memory, and both assemblies have a dependency on InterfaceAssemblyC, then how many times will it load InterfaceAssemblyC into memory if you reference it in your ASP.NET page? Will it load it twice?

Since it does sound like a shadow-copy problem, the only workaround that I could think of is to create a LoadStrategy that resolves the type dependency at runtime rather than at load time. Perhaps you could give it an AssemblyQualifiedName name string, and let System.Reflection resolve the assembly for you? I suspect that doing a string-based assembly load might resolve the duplication. I know it's pretty sketchy, but it's the only workaround I can think of at this point.

If that doesn't work, you can always try creating your own IFactory instance that resolves interface instances using the same assembly-qualified name resolution techniques. I haven't experimented in doing late-bound dependency resolution yet, but I think it's worth a shot.

Regards,

Philip laureano

Brian Chavez

unread,
Jul 19, 2008, 12:04:07 AM7/19/08
to LinFu.Framework
> This one is an interesting case, and I have a couple of questions for you.
> If ASP.NET loads AssemblyA and AssemblyB into memory, and both assemblies
> have a dependency on InterfaceAssemblyC, then how many times will it load
> InterfaceAssemblyC into memory if you reference it in your ASP.NET page?
> Will it load it twice?

No, not that I'm aware of. Straight up referencing only AssemblyA and
AssemblyB, will require that you add a reference to InterfaceAssemblyC
because the ASP.NET/C# compiler will complain that
"InterfaceAssemblyC should be referenced". Visual Studio will
automatically add InterfaceAssemblyC to your \Bin folder.

You can see here:
http://www.bitarmory.com/temp/aspasmtest.jpg

InterfaceAssemblyC is only loaded once.

http://www.bitarmory.com/temp/aspasmtest.zip

for the test solution.

I think one problem might be the AssemblyLoader.LoadFile().
Internally, it uses Assembly.LoadFile('path') which bypasses a lot of
checks done by the .NET runtime. I'm not sure if changing the
implementation to Assembly.LoadFrom('path') would help.

Having an additional Loader strategy that loads from
Assembly.Load('assembly name') would definitely help too.

Philip Laureano

unread,
Jul 19, 2008, 12:24:55 AM7/19/08
to linfufr...@googlegroups.com
If InterfaceAssemblyC is only loaded once, then you should be able to get around the duplication by working with the interfaces instead of the concrete class instances. On the other hand, if you absolutely need to do name-based assembly loading, you can probably derive your own implementation from AssemblyLoader and call Assembly.LoadFrom() so that you'll have complete control over which assembly gets loaded into memory.

The Loader class lets you override the default AssemblyLoader with your own implementation with the following constructor:

        public Loader(IContainer container, IAssemblyLoader loader) : base(container, loader)
        {
        }

It should be pretty straightforward to define your own IAssemblyLoader implementation. The only thing I haven't figured out yet is how to convert an assembly filename into a fully-qualified assembly name to pass to Assembly.LoadFrom(). Once there is a consistent and reliable way to do that, then your problem with duplicate assemblies should be solved.

Regards,

Philip Laureano.

Brian Chavez

unread,
Jul 19, 2008, 1:14:29 AM7/19/08
to LinFu.Framework
Actually, Assembly.LoadFrom(string) takes in a filename/path, not an
assembly name.

So, if I understand your strategy correctly, you wouldn't need to
preform a mapping from Assembly.dll -> "SomeType, Assembly,
PublicKey=etc..." name format. You can directly pass in
Assembly.LoadFrom('Assembly.dll').

But, like I mentioned before, Assembly.LoadFrom() is safer to call
because it does some extra (assembly hash?) checking on the assemblies
already loaded by Fusion. So, Assembly.LoadFrom() will prevent
loading duplicates.

To test it out. I turned back shadow-copy on my unit tests, and used:

public class SafeAssemblyLoader : AssemblyLoader
{
protected override Assembly LoadFile(string assemblyFile)
{
return Assembly.LoadFrom( assemblyFile );
}
}

SimpleContainer container = new SimpleContainer();
Loader loader = new Loader(container, new SafeAssemblyLoader());

The unit tests that failed, now pass. And I do not see any more
duplicates in the loaded modules window.

I also tried this out with ASP.NET and no more duplicates exist. So,
I think you would probably want to change your default implementation
to use Assembly.LoadFrom instead of LoadFile!

Brian Chavez

unread,
Jul 19, 2008, 1:22:07 AM7/19/08
to LinFu.Framework
To verify the behavior, use your modified unit test example you gave
earlier...

Assembly.LoadFrom() should make your modified Type.Assembly.Location
unit test pass, and the Location on the types should be the same no
matter how may times you call LoadDirectory() from different file
locations.

TestHarness.typeSetFromUnitTest.Assembly.Location
TestHarness.typeInConstructor.Assembly.Location

Assembly.LoadFrom() wins!!! :D

Philip Laureano

unread,
Jul 19, 2008, 1:24:29 AM7/19/08
to linfufr...@googlegroups.com
Thanks for pointing this out, Brian! I never thought that a single line of code would make such a difference. I went ahead and patched the AssemblyLoader in revision 137, so you shouldn't have anymore problems with duplicate assemblies. Thanks for all your help!

Regards,

Philip Laureano

Brian Chavez

unread,
Jul 19, 2008, 1:47:15 AM7/19/08
to linfufr...@googlegroups.com, te...@bitarmory.com

Thanks for the patch Philip.

 

It took me nearly 2 days to figure out what was going wrong with my code, until I found that the Dictionary< Type > wasn't working like it was suppose to.

 

Then finding that the TypeHandle pointers were pointing to different metadata structures when they should logically be the same.  Then finding that LinFu was the culprit passing in the bad TypeHandle.Value.

 

Thanks again for the fix, I'll update my assemblies tonight, and now I can get back into my main work!

 

Yeah, sometimes a single line of code can have far reaching consequences. :)

 

Take care,

Brian

 


----------------------------------------------
Brian Chavez
Bit Armory, Inc.
http://www.bitarmory.com

 

 

From: linfufr...@googlegroups.com [mailto:linfufr...@googlegroups.com] On Behalf Of Philip Laureano
Sent: Friday, July 18, 2008 10:24 PM
To: linfufr...@googlegroups.com
Subject: Re: LinFu and .NET type problems

 

Thanks for pointing this out, Brian! I never thought that a single line of code would make such a difference. I went ahead and patched the AssemblyLoader in revision 137, so you shouldn't have anymore problems with duplicate assemblies. Thanks for all your help!

Regards,

Philip Laureano

Actually, Assembly.LoadFrom(string) takes in a filename/path, not an assembly name.

From: linfufr...@googlegroups.com [mailto:linfufr...@googlegroups.com] On Behalf Of Philip Laureano
Sent: Friday, July 18, 2008 9:25 PM
To: linfufr...@googlegroups.com
Subject: Re: LinFu and .NET type problems

 

If InterfaceAssemblyC is only loaded once, then you should be able to get around the duplication by working with the interfaces instead of the concrete class instances. On the other hand, if you absolutely need to do name-based assembly loading, you can probably derive your own implementation from AssemblyLoader and call Assembly.LoadFrom() so that you'll have complete control over which assembly gets loaded into memory.

Reply all
Reply to author
Forward
0 new messages