.Net下进程外COM服务器的实现

 

要求:

为一个WinForm程序暴露一个COM接口,让其它应用程序能够以COM服务器(LocalServer)方式启动这个程序并且对其进行操作
如果发现已经在运行的应用程序,则直接重用当前运行的应用程序进行操作。

分析:
根据要求,分解具体需要解决的技术问题如下:
在WinForm程序中定义一个COM visible接口并实现。
将这个Winform程序变为COM服务器(LocalServer)。
将Winform程序的COM对象加入系统的ROT表中。

验证方法:
客户端通过CoCreateInstance(LocalServer)方式激活这个COM对象,应当看到对应的WinForm程序启动,并且CoCreateInstance成功返回我们所需的Interface指针,
客户端调用Interface的相关方法,Winform程序能够成功执行。
Winform程序运行时,客户端能够在ROT取得Winform程序的IUnknown指针。能够成功QI 成所实现的COM接口,并且调用相关方法成功执行。

解决方案:
1. 在WinForm程序中定义一个COM visible接口并实现
在.Net中定义COM 接口可以通过在接口定义上添加GuidAttribute和InterfaceTypeAttribute,定义该接口的IID并告知CLR该接口需要同时导出为普通的IUnknown COM 接口和OLE automation接口。具体例子如下:

 

None.gif [InterfaceType(ComInterfaceType.InterfaceIsDual)]
None.gif[Guid(
" CF7C704A-6AC3-4963-8818-EF1493CEC2D1 " )]
None.gif
public   interface  IProvider
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
void Test();
ExpandedBlockEnd.gif}

实现这个interface,

 

None.gif [ClassInterface(ClassInterfaceType.None)]
None.gif[Guid(
" 58C142C7-E599-4921-BF29-33DC0FCCBECA " )]
None.gif
public   class  ProviderImp : IProvider
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public void Test()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        System.Diagnostics.Debug.WriteLine(
"Test");
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
 

 

这部分和.Net中实现进程内COM服务器是相同的,在.Net Framework SDK的文档中有详细的介绍。关于在.Net中实现COM组件可以参看MSDN。

2. 将这个Winform程序变为COM服务器(LocalServer)
根据COM本质论中的论述实现进程外COM服务器的需要以下几方面条件;
注册表中对应的CLSID下需要添加LocalServer32键,并把default设为EXE程序的路径
进程外服务器需要在启动时主动向SCM(Service Control Manager)中注册COM Class Object,这样SCM才能创建出对应的COM object返回给客户端,因此.Net程序需要提供一个Class Object(一个实现了IClassFactory COM接口的对象)并调用CoRegisterClassObject将其注册到SCM中。
除此之外,.Net中需要使用regasm命令将assembly中的COM visible类型加入注册表。(注意如果assembly没有加入GAC,请在注册的时候加上/codebase参数否则.Net会无法加载对应的assembly产生奇怪的E_NOINTERFACE错误)

具体的实现方式根据使用的.Net版本有所差异:对于.Net v2.0及其后版本而言,.Net RegistrationServices类提供了RegisterTypeForComClients和UnregisterTypeForComClients方法能够帮助我们很方便的实现注册和注销。

 

None.gif private   static   int  cookie;
None.gif
private   static  RegistrationServices msRegSvc  =   new  RegistrationServices();
None.gif
public   static   void  RegisterServer()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    Guid clsid 
= typeof(ProviderImp).GUID;
InBlock.gif    cookie 
= msRegSvc.RegisterTypeForComClients(typeof(ProviderImp), 
InBlock.gif     RegistrationClassContext.LocalServer,RegistrationConnectionType.SingleUse);
ExpandedBlockEnd.gif}
 
None.gif
None.gif
public   static   void  UnregisterServer()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    msRegSvc.UnregisterTypeForComClients(cookie);
ExpandedBlockEnd.gif}
 
None.gif
None.gif

对于.Net 2.0之前的情况,我们可以通过下面的方式使用.Net自己的IClassFactory实现来实现SCM的注册

 

None.gif private   static   uint  appId  =   0 ;
None.gif
private   static   void  RegisterServerImp(Guid clsid)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    IntPtr pCF;
InBlock.gif    Guid IID_IClassFactory 
= new Guid(0x000000010x00000x00000xC00x000x000x000x000x000x000x46);
InBlock.gif    
uint CLSCTX_LOCAL_SERVER = 4;
InBlock.gif    
uint REGCLS_SINGLEUSE = 0;
InBlock.gif    
int hr; 
InBlock.gif
InBlock.gif    hr 
= DllGetClassObject(ref clsid, ref IID_IClassFactory, out pCF);
InBlock.gif    
if (hr < 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
throw new COMException("DLLGetClassObject failed.", hr);
ExpandedSubBlockEnd.gif    }
 
InBlock.gif
InBlock.gif    hr 
= CoRegisterClassObject(ref clsid, pCF, CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, out appId);
InBlock.gif    
if (hr < 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
throw new COMException("CoRegisterClassObject failed.", hr);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
 
None.gif
None.gif
private   static   void  UnRegisterServerImp( uint  appId)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    CoRevokeClassObject(appId);
ExpandedBlockEnd.gif}
 
None.gif
None.gif

3. 将Winform程序的COM对象加入系统的ROT表中

加入ROT表有很多办法,可以通过取得系统IRunningObjectTable接口来直接注册也可以使用OLE的API RegisterActiveObject来实现,这里由于没有特殊要求,我们使用后者来实现。

 

