用C#编写一个进程外的COM组件
我在以前的一篇文章《COM互操作 - 在VB 脚本里面使用.NET类型》里面写过如何在COM客户端程序里面使用.NET组件,但是这些.NET组件都属于进程内的组件,即COM客户端需要将CLR和.NET组件都加载进自身进程的内存空间里面才能使用。上一次在MSDN中文论坛上看到有网友问如何使用C#编写一个进程外的COM组件,由于在使用regasm.exe注册.NET组件的时候,regasm.exe将.NET组件里面发布的COM可见的类型对应CLSID的键值里加上了InprocServer32项,并且设置值为mscoree.dll。这也就是说,.NET的默认实现强制了我们只能在COM里面激活进程内的.NET组件,但是如何用.NET实现进程外的组件呢?难道真的要我们写一个新的COM程序来Host CLR?
答案是否定的,这里是另外一个替代方案,你需要完成下面这些步骤:
1. 在C#代码里面自己实现一个ClassFactory,用来激活我们的Com可见的(Com Visible)类型。
2. 调用COM API CoRegisterClassObject将我们自己的ClassFactory注册在COM库里面,以便监听COM的激活申请。
3. COM端使用完毕以后,可以通过调用CoRevokeClassObject撤销我们ClassFactory在COM库里面的注册。
4. 如果我们的COM客户端是C++编写的话,并且采用前绑定接口的方式使用我们的Com可见(Com Visible)类型的话,为了能够将接口指针跨越进程边界传输,你还需要将.NET Assembly生成的Tlb文件注册,向COM库注册列集(Marshaling)接口的方法。
NET 代码
TestComVisibleClass.cs
1. using System; 2. using System.Runtime.InteropServices; 3. using System.Windows.Forms; 4. 5. namespace TestComServer 6. { 7. internal static class ComHelperClass 8. { 9. public const string s_IID_ITestComVisible = "C66C0654-49AE-4f2e-8EDA-BD01C8259C20"; 10. public const string s_CLSID_TestComVisibleClass = "12D783BB-33BF-4973-B38B-2A8F0BA926E4"; 11. public static readonly Guid IID_ITestComVisible = new Guid(s_IID_ITestComVisible); 12. public static readonly Guid CLSID_TestComVisibleClass = new Guid(s_CLSID_TestComVisibleClass); 13. 14. public const string s_IID_IClassFactory = "00000001-0000-0000-C000-000000000046"; 15. public static readonly Guid IID_IClassFactory = new Guid("00000001-0000-0000-C000-000000000046"); 16. public static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); 17. 18. [DllImport("ole32.dll")] 19. public static extern int CoRegisterClassObject( 20. [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, 21. [MarshalAs(UnmanagedType.IUnknown)] object pUnk, 22. uint dwClsContext, 23. uint flags, 24. out uint lpdwRegister); 25. 26. [DllImport("ole32.dll")] 27. public static extern int CoRevokeClassObject(uint dwRegister); 28. 29. [DllImport("ole32.dll")] 30. public static extern int CoInitializeSecurity( 31. IntPtr securityDescriptor, 32. Int32 cAuth, 33. IntPtr asAuthSvc, 34. IntPtr reserved, 35. UInt32 AuthLevel, 36. UInt32 ImpLevel, 37. IntPtr pAuthList, 38. UInt32 Capabilities, 39. IntPtr reserved3); 40. 41. public const int RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; // Encrypted DCOM communication 42. public const int RPC_C_IMP_LEVEL_IDENTIFY = 2; // No impersonation really required 43. public const int CLSCTX_LOCAL_SERVER = 4; 44. public const int REGCLS_MULTIPLEUSE = 1; 45. public const int EOAC_DISABLE_AAA = 0x1000; // Disable Activate-as-activator 46. public const int EOAC_NO_CUSTOM_MARSHAL = 0x2000; // Disable custom marshalling 47. public const int EOAC_SECURE_REFS = 0x2; // Enable secure DCOM references 48. public const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110); 49. public const int E_NOINTERFACE = unchecked((int)0x80004002); 50. 51. } 52. 53. [ComVisible(true)] 54. [Guid(ComHelperClass.s_IID_ITestComVisible)] 55. public interface ITestComVisible 56. { 57. [DispId(1)] 58. string TestProperty { get; set; } 59. 60. [DispId(2)] 61. void TestMethod(); 62. } 63. 64. [ComVisible(true)] 65. [Guid(ComHelperClass.s_CLSID_TestComVisibleClass)] 66. public class TestComVisibleClass : ITestComVisible 67. { 68. public string TestProperty { get; set; } 69. 70. public void TestMethod() 71. { 72. MessageBox.Show("Test Method"); 73. } 74. } 75. 76. // 类厂 77. [ 78. ComImport, 79. InterfaceType(ComInterfaceType.InterfaceIsIUnknown), 80. Guid(ComHelperClass.s_IID_IClassFactory) 81. ] 82. internal interface IClassFactory 83. { 84. [PreserveSig] 85. int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject); 86. 87. [PreserveSig] 88. int LockServer(bool fLock); 89. } 90. 91. internal class ComClassFactory : IClassFactory 92. { 93. #region IClassFactory Members 94. 95. public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject) 96. { 97. ppvObject = IntPtr.Zero; 98. 99. if (pUnkOuter != IntPtr.Zero) 100. Marshal.ThrowExceptionForHR(ComHelperClass.CLASS_E_NOAGGREGATION); 101. 102. if (riid == ComHelperClass.IID_ITestComVisible || 103. riid == ComHelperClass.IID_IUnknown) 104. { 105. ppvObject = Marshal.GetComInterfaceForObject( 106. new TestComVisibleClass(), typeof(ITestComVisible)); 107. } 108. else 109. Marshal.ThrowExceptionForHR(ComHelperClass.E_NOINTERFACE); 110. 111. return 0; // S_OK 112. } 113. 114. public int LockServer(bool fLock) 115. { 116. return 0; // S_OK 117. } 118. 119. #endregion 120. } 121. } |
Program.cs
1. using System; 2. using System.Windows.Forms; 3. using System.Runtime.InteropServices; 4. 5. namespace TestComServer 6. { 7. static class Program 8. { 9. private static uint m_ComCookie = 0; 10. 11. /// <summary> 12. /// The main entry point for the application. 13. /// </summary> 14. [STAThread] 15. static void Main() 16. { 17. Application.EnableVisualStyles(); 18. Application.SetCompatibleTextRenderingDefault(false); 19. 20. RegisterDcomServer(); 21. 22. Application.ApplicationExit += new EventHandler(Application_ApplicationExit); 23. Application.Run(new Form1()); 24. } 25. 26. static void Application_ApplicationExit(object sender, EventArgs e) 27. { 28. RevokeDcomServer(); 29. } 30. 31. private static void RegisterDcomServer() 32. { 33. // 做一些安全检查,确保只有一些有权限的人才能调用你的C# Dcom组件 34. // 如果你对安全性不关心的话,可以删除下面的语句 35. int hr = ComHelperClass.CoInitializeSecurity( 36. IntPtr.Zero, // 这里要输入你的安全描述符 37. -1, 38. IntPtr.Zero, 39. IntPtr.Zero, 40. ComHelperClass.RPC_C_AUTHN_LEVEL_PKT_PRIVACY, 41. ComHelperClass.RPC_C_IMP_LEVEL_IDENTIFY, 42. IntPtr.Zero, 43. ComHelperClass.EOAC_DISABLE_AAA | ComHelperClass.EOAC_SECURE_REFS | ComHelperClass.EOAC_NO_CUSTOM_MARSHAL, 44. IntPtr.Zero); 45. if (hr != 0) 46. Marshal.ThrowExceptionForHR(hr); 47. 48. hr = ComHelperClass.CoRegisterClassObject( 49. ComHelperClass.CLSID_TestComVisibleClass, 50. new ComClassFactory(), 51. ComHelperClass.CLSCTX_LOCAL_SERVER, 52. ComHelperClass.REGCLS_MULTIPLEUSE, 53. out m_ComCookie); 54. if (hr != 0) 55. Marshal.ThrowExceptionForHR(hr); 56. } 57. 58. private static void RevokeDcomServer() 59. { 60. if (m_ComCookie != 0) 61. ComHelperClass.CoRevokeClassObject(m_ComCookie); 62. } 63. } 64. } |
注册表代码
1. Windows Registry Editor Version 5.00 2. 3. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}] 4. 5. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}"1.0] 6. @="TestComServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 7. 8. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}"1.0"0] 9. 10. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}"1.0"0"win32] 11. @="D:""Workspace""Forum""Test""TestComServer""bin""Debug""TestComServer.tlb" 12. 13. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}"1.0"FLAGS] 14. @="0" 15. 16. [HKEY_CLASSES_ROOT"TypeLib"{9903F14C-12CE-4c99-9986-2EE3D7D588A8}"1.0"HELPDIR] 17. 18. [HKEY_CLASSES_ROOT"Interface"{C66C0654-49AE-4f2e-8EDA-BD01C8259C20}] 19. 20. [HKEY_CLASSES_ROOT"Interface"{C66C0654-49AE-4f2e-8EDA-BD01C8259C20}"ProxyStubClsid] 21. @="{00020424-0000-0000-C000-000000000046}" 22. 23. [HKEY_CLASSES_ROOT"Interface"{C66C0654-49AE-4f2e-8EDA-BD01C8259C20}"ProxyStubClsid32] 24. @="{00020424-0000-0000-C000-000000000046}" 25. 26. [HKEY_CLASSES_ROOT"Interface"{C66C0654-49AE-4f2e-8EDA-BD01C8259C20}"TypeLib] 27. "Version"="1.0" 28. @="{9903F14C-12CE-4c99-9986-2EE3D7D588A8}" 29. 30. [HKEY_CLASSES_ROOT"CLSID"{12D783BB-33BF-4973-B38B-2A8F0BA926E4}] 31. @="TestComServer.TestComVisibleClass" 32. 33. [HKEY_CLASSES_ROOT"CLSID"{12D783BB-33BF-4973-B38B-2A8F0BA926E4}"Implemented Categories] 34. 35. [HKEY_CLASSES_ROOT"CLSID"{12D783BB-33BF-4973-B38B-2A8F0BA926E4}"Implemented Categories"{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}] 36. 37. [HKEY_CLASSES_ROOT"CLSID"{12D783BB-33BF-4973-B38B-2A8F0BA926E4}"LocalServer32] 38. @="D:""Workspace""Forum""Test""TestComServer""bin""Debug""TestComServer.exe" 39. 40. [HKEY_CLASSES_ROOT"CLSID"{12D783BB-33BF-4973-B38B-2A8F0BA926E4}"ProgId] 41. @="TestComServer.TestComVisibleClass" |
客户端C++代码
1. // TestComClient.cpp : Defines the entry point for the console application. 2. // 3. 4. #include "stdafx.h" 5. #include <windows.h> 6. #import "D:"Workspace"Forum"Test"TestComServer"bin"Debug"TestComServer.tlb" no_namespace, named_guids 7. 8. int _tmain(int argc, _TCHAR* argv[]) 9. { 10. HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 11. assert(SUCCEEDED(hr)); 12. 13. CLSID clsid; 14. hr = ::CLSIDFromProgIDEx(TEXT("TestComServer.TestComVisibleClass"), &clsid); 15. assert(SUCCEEDED(hr)); 16. 17. MULTI_QI mq; 18. mq.pIID = &IID_ITestComVisible; 19. mq.pItf = NULL; 20. mq.hr = S_OK; 21. hr = ::CoCreateInstanceEx(clsid, NULL, CLSCTX_LOCAL_SERVER, NULL, 1, &mq); 22. assert(SUCCEEDED(hr)); 23. 24. ITestComVisible *pIt = (ITestComVisible *)mq.pItf; 25. pIt->TestMethod(); 26. pIt->Release(); 27. 28. CoUninitialize(); 29. return 0; 30. } |
另外一种客户端,使用VB Script代码
set obj = CreateObject("TestComServer.TestComVisibleClass") obj.TestMethod() |
今天急着回家,下一篇文章再解释代码里面的意思。