跨越C#和C++的单件(SINGLETON)模式

来源:互联网 发布:怎么设置淘宝客 编辑:程序博客网 时间:2024/05/17 01:33

无论是使用C#语言还是C++语言开发出一个单件模式对于大多数人来说都不是太难的一件事。但是使用.Net平台开发项目的时候,一个解决方案中往往会有C#C++两种工程(没办法,谁让C#有那么好的界面功能,以及那么强的XML和数据库操作功能J)。如何使一个类对象实例贯穿在C#C++两种工程中,并且使它在整个过程中有且仅有一个?也就是说,这是不仅仅是一个工程中的单件,而是一个解决方案中的单件。相信这一定是任何一个具有.Net平台开发经验的程序员急切想了解的话题。

 

本文中给出了一个解决方案。这是一个从实际项目开发中总结出来的案例,目的就是要在软件运行过程中保证仅有的一个工程管理类实例存在,而对该管理类实例的使用却要贯穿整个解决方案(C#C++两种工程)。

 

首先让我们来了解一下.Net框架类库提供的两个重要相关类型的使用:

 

GCHandleType 枚举--表示 GCHandle 类可以分配的句柄的类型。

命名空间: System.Runtime.InteropServices
程序集: mscorlib(在 mscorlib.dll 中)

 

成员名称

说明

Normal

此句柄类型表示不透明句柄,这意味着无法通过此句柄解析固定对象的地址。可以使用此类型跟踪对象,并防止它被垃圾回收器回收。当非托管客户端持有对托管对象的唯一引用(从垃圾回收器检测不到该引用)时,此枚举成员很有用。 

Pinned

此句柄类型类似于 Normal,但允许使用固定对象的地址。这将防止垃圾回收器移动对象,因此将降低垃圾回收器的效率。需要使用 Free 方法可尽快释放已分配的句柄。 Use the Free method to free the allocated handle as soon as possible. 

Weak

此句柄类型用于跟踪对象,但允许回收该对象。当回收某个对象时,GCHandle 的内容归零。在终结器运行之前,Weak 引用归零,因此即使终结器使该对象复活,Weak 引用仍然是归零的。 

WeakTrackResurrection

该句柄类型类似于 Weak,但如果对象在终结过程中复活,此句柄不归零。 

 

GCHandle 结构--提供从非托管内存访问托管对象的方法。

命名空间: System.Runtime.InteropServices
程序集: mscorlib(在 mscorlib.dll 中)

 

名称

说明

IsAllocated

获取一个值,该值指示是否分配了句柄。

Target

获取或设置该句柄表示的托管对象。

Alloc

传入一个托管对象,为这个指定的对象分配句柄。缺省为该对象分配GCHandleType. Normal 句柄。

ToIntPtr

返回 GCHandle 对象的内部整数表示形式。

FromIntPtr

传入GCHandle代表的某个托管对象的内部整数表示,返回一个新创建GCHandle 对象。

Free

释放 GCHandle调用方必须确保对于给定的句柄,只调用 Free 一次

     

GCHandle 类与 GCHandleType 枚举结合使用以创建对应于任何托管对象的句柄。此句柄可为四种类型之一:WeakWeakTrackResurrectionNormal Pinned。分配了句柄以后,在非托管客户端保留唯一的引用时,可以使用 GCHandle 防止垃圾回收器回收托管对象。如果没有这样的句柄,则在该对象代表非托管客户端完成工作以前,有可能被垃圾回收器回收。

  
使用GCHandle的简单示例
下面的示例演示App类如何使用GCHandle.Alloc方法创建托管对象的句柄,以防回收托管对象。
示例使用了一个非托管函数,从User32.dll导出的EnumWindows。首先我们来了解一下EnumWindows的原始函数声明以及该函数的使用:
(This function enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is enumerated or the callback function returns FALSE. )
BOOL EnumWindows( 
  WNDENUMPROC lpEnumFunc, 
  LPARAM lParam );
Parameters
lpEnumFunc
[in] Long pointer to an application-defined callback function.
lParam
[in, out] Specifies an application-defined value to be passed to the callback function.
 
其中
BOOL CALLBACK EnumWindowsProc(
  HWND hwnd, 
  LPARAM lParam );
Parameters
hwnd
[out] Handle to a top-level window.
lParam
[in] Specifies the application-defined value given in EnumWindows
Return Values
TRUE continues enumeration. FALSE stops enumeration.
简单地说, WNDENUMPROC 是一个回调函数,我们需要定义一个该类型的回调函数,其中是自己想做的操作,当调用EnumWindows的时候,每次遇到一个窗口,系统就调用一次你的WNDENUMPROC ,然后把窗口句柄通过WNDENUMPROC 的hwnd参数 传给你。而lParam参数则通过EnumWindows间接传递给WNDENUMPROC
示例说明如何将托管对象传递给需要LPARAM类型的非托管函数。LPARAM类型是指向非托管参数的指针。在该示例中,LibWrap类包含EnumWindows方法的托管原型。作为其参数,该托管方法用CallBack委托替换WNDENUMPROC函数指针,并用IntPtr指针替换LPARAM类型。App类使用GCHandle.Alloc方法创建托管对象的句柄,从而防止该托管对象被收集。对EnumWindows方法的调用传递该委托和托管对象,并将句柄强制转换为IntPtr。非托管函数将类型作为回调函数的参数传回调用方。
声明原型
public delegate bool CallBack( int handle, IntPtr param );

public class LibWrap
{
   
// 传入一个托管对象作为LPARAM参数.(经过IntPtr指针的转换)
   
// 为非托管函数声明一个托管的原型
   [ DllImport( "user32.dll" )]
   
public static extern bool EnumWindows( CallBack cb, IntPtr param );
}

调用函数

public class App
{
   
public static void Main()
   {
      TextWriter tw 
= System.Console.Out;
      GCHandle gch 
= GCHandle.Alloc( tw );
      CallBack cewp 
= new CallBack( CaptureEnumWindowsProc );    
      LibWrap.EnumWindows( cewp, (IntPtr)gch );
      gch.Free();
   }
   
   
private static bool CaptureEnumWindowsProc( int handle, IntPtr param )
   {
      GCHandle gch 
= (GCHandle)param;
      TextWriter tw 
= (TextWriter)gch.Target;
      tw.WriteLine( handle );
      
return true;
   }   
}

 

 

了解了GCHandle的作用和使用方法之后,来看看我们实际问题的解决方案。呵呵,以上只是开胃小菜,大餐要真正开始了。
1C#构建一个Project类的单件模式
建立一个C#语言的Class Libray工程。在该工程中采用单件模式定义并实现我们所需要的工程管理类Project.
namespace ProjectManager
{
    
public class Project
    {
        
private static Project activeProject = null;
        
private Project() { }        

        
public static Project ActiveProject
        {
            
get
            {
                
if (activeProject == null)
                    activeProject 
= new Project();
                
return activeProject;
            } 
        }
        
public bool DoWorkAboutXML()
        {
            
//...
            return true;
        }
        
public bool DoWorkAboutDB()
        {
            
//...
            return true;
        }
    }
}

由于Project的实现采用了单件模式,因此可以保证在任何时刻都只可能有一个Project对象实例存在。通过Project::ActiveProject属性实现该唯一对象的访问。

 

2.采用托管C++包装一个相应的AcPpProject类型

首先创建一个支持CLR的托管工程,声明一个C++类--AcPpProject,在类型定义使用 GCHandle 创建对应于托管对象Project::ActiveProject的句柄。从而可以效防止在该对象代表非托管客户端完成工作以前被垃圾回收器回收。 Class AcPpProject类型中通过一个void *指向GCHandle object.

//// ProjectManagerMgd.h

class __declspec(dllexport) AcPpProject
{
public:
    AcPpProject();
    
~AcPpProject();
    
bool DoWorkAboutXML();    
    
bool DoWorkAboutDB();  
protected:
    
void* m_pProjectGCHandle;
};

// ProjectManagerMgd.cpp

#include 
"ProjectManagerMgd.h"
using namespace ProjectManager;
using namespace System::Runtime::InteropServices;

AcPpProject::AcPpProject()
{
    GCHandle gch 
= GCHandle::Alloc(Project::ActiveProject);

    m_pProjectGCHandle 
= GCHandle::ToIntPtr(gch).ToPointer();
}

AcPpProject::
~AcPpProject ()
{
    GCHandle gch 
= GCHandle::FromIntPtr(System::IntPtr(m_pProjectGCHandle)); 
    gch.Free();
    m_pProjectGCHandle 
= 0;
}

对于Project中的每一个实例方法,在AcPpProject都有相应的对应方法定义,并通过将m_pProjectGCHandle还原为原来的Project对象来具体实现调用。因此AcPpProject中的对应方法函数都是对Project中的方法的包装实现。 

inline Project^ GETPROJECT(void* pProjectGCHandle)
{
    Project
^ pProject = nullptr;
    
if (pProjectGCHandle != 0)
    {
        GCHandle gch 
= GCHandle::FromIntPtr(System::IntPtr(pProjectGCHandle));
        pProject 
= static_cast<Project^>(gch.Target);
    }
    
return pProject;
}

bool AcPpProject::DoWorkAboutXML()
{
    Project
^ pProject = GETPROJECT(m_pProjectGCHandle);
    
if (pProject == nullptr)
    {
        
return false;
    }
    
return pProject->DoWorkAboutXML();    
}

bool AcPpProject::DoWorkAboutDB()
{
    Project
^ pProject = GETPROJECT(m_pProjectGCHandle);
    
if (pProject == nullptr)
    {
        
return false;
    }
    
return pProject->DoWorkAboutDB();
}

 

 

因为我们在AcPpProject的声明中没有出现任何托管相关的内容(使用void指针巧妙替换了GCHandle对象),并使用__declspec(dllexport)AcPpProjectexport出去,从而保证ProjectManager.h可以被用于任何native C++ (no /cli)工程。这样一来,整个过程中将统一使用唯一的一个托管Project类对象。实现了跨越C#C++两种语言的单件模式。
Note:
 
A GCHande is a managed object that allows native code to access a managed variable. Managed variables may be moved around in physical memory by the managed runtime memory manager and therefore native code cannot have a direct pointer to them. That is where GCHandle comes in. The managed memory manager does not move GCHandle around so native code can have a reliable pointer to it. If the managed memory manager moves the managed variable, it updates any GCHandles that point to the variable that was moved.  
 
 
Let me try to clarify relationships:
 
 
 
Native pointer               GCHandle               Managed Variable
 
X                                 Y                           Z   (can move around in memory)
 
X is a native pointer to Y.
 
Y is created and not normally exist, once created, the managed run-time keeps it up to date.
 
Y.Target is Z