灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:3190回复:0

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

楼主#
更多 发布于:2011-11-02 03:44
要求:
为一个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接口。具体例子如下:
[table=95%][tr][td][InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("CF7C704A-6AC3-4963-8818-EF1493CEC2D1")]
public interface IProvider
{
    void Test();
}
[/td][/tr][/table]
实现这个interface,[table=95%][tr][td][ClassInterface(ClassInterfaceType.None)]
[Guid("58C142C7-E599-4921-BF29-33DC0FCCBECA")]
public class ProviderImp : IProvider
{
    public void Test()
    {
        System.Diagnostics.Debug.WriteLine("Test");
    }
}
[/td][/tr][/table]这部分和.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方法能够帮助我们很方便的实现注册和注销。
private static int cookie;
private static RegistrationServices msRegSvc = new RegistrationServices();
public static void RegisterServer()
{
    Guid clsid = typeof(ProviderImp).GUID;
    cookie = msRegSvc.RegisterTypeForComClients(typeof(ProviderImp),
     RegistrationClassContext.LocalServer,RegistrationConnectionType.SingleUse);
}
public static void UnregisterServer()
{
    msRegSvc.UnregisterTypeForComClients(cookie);
}
private static uint appId = 0;
private static void RegisterServerImp(Guid clsid)
{
    IntPtr pCF;
    Guid IID_IClassFactory = new Guid(0x00000001, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
    uint CLSCTX_LOCAL_SERVER = 4;
    uint REGCLS_SINGLEUSE = 0;
    int hr;
   hr = DllGetClassObject(ref clsid, ref IID_IClassFactory, out pCF);
    if (hr < 0)
    {
        throw new COMException("DLLGetClassObject failed.", hr);
    }
   hr = CoRegisterClassObject(ref clsid, pCF, CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, out appId);
    if (hr < 0)
    {
        throw new COMException("CoRegisterClassObject failed.", hr);
    }
}
private static void UnRegisterServerImp(uint appId)
{
    CoRevokeClassObject(appId);
}
3. 将Winform程序的COM对象加入系统的ROT表中 加入ROT表有很多办法,可以通过取得系统IRunningObjectTable接口来直接注册也可以使用OLE的API RegisterActiveObject来实现,这里由于没有特殊要求,我们使用后者来实现。
//define static object to keep COM object alive in whole application lifecycle.
private static ProviderImp msProvider = new ProviderImp();
private static int dwRegister = 0;
public static void RegisterActiveObject()
{
    Guid clsidProviderImp = typeof(ProviderImp).GUID;
    int hr = RegisterActiveObject(msProvider, ref clsidProviderImp, 0, out dwRegister);
}

public static void RevokeActiveObject()
{
    RevokeActiveObject(dwRegister, IntPtr.Zero);
}


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

[DllImport("Ole32.Dll")]
public static extern int CreateBindCtx(int reserved,out IBindCtx bindCtx);

[DllImport("oleaut32.dll")]
private static extern int RegisterActiveObject([MarshalAs(UnmanagedType.IUnknown)] object pUnk, ref Guid rclsid, uint dwFlags, out int pdwRegister);

[DllImport("oleaut32.dll")]
private static extern uint GetActiveObject(ref Guid rclsid, IntPtr pvReserved, [Out]out IntPtr ppunk);

[DllImport("oleaut32.dll")]
private static extern uint RevokeActiveObject(int dwRegister, IntPtr lpReserved);

//from www.pinvoke.net
[DllImport(/**//*NOXLATE*/"ole32.dll")]
private static extern int CoRegisterClassObject(
[In] ref Guid rclsid,
IntPtr pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);

[DllImport("ole32.dll")]
private static extern int CoRevokeClassObject(uint dwRegister);

[DllImport(/**//*NOXLATE*/"mscoree.dll", ExactSpelling = true)]
private static extern int DllGetClassObject(
ref Guid rclsid,
ref Guid riid,
out IntPtr ppv);


2.C++客户端测试代码
#include "stdafx.h"
#import "Server.tlb"

class COMHelper
{
public:
COMHelper(){ ::CoInitialize(NULL);}
~COMHelper(){ ::CoUninitialize();}

HRESULT FindRunningInstance(LPOLESTR lpszItem, IUnknown** ppUnk)
{
HRESULT hr = S_OK;
IMonikerPtr pMoniker;

hr = CreateItemMoniker(_T("!"), lpszItem, ;pMoniker);
if(!SUCCEEDED(hr))
return hr;

IRunningObjectTablePtr pROT;
GetRunningObjectTable(0, ;pROT);
hr = pROT->GetObjectW(pMoniker, ppUnk);
return hr;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
COMHelper comSystem;

IUnknownPtr pUnk;
HRESULT hr = S_OK;
//look up the ROT table directly
hr = comSystem.FindRunningInstance(_T("{58C142C7-E599-4921-BF29-33DC0FCCBECA}"), ;pUnk);
//alternative approach find running object in OLE way
//hr = ::GetActiveObject(__uuidof(Server::ProviderImp), NULL, ;pUnk);

//if no running instance found start a new one.
if(!SUCCEEDED(hr))
{
hr = CoCreateInstance(__uuidof(Server::ProviderImp), NULL, CLSCTX_LOCAL_SERVER,
__uuidof(Server::IProvider), (LPVOID*);pUnk);
if(!SUCCEEDED(hr))
throw 0;
}

//convert IUnknown to IProvider
Server::IProviderPtr pProvider;
hr = pUnk->QueryInterface(__uuidof(Server::IProvider), (LPVOID*);pProvider);
if(!SUCCEEDED(hr))
throw 0;

//call IProvider
pProvider->Test();
return 0;
}




喜欢0 评分0
游客

返回顶部