昨夜一名 框架爱好者在测试V5Beta版本时,向我发一个问题, 说F5下正常,直接运行exe会报错,于是,一夜深究后,就有了今天的博文。
当你运行一段代码、一个软件,在运行时你发现:Debug下正常, Release下F5运行也正常,唯独Release编绎后单独运行异常,你会怎么想?
这个问题对我而言,脑海里不曾有过,于是蛋定思蛋,先百度了一下:发现网上信息并不多,偶尔几条也是VC++的,对于C#的,绝无仅无。
当我把代码发给一友人,让他也帮忙试下时,发现对方的win7 64位竟然运行正常,灵异性又提升了一个等级。
面对这神一般的灵异Bug,我费了N个小时折腾,找一个不能调试的Bug,因为F5下都是正常的,那个辛苦啊。
灵异现象已经说了,那灵异点是什么呢?标题已经出卖了这个问题,和Aop的RealProxy有关。
光说AOP,园子里懂的人不多,用的估计也不多,所以深入无谓,浅出又难,只好简单过过场。
我是怎么发现问题是在Aop RealProxy?
这个使用常规方法,在代码段里插一些弹出信息,来缩小出错的代码片断,最终找到了它。
为了减少废话,这里直接讲述两点:
1:哪些代码有问题,哪里有问题?
以前我写过一篇文章:,可以先理解下AOP,但是它是有问题的,就是今天的问题。
这里给出详细的错误代码,和注释错误点,代码通常都有点长,不容易看:
using System;
using System.Reflection;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
internal class AopAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
AopProxy proxy =
new AopProxy(serverType);
return (proxy.GetTransparentProxy()
as MarshalByRefObject);
}
}
internal class AopProxy : RealProxy
{
public AopProxy(Type serverType) :
base(serverType)
{
}
public override IMessage Invoke(IMessage msg)
{
IMessage message3;
if (msg ==
null)
{
return msg;
}
if (msg
is IConstructionCallMessage)
{
IConstructionReturnMessage message =
base.InitializeServerObject((IConstructionCallMessage) msg);
RealProxy.SetStubData(
this, message.ReturnValue);
return message;
}
if (!(msg
is IMethodCallMessage))
{
return msg;
}
IMethodCallMessage mcm = msg
as IMethodCallMessage;
try {
object[] args = mcm.Args;
message3 =
new ReturnMessage(mcm.MethodBase.Invoke(
base.GetUnwrappedServer(), args), args, args.Length, mcm.LogicalCallContext, mcm);
}
catch (Exception exception)
{
throw new Exception(exception.Message);
}
return message3;
}
}
[AopAttribute]
public class Users : ContextBoundObject
{
public Users()
{
}
int i =
0;
// 任何的成员变量,在被Aop调用的时候,都会报“未将对象引用到对象实例的错误” public void Test()
{
try {
i =
1;
}
catch (Exception err)
{
System.Windows.Forms.MessageBox.Show(err.Message);
}
}
通常大量的代码测试及分析方法,我发现了,只要Aop对象涉及到内部成员变量,在Release编绎后运行就一定会报错。
2:如何解决。
在历经各种无解后,我发现了,RealProxy还有另一种写法,而这另一种写法,竟然可以解决这个问题,代码如下:
class AopAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
AopProxy realProxy =
new AopProxy(serverType,
base.CreateInstance(serverType));
return realProxy.GetTransparentProxy()
as MarshalByRefObject;
}
}
class AopProxy : RealProxy
{
MethodInfo method;
MarshalByRefObject _target =
null;
public AopProxy(Type serverType,MarshalByRefObject target)
:
base(serverType)
{
_target = target;
method = serverType.GetMethod(
" Set ", BindingFlags.NonPublic | BindingFlags.Instance);
}
public override IMessage Invoke(IMessage msg)
{
if (msg !=
null)
{
if (msg
is IConstructionCallMessage)
{
IConstructionCallMessage constructCallMsg = msg
as IConstructionCallMessage;
RealProxy defaultProxy = RemotingServices.GetRealProxy(_target);
// 如果不做下面这一步,_target还是一个没有直正实例化被代理对象的透明代理, // 这样的话,会导致没有直正构建对象。 defaultProxy.InitializeServerObject(constructCallMsg);
// 本类是一个RealProxy,它可通过GetTransparentProxy函数得到透明代理 return System.Runtime.Remoting.Services.EnterpriseServicesHelper.CreateConstructionReturnMessage(constructCallMsg, (MarshalByRefObject)GetTransparentProxy());
}
else if (msg
is IMethodCallMessage)
{
IMethodCallMessage callMsg = msg
as IMethodCallMessage;
object[] args = callMsg.Args;
// System.Windows.Forms.MessageBox.Show(callMsg.MethodBase.ToString()); if (callMsg.MethodName.StartsWith(
" set_ ") && args.Length ==
1)
{
method.Invoke(_target,
new object[] { callMsg.MethodName.Substring(
4), args[
0] });
// 对属性进行调用 }
return RemotingServices.ExecuteMessage(_target, callMsg);
}
}
return msg;
}
3:Debug和Release还有F5,究竟隐藏怎样的秘密?
我也不懂,这涉及更底层的问题,不在我深究的范围内,懂的人士可以留言给大伙解答下。
总结:
在编程的世界里,灵异总是无处不在的,但是找它灵异的原因,并且能找到一种解决这类事件的方法,是每个代码建造师必须具有能力。
至于为何会产生这样那样的灵异事件,在研究的范围内,可以追究原因,在非自身研究的领域,大伙看着办了。
终于,在忙碌系列软件的日子里,终于又抽空完成了一篇文章,感觉不容易啊。