如何用C#编写DCOM服务器

How to write a DCOM server in C# 如何用C#编写DCOM服务器

先讲讲.net Remoting与DCOM的区别:.net Remoting 是在DCOM等基础上发展起来的一种技术,它的主要目的是实现跨平台、跨语言、穿透企业防火墙。DCOM是通过TCP/IP通道安全的进程间通信,而.NET remoting 不是。任何进程包括windows服务都能寄宿在DCOM服务器中。

如果你已经十分清楚地知道了COM的话,可以参考下面的几点加深你对DCOM的了解。

1) Your server process will expose a COM class factory that would just create your .NET object.

    你的DCOM服务器进程应该暴露一个COM类工厂以便于创建.net对象

2) In COM you register the class factory using the standard CoRegisterClassObjects API 。

    在COM中尼应该使用标准的API(CoRegisterClassObjects函数)注册类工厂。

3) Make sure you call CoInitializeSecurity on your first process, for example to allow only Administrators to call in 。

   确认在第一个进程中调用CoInitializeSecurity函数,例如尽允许在具有管理员的主机上调用。

4) Register your .NET assemblies with REGASM.EXE. Make sure your .NET class is visible through COM so CCW can be created around it (more details in MSDN on COM Interop section).

    使用REGASM.EXE工具注册.net汇编程序,确认你的.netleu通过COM组件后可见,以便于可以创建CCW。

5) Remove the auto-generated InprocServer32 key after registration (REGASM puts it there but we are going out-of-proc)

  完成注册后移除自动产生的InprocServer32 key。

6) Add the standard LocalServer32 / AppID registry keys.

   添加标准的LocalServer32 / AppID的注册表键值。

