CSharp Tips:以整数的形式传递对象引用

0 、前言
怀念C/C++光荣岁月的孩子,总是念念不忘指针。指针的确是个很神奇的东西,一个32位的整型(仅针对32位平台而言),时而函数、时而结构、时而数组,从而使得C,简洁又不失功能强大。指针和结构珠联璧合,巧妙地解决了很多编程上的问题,这也是我们在DotNET平台下条用Win32 API比较麻烦的根源。
指针简洁的背面充斥着内存非法访问和内存泄露等一大堆头疼的问题,所以在DotNET平台下,它退居到了幕后,我们只能够拿到的是指针封装后的数据结构—引用。没有这32位整型地指针,我们几乎可以做所有的事情,而且无需考虑内存泄露的问题,阳光一样灿烂,世界一样美好。
我们脚踩着大地,却抬头仰望着星空,好奇心驱使着我们探索世界的根源。我们创建了规则,也总是超越规则,幸运的是有的时候真理恰恰在现有规则之外。今天又有小子跳出来,站在日渐辉煌的DotNET大厦前,叫嚣着:“我就是要用指针,我就是要用32位整型传递一个对象引用,怎么办?”
“厄,为什么?”
“问一个问题需要理由么?”
“当然需要,我们在探讨技术问题,不是肥皂剧,不是搞笑片。”
“如果非要一个理由,那么我希望发送Windows消息时,一个类实例的引用作为消息的参数,传递给被通知方,怎么办?”
“厄,哪试试看吧…”某人背后继续嘀咕着“就算就算是对象作为消息参数,也不一定非要把对象引用变为指针呀,可以…”
废话太多,全部踢飞,转入正题。
 
1 、函数指针
DotNET中所有引用的类型全部从Object派生,但是Object就那么几个方法和属性,怎么看都看不出和地址有什么关系,没啥头绪。但是想到和反射(Reflection)相关的一套类库中,包含了MethodInfo等和方法有关的类,看有没有什么线索。
观察MethodInfo类,很快就能发现MethodInfo下有一个MethodHandle的属性,而RuntimeMethodHandle.Value就是一个IntPtr,名眼人一看,这个IntPtr就算不是这个方法编译之后存放在内存中的地址,也是和地址相关的句柄,总之有了它就可以调用这个方法了。
通过MethodInfo我们就可以获得一个方法的32位整数的表现形式。示例代码如下:
         /// <summary>
        
/// 获得一个方法的句柄
        
/// </summary>
        
/// <param name="obj">对象实例</param>
        
/// <param name="methodName">方法名称</param>
        
/// <returns>方法的句柄,无符号32位整型</returns>

         public   static  UInt32 GetMethodAddress(Object obj,String methodName)
        
{
            Type typObject 
= null;
            MethodInfo oMethod 
= null;
            UInt32 dwMethodAddr 
= 0;

            
if (obj == null)
                
return 0;
            
if (methodName == null)
                
return 0;

            typObject 
= obj.GetType();
            
if (typObject == null)
                
return 0;

            oMethod 
= typObject.GetMethod(methodName);
            
if (oMethod != null)
            
{
                dwMethodAddr 
= (UInt32) oMethod.MethodHandle.Value;
                oMethod 
= null;
            }

            typObject 
= null;

            
return dwMethodAddr;
        }

 
获得MethodHandle的Value之后,转换回MethodInfo却有点小问题。MethodInfo提供了GetMethodFromHandle的方法,可以根据RuntimeMethodHandle声称MethodInfo的实例,但是RuntimeMethodHandle.Value是一个只读属性,辛辛苦苦拿到了“指针”的值却没有办法直接声称对应的RuntimeMethodHandle,也就没办法声称MethodInfo,也就算没法调用这个方法,不是前功尽弃了么?
MethodHandle是一个结构,在调试器中可以看到它包含了只包含一个m_ptr的成员变量,也是IntPtr类型,可以推测MethodHandle就是一个只包含一个IntPtr成员的结构。熟悉结构的内存布局的话,我们知道,只需要将一个整型的类型利用内存复制的方法,就可以完成对MethodHandle复制。思路有了,实现就很简单。尽管DotNET下的结构没法直接参与内存复制,但是利用全局堆转换一下即可。示例代码如下:
         /// <summary>
        
/// 通过方法句柄,利用反射机制调用该方法。只适用于静态方法。
        
/// </summary>
        
/// <param name="addr">方法句柄,无符号32位整型</param>
        
/// <param name="paramValues">参数数组</param>
        
/// <returns>该方法调用的返回值</returns>

         public   static  Object CallMethodWithAddress(UInt32 addr, Object[] paramValues)
        