None.gif // define static object to keep COM object alive in whole application lifecycle.
None.gif
private   static  ProviderImp msProvider  =   new  ProviderImp();
None.gif
private   static   int  dwRegister  =   0 ;
None.gif
public   static   void  RegisterActiveObject()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    Guid clsidProviderImp 
= typeof(ProviderImp).GUID;
InBlock.gif    
int hr = RegisterActiveObject(msProvider, ref clsidProviderImp, 0out dwRegister);
ExpandedBlockEnd.gif}
 
None.gif
None.gif
public   static   void  RevokeActiveObject()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    RevokeActiveObject(dwRegister, IntPtr.Zero);
ExpandedBlockEnd.gif}
 
None.gif
None.gif

附录:
1.相关API的PInvoke定义:

None.gif [DllImport( " Ole32.Dll " )]
None.gif
public   static   extern   int  CreateBindCtx( int  reserved, out  IBindCtx bindCtx); 
None.gif
None.gif[DllImport(
" oleaut32.dll " )]
None.gif
private   static   extern   int  RegisterActiveObject([MarshalAs(UnmanagedType.IUnknown)]  object  pUnk,  ref  Guid rclsid,  uint  dwFlags,  out   int  pdwRegister); 
None.gif
None.gif[DllImport(
" oleaut32.dll " )]
None.gif
private   static   extern   uint  GetActiveObject( ref  Guid rclsid, IntPtr pvReserved, [Out] out  IntPtr ppunk); 
None.gif
None.gif[DllImport(
" oleaut32.dll " )]
None.gif
private   static   extern   uint  RevokeActiveObject( int  dwRegister, IntPtr lpReserved); 
None.gif
None.gif
// from www.pinvoke.net
ExpandedBlockStart.gifContractedBlock.gif
[DllImport( /**/ /*NOXLATE*/ " ole32.dll " )]
None.gif
private   static   extern   int  CoRegisterClassObject(
None.gif[In] 
ref  Guid rclsid,
None.gifIntPtr pUnk,
None.gif
uint  dwClsContext,
None.gif
uint  flags,
None.gif
out   uint  lpdwRegister); 
None.gif
None.gif[DllImport(
" ole32.dll " )]
None.gif
private   static   extern   int  CoRevokeClassObject( uint  dwRegister); 
None.gif
ExpandedBlockStart.gifContractedBlock.gif[DllImport(
/**/ /*NOXLATE*/ " mscoree.dll " , ExactSpelling  =   true )]
None.gif
private   static   extern   int  DllGetClassObject(
None.gif
ref  Guid rclsid,
None.gif
ref  Guid riid,
None.gif
out  IntPtr ppv);
None.gif
None.gif

2.C++客户端测试代码

 

None.gif #include  " stdafx.h "
None.gif#import 
" Server.tlb "  
None.gif
None.gif
class  COMHelper
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
public:
ExpandedSubBlockStart.gifContractedSubBlock.gifCOMHelper()
dot.gif{ ::CoInitialize(NULL);}
ExpandedSubBlockStart.gifContractedSubBlock.gif
~COMHelper()dot.gif{ ::CoUninitialize();} 
InBlock.gif
InBlock.gifHRESULT FindRunningInstance(LPOLESTR lpszItem, IUnknown
** ppUnk)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifHRESULT hr 
= S_OK;
InBlock.gifIMonikerPtr pMoniker; 
InBlock.gif
InBlock.gifhr 
= CreateItemMoniker(_T("!"), lpszItem, &pMoniker);
InBlock.gif
if(!SUCCEEDED(hr))
InBlock.gif
return hr; 
InBlock.gif
InBlock.gifIRunningObjectTablePtr pROT;
InBlock.gifGetRunningObjectTable(
0&pROT);
InBlock.gifhr 
= pROT->GetObjectW(pMoniker, ppUnk);
InBlock.gif
return hr;
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
int  _tmain( int  argc, _TCHAR *  argv[])
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gifCOMHelper comSystem; 
InBlock.gif
InBlock.gifIUnknownPtr pUnk;
InBlock.gifHRESULT hr 
= S_OK;
InBlock.gif
//look up the ROT table directly
InBlock.gif
hr = comSystem.FindRunningInstance(_T("{58C142C7-E599-4921-BF29-33DC0FCCBECA}"), &pUnk);
InBlock.gif
//alternative approach find running object in OLE way
InBlock.gif
//hr = ::GetActiveObject(__uuidof(Server::ProviderImp), NULL, &pUnk); 
InBlock.gif
InBlock.gif
//if no running instance found start a new one.
InBlock.gif
if(!SUCCEEDED(hr))
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifhr 
= CoCreateInstance(__uuidof(Server::ProviderImp), NULL, CLSCTX_LOCAL_SERVER,
InBlock.gif__uuidof(Server::IProvider), (LPVOID
*)&pUnk);
InBlock.gif
if(!SUCCEEDED(hr))
InBlock.gif
throw 0;
ExpandedSubBlockEnd.gif}
 
InBlock.gif
InBlock.gif
//convert IUnknown to IProvider
InBlock.gif
Server::IProviderPtr pProvider;
InBlock.gifhr 
= pUnk->QueryInterface(__uuidof(Server::IProvider), (LPVOID*)&pProvider);
InBlock.gif
if(!SUCCEEDED(hr))
InBlock.gif
throw 0
InBlock.gif
InBlock.gif
//call IProvider
InBlock.gif
pProvider->Test();
InBlock.gif
return 0;
ExpandedBlockEnd.gif}

None.gif

转载于:https://www.cnblogs.com/jonnyyu/archive/2007/07/05/807726.html

你可能感兴趣的:(.Net下进程外COM服务器的实现)