view plaincopy to clipboardprint?
using System;  
using System.ComponentModel;  
using System.Data;  
using System.Diagnostics;  
using System.ServiceProcess;  
using System.Threading;  
using System.Runtime.InteropServices;  
namespace Test  
{  
 //   
 // .NET class, interface exposed through DCOM  
 //  
 // exposed COM interface  
 [GuidAttribute(MyService.guidIMyInterface), ComVisible(true)]  
 public interface IMyInterface  
 {  
  string GetDateTime(string prefix);   
 }  
 // exposed COM class  
 [GuidAttribute(MyService.guidMyClass), ComVisible(true)]  
 public class CMyClass: IMyInterface  
 {  
  // Print date & time and the current EXE name  
  public string GetDateTime(string prefix)   
  {   
   Process currentProcess = Process.GetCurrentProcess();  
   return string.Format("{0}: {1} [server-side COM call executed on {2}]",   
    prefix, DateTime.Now, currentProcess.MainModule.ModuleName);  
  }   
 }  
 //  
 // My hosting Windows service  
 //  
 internal class MyService :   
  ServiceBase  
 {  
  public MyService()  
  {  
   // Initialize COM security  
   Thread.CurrentThread.ApartmentState = ApartmentState.STA;  
   UInt32 hResult = ComAPI.CoInitializeSecurity(  
    IntPtr.Zero, // Add here your Security descriptor  
    -1,  
    IntPtr.Zero,  
    IntPtr.Zero,  
    ComAPI.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  
    ComAPI.RPC_C_IMP_LEVEL_IDENTIFY,  
    IntPtr.Zero,  
    ComAPI.EOAC_DISABLE_AAA   
    | ComAPI.EOAC_SECURE_REFS   
    | ComAPI.EOAC_NO_CUSTOM_MARSHAL,  
    IntPtr.Zero);  
   if (hResult != 0)  
    throw new ApplicationException(  
     "CoIntializeSecurity failed" + hResult.ToString("X"));  
  }  
  // The main entry point for the process  
  static void Main()  
  {  
   ServiceBase.Run(new ServiceBase[] { new MyService() });  
  }  
  ///   
  /// On start, register the COM class factory  
  ///   
  protected override void OnStart(string[] args)  
  {  
   Guid CLSID_MyObject = new Guid(MyService.guidMyClass);  
   UInt32 hResult = ComAPI.CoRegisterClassObject(  
    ref CLSID_MyObject,   
    new MyClassFactory(),   
    ComAPI.CLSCTX_LOCAL_SERVER,   
    ComAPI.REGCLS_MULTIPLEUSE,   
    out _cookie);  
   if (hResult != 0)  
    throw new ApplicationException(  
     "CoRegisterClassObject failed" + hResult.ToString("X"));    
  }  
  ///   
  /// On stop, remove the COM class factory registration  
  ///   
  protected override void OnStop()  
  {  
   if (_cookie != 0)  
    ComAPI.CoRevokeClassObject(_cookie);  
  }  
  private int _cookie = 0;  
  //  
  // Public constants  
  //  
  public const string serviceName = "MyService";  
  public const string guidIMyInterface = "e88d15a5-0510-4115-9aee-a8421c96decb";  
  public const string guidMyClass = "f681abd0-41de-46c8-9ed3-d0f4eba19891";  
 }  
 //  
 // Standard installer   
 //  
 [RunInstaller(true)]  
 public class MyServiceInstaller :   
  System.Configuration.Install.Installer  
 {  
  public MyServiceInstaller()  
  {  
   processInstaller = new ServiceProcessInstaller();  
   serviceInstaller = new ServiceInstaller();  
   // Add a new service running under Local SYSTEM  
   processInstaller.Account = ServiceAccount.LocalSystem;  
   serviceInstaller.StartType = ServiceStartMode.Manual;  
   serviceInstaller.ServiceName = MyService.serviceName;  
   Installers.Add(serviceInstaller);  
   Installers.Add(processInstaller);  
  }  
  private ServiceInstaller serviceInstaller;  
  private ServiceProcessInstaller processInstaller;  
 }  
 //  
 // Internal COM Stuff  
 //  
 ///   
 /// P/Invoke calls  
 ///   
 internal class ComAPI  
 {  
  [DllImport("OLE32.DLL")]  
  public static extern UInt32 CoInitializeSecurity(  
   IntPtr securityDescriptor,   
   Int32 cAuth,  
   IntPtr asAuthSvc,  
   IntPtr reserved,  
   UInt32 AuthLevel,  
   UInt32 ImpLevel,  
   IntPtr pAuthList,  
   UInt32 Capabilities,  
   IntPtr reserved3  
   );  
  [DllImport ("ole32.dll")]  
  public static extern UInt32 CoRegisterClassObject (  
   ref Guid rclsid,   
   [MarshalAs (UnmanagedType.Interface)]IClassFactory pUnkn,   
   int dwClsContext,   
   int flags,   
   out int lpdwRegister);  
  [DllImport ("ole32.dll")]  
  public static extern UInt32 CoRevokeClassObject (int dwRegister);  
  public const int RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; // Encrypted DCOM communication  
  public const int RPC_C_IMP_LEVEL_IDENTIFY = 2;  // No impersonation really required  
  public const int CLSCTX_LOCAL_SERVER = 4;   
  public const int REGCLS_MULTIPLEUSE = 1;  
  public const int EOAC_DISABLE_AAA = 0x1000;  // Disable Activate-as-activator  
  public const int EOAC_NO_CUSTOM_MARSHAL = 0x2000; // Disable custom marshalling  
  public const int EOAC_SECURE_REFS = 0x2;   // Enable secure DCOM references  
  public const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);  
  public const int E_NOINTERFACE = unchecked((int)0x80004002);  
  public const string guidIClassFactory = "00000001-0000-0000-C000-000000000046";  
  public const string guidIUnknown = "00000000-0000-0000-C000-000000000046";  
 }  
 ///   
 /// IClassFactory declaration  
 ///   
 [ComImport (), InterfaceType (ComInterfaceType.InterfaceIsIUnknown),   
 Guid (ComAPI.guidIClassFactory)]  
 internal interface IClassFactory  
 {  
  [PreserveSig]  
  int CreateInstance (IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);  
  [PreserveSig]  
  int LockServer (bool fLock);  
 }  
 ///   
 /// My Class factory implementation  
 ///   
 internal class MyClassFactory : IClassFactory  
 {  
  public int CreateInstance (IntPtr pUnkOuter,   
   ref Guid riid,   
   out IntPtr ppvObject)  
  {  
   ppvObject = IntPtr.Zero;  
   if (pUnkOuter != IntPtr.Zero)  
    Marshal.ThrowExceptionForHR (ComAPI.CLASS_E_NOAGGREGATION);  
   if (riid == new Guid(MyService.guidIMyInterface)   
    || riid == new Guid(ComAPI.guidIUnknown))  
   {  
    //  
    // Create the instance of my .NET object  
    //  
    ppvObject = Marshal.GetComInterfaceForObject(  
        new CMyClass(), typeof(IMyInterface));  
   }  
   else 
    Marshal.ThrowExceptionForHR (ComAPI.E_NOINTERFACE);  
   return 0;  
  }  
  public int LockServer (bool lockIt)  
  {  
   return 0;  
  }   
 }  

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
using System.Runtime.InteropServices;
namespace Test
{
 //
 // .NET class, interface exposed through DCOM
 //
 // exposed COM interface
 [GuidAttribute(MyService.guidIMyInterface), ComVisible(true)]
 public interface IMyInterface
 {
  string GetDateTime(string prefix);
 }
 // exposed COM class
 [GuidAttribute(MyService.guidMyClass), ComVisible(true)]
 public class CMyClass: IMyInterface
 {
  // Print date & time and the current EXE name
  public string GetDateTime(string prefix)
  {
   Process currentProcess = Process.GetCurrentProcess();
   return string.Format("{0}: {1} [server-side COM call executed on {2}]",
    prefix, DateTime.Now, currentProcess.MainModule.ModuleName);
  }
 }
 //
 // My hosting Windows service
 //
 internal class MyService :
  ServiceBase
 {
  public MyService()
  {
   // Initialize COM security
   Thread.CurrentThread.ApartmentState = ApartmentState.STA;
   UInt32 hResult = ComAPI.CoInitializeSecurity(
    IntPtr.Zero, // Add here your Security descriptor
    -1,
    IntPtr.Zero,
    IntPtr.Zero,
    ComAPI.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
    ComAPI.RPC_C_IMP_LEVEL_IDENTIFY,
    IntPtr.Zero,
    ComAPI.EOAC_DISABLE_AAA
    | ComAPI.EOAC_SECURE_REFS
    | ComAPI.EOAC_NO_CUSTOM_MARSHAL,
    IntPtr.Zero);
   if (hResult != 0)
    throw new ApplicationException(
     "CoIntializeSecurity failed" + hResult.ToString("X"));
  }
  // The main entry point for the process
  static void Main()
  {
   ServiceBase.Run(new ServiceBase[] { new MyService() });
  }
  ///
  /// On start, register the COM class factory
  ///
  protected override void OnStart(string[] args)
  {
   Guid CLSID_MyObject = new Guid(MyService.guidMyClass);
   UInt32 hResult = ComAPI.CoRegisterClassObject(
    ref CLSID_MyObject,
    new MyClassFactory(),
    ComAPI.CLSCTX_LOCAL_SERVER,
    ComAPI.REGCLS_MULTIPLEUSE,
    out _cookie);
   if (hResult != 0)
    throw new ApplicationException(
     "CoRegisterClassObject failed" + hResult.ToString("X")); 
  }
  ///
  /// On stop, remove the COM class factory registration
  ///
  protected override void OnStop()
  {
   if (_cookie != 0)
    ComAPI.CoRevokeClassObject(_cookie);
  }
  private int _cookie = 0;
  //
  // Public constants
  //
  public const string serviceName = "MyService";
  public const string guidIMyInterface = "e88d15a5-0510-4115-9aee-a8421c96decb";
  public const string guidMyClass = "f681abd0-41de-46c8-9ed3-d0f4eba19891";
 }
 //
 // Standard installer
 //
 [RunInstaller(true)]
 public class MyServiceInstaller :
  System.Configuration.Install.Installer
 {
  public MyServiceInstaller()
  {
   processInstaller = new ServiceProcessInstaller();
   serviceInstaller = new ServiceInstaller();
   // Add a new service running under Local SYSTEM
   processInstaller.Account = ServiceAccount.LocalSystem;
   serviceInstaller.StartType = ServiceStartMode.Manual;
   serviceInstaller.ServiceName = MyService.serviceName;
   Installers.Add(serviceInstaller);
   Installers.Add(processInstaller);
  }
  private ServiceInstaller serviceInstaller;
  private ServiceProcessInstaller processInstaller;
 }
 //
 // Internal COM Stuff
 //
 ///
 /// P/Invoke calls
 ///
 internal class ComAPI
 {
  [DllImport("OLE32.DLL")]
  public static extern UInt32 CoInitializeSecurity(
   IntPtr securityDescriptor,
   Int32 cAuth,
   IntPtr asAuthSvc,
   IntPtr reserved,
   UInt32 AuthLevel,
   UInt32 ImpLevel,
   IntPtr pAuthList,
   UInt32 Capabilities,
   IntPtr reserved3
   );
  [DllImport ("ole32.dll")]
  public static extern UInt32 CoRegisterClassObject (
   ref Guid rclsid,
   [MarshalAs (UnmanagedType.Interface)]IClassFactory pUnkn,
   int dwClsContext,
   int flags,
   out int lpdwRegister);
  [DllImport ("ole32.dll")]
  public static extern UInt32 CoRevokeClassObject (int dwRegister);
  public const int RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; // Encrypted DCOM communication
  public const int RPC_C_IMP_LEVEL_IDENTIFY = 2;  // No impersonation really required
  public const int CLSCTX_LOCAL_SERVER = 4;
  public const int REGCLS_MULTIPLEUSE = 1;
  public const int EOAC_DISABLE_AAA = 0x1000;  // Disable Activate-as-activator
  public const int EOAC_NO_CUSTOM_MARSHAL = 0x2000; // Disable custom marshalling
  public const int EOAC_SECURE_REFS = 0x2;   // Enable secure DCOM references
  public const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
  public const int E_NOINTERFACE = unchecked((int)0x80004002);
  public const string guidIClassFactory = "00000001-0000-0000-C000-000000000046";
  public const string guidIUnknown = "00000000-0000-0000-C000-000000000046";
 }
 ///
 /// IClassFactory declaration
 ///
 [ComImport (), InterfaceType (ComInterfaceType.InterfaceIsIUnknown),
 Guid (ComAPI.guidIClassFactory)]
 internal interface IClassFactory
 {
  [PreserveSig]
  int CreateInstance (IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
  [PreserveSig]
  int LockServer (bool fLock);
 }
 ///
 /// My Class factory implementation
 ///
 internal class MyClassFactory : IClassFactory
 {
  public int CreateInstance (IntPtr pUnkOuter,
   ref Guid riid,
   out IntPtr ppvObject)
  {
   ppvObject = IntPtr.Zero;
   if (pUnkOuter != IntPtr.Zero)
    Marshal.ThrowExceptionForHR (ComAPI.CLASS_E_NOAGGREGATION);
   if (riid == new Guid(MyService.guidIMyInterface)
    || riid == new Guid(ComAPI.guidIUnknown))
   {
    //
    // Create the instance of my .NET object
    //
    ppvObject = Marshal.GetComInterfaceForObject(
        new CMyClass(), typeof(IMyInterface));
   }
   else
    Marshal.ThrowExceptionForHR (ComAPI.E_NOINTERFACE);
   return 0;
  }
  public int LockServer (bool lockIt)
  {
   return 0;
  }
 }
}

下面是注册的CMD批处理脚本:

view plaincopy to clipboardprint?
set EXE_FULL_PATH=%~dp0windowsservice1.exe  
if not exist %EXE_FULL_PATH% @echo Executable %EXE_FULL_PATH% not present in the current directory! & @goto :EOF  
installutil /u %EXE_FULL_PATH%  
installutil %EXE_FULL_PATH%  
regasm %EXE_FULL_PATH% /codebase  
REG.EXE ADD HKCR\AppID\{9922b97d-ce4a-4cc8-a26f-4944708e652d} /v LocalService /t REG_SZ /d MyService /f  
REG.EXE ADD HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\LocalServer32 /ve /t REG_SZ /d %EXE_FULL_PATH% /f  
REG.EXE DELETE HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\InprocServer32 /f 
set EXE_FULL_PATH=%~dp0windowsservice1.exe
if not exist %EXE_FULL_PATH% @echo Executable %EXE_FULL_PATH% not present in the current directory! & @goto :EOF
installutil /u %EXE_FULL_PATH%
installutil %EXE_FULL_PATH%
regasm %EXE_FULL_PATH% /codebase
REG.EXE ADD HKCR\AppID\{9922b97d-ce4a-4cc8-a26f-4944708e652d} /v LocalService /t REG_SZ /d MyService /f
REG.EXE ADD HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\LocalServer32 /ve /t REG_SZ /d %EXE_FULL_PATH% /f
REG.EXE DELETE HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\InprocServer32 /f 

And a test VBS script that will exercise our service from a separate process obviously:

通过一个独立进程的VBS测试脚本来实践我们的服务

view plaincopy to clipboardprint?
Dim obj  
Set obj = CreateObject( "Test.CMyClass" )  
wscript.echo obj.GetDateTime("Current date: ") 
Dim obj
Set obj = CreateObject( "Test.CMyClass" )
wscript.echo obj.GetDateTime("Current date: ")

One more comment: The code above allows everybody to call into the process. This is probably not very useful since you might want to allow only administrators to call into your service. There is a solution though: all you have to do is to pass a certain security descriptor to CoInitializeSecurity that will allow only certain classes of users to call into the process. This is actually not very hard, and I'll probably post a sample code in the future. Now I have to get back to work, so see you for the next time!

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/libicong00/archive/2010/12/02/6049061.aspx

原文地址:https://www.cnblogs.com/Leo_wl/p/2453224.html