{
            RuntimeMethodHandle handler 
= new RuntimeMethodHandle();
            MethodInfo oMethod 
= null;
            IntPtr pBlock 
= IntPtr.Zero;
            Object objRet 
= null;

            pBlock 
= Marshal.AllocHGlobal(4);
            
if (pBlock != IntPtr.Zero)
            
{
                Marshal.Copy(BitConverter.GetBytes(addr), 
0, pBlock, 4);

                
// MethodHandle.Value为只读属性,只能通过复制之后强制转换类型的方式生成
                handler = (RuntimeMethodHandle)Marshal.PtrToStructure(pBlock, typeof(RuntimeMethodHandle));
                oMethod 
= (MethodInfo)MethodInfo.GetMethodFromHandle(handler);
                
if (oMethod != null)
                
{
                    objRet 
= oMethod.Invoke(null, paramValues);
                    oMethod 
= null;
                }


                Marshal.FreeHGlobal(pBlock);
                pBlock 
= IntPtr.Zero;
            }


            
return objRet;
        }

 
需要说明的是,利用反射调用方法,必须提供目标对象才能调用示例的成员方法,反之只能够调用类的静态方法。上面的例子中没有传递类示例,所以只能调用静态方法。
 
2 、类实例引用的指针
面对一个Object的引用,想不出有什么方法能够将它转变成为一个UInt32 类型,直接使用Marshal.PtrToStructure和Marshal.StruectToPtr传递的是一个新的Object的示例,而非原有Object示例的引用。
不过前面函数指针中结构的转换给了我们一个很好的思路,定义一个仅包含对象引用的结构,再利用全局堆复制结构,即可以实现利用UInt32 传递对象引用的功能。
我们可以定义这样的结构:
         private   struct  SObjectProxy
        
{
            
public Object ObjectRef;
        }

 
利用这个对象代理结构,可以获得一个能够还原对象引用的整型,示例如下:
         /// <summary>
        
/// 获得一个包装类实例引用的地址
        
/// </summary>
        
/// <param name="obj"></param>
        
/// <returns></returns>

         public   static  UInt32 GetObjectProxyAddress(Object obj)
        
{
            SObjectProxy proxy 
= new SObjectProxy();
            IntPtr pObjAddr 
= IntPtr.Zero;

            
if (obj == null)
                
return 0;

            proxy.ObjectRef 
= obj;
            
// 在全局堆中申请一块内存
            pObjAddr = Marshal.AllocHGlobal(Marshal.SizeOf(proxy));
            
if (pObjAddr != null)
            
{
                
// 将结构复制到全局堆中申请的内存里
                Marshal.StructureToPtr(proxy, pObjAddr, false);
            }


            
return (UInt32) pObjAddr.ToInt32();
        }

 
利用上述过程中获得的整型,还原对象引用的代码如下:
         /// <summary>
        
/// 根据一个包装类实例引用的地址获得所包装的类实例引用本身
        
/// </summary>
        
/// <param name="proxyAddr"></param>
        
/// <returns></returns>

         public   static  Object GetObjectFromAddress(UInt32 proxyAddr)
        
{
            SObjectProxy proxy;
            Object obj 
= null;

            
if (proxyAddr == 0)
                
return null;

            proxy 
= (SObjectProxy) Marshal.PtrToStructure((IntPtr)proxyAddr, typeof(SObjectProxy));
            obj 
= proxy.ObjectRef;
            Marshal.FreeHGlobal((IntPtr) proxyAddr);

            
return obj;
        }

 
3 、注意事项
虽然和C/C++中类实例的指针不一样,但是确实达成了,以一个整数形式传递对象引用的目的。但是目前的实现方案中存在一些比较大的缺陷。
首先、DotNET平台内部有一套引用计数和垃圾收集的机制,我们在全局堆中申请内存,复制类实例引用的时候,并没有增加这个对象的引用计数,所以如果这个对象被释放的话,我们还通过对象代理还原到那个实例的引用,使用的时候应该会有一个“访问无效对象”的错误。所以在使用这个机制的过程中,在保证在最后一次调用之前,被传递引用的实例始终是有效的,因此一个局部变量绝对不能够应用在这种机制上。
其次是效率问题,一次传递就涉及了一次内存的申请与释放,两次结构的复制,所以如果是很频繁的调用的话,会有一些性能问题。此外通过反射调用方法的话,本身效率就比较低。
再次,要求实例的创建者和该实例引用的使用者必须在同一个AppDomain中。
 
所以又回到了那句老话,语言本身没有高低之分,每种语言都有各自擅长的领域。很多事情,C#或者DotNET平台不是做不到,而是实现起来不方面,并没有得到相应的好处,没有必要吃力不讨好呀。
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值