DCOM服务器和Cs客户端互操作完全解释.doc_第1页
DCOM服务器和Cs客户端互操作完全解释.doc_第2页
DCOM服务器和Cs客户端互操作完全解释.doc_第3页
DCOM服务器和Cs客户端互操作完全解释.doc_第4页
DCOM服务器和Cs客户端互操作完全解释.doc_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C+ DCOM服务器和C#客户端互操作完全解释发布者:豆豆网 日期: 2010-07-05 00:00:00浏览次数:61今天有个网友问我如何编写一个DCOM服务器,可以在C#的客户端中调用。看起来还是有很多人在用COM技术,趁这个机会,就把DCOM和C#之间的互操作好好讲讲。实际上,C#调用DCOM服务器的时候,只需要在C#这边做一些手脚,对于原先的C+ DCOM服务器来说,是不需要做任何改动的。道理很简单,C#后于C+ DCOM技术出现,作为前辈的DCOM技术不可能预知采用什么技术支持小辈C#。在C#里面使用DCOM的服务,跟 C+的COM客户端的步骤是一样的,即:1. 查询注册表,启动CLSID对应的COM服务器,并激活COM对象。2. 根据IID获取COM的指针,然后调用COM对象提供的服务。当C#尝试调用DCOM服务的时候,实际上步骤是一样的,只不过前面两步的工作由所谓的PIA(Primary Interop Assembly)做了,更精确地说,是创建了一个只包含抽象函数的类来实现的。每次C#程序调用这个类的抽象函数的时候,CLR会自动将调用转换成对应的COM调用。DCOM服务器 为了简单起见,我们先来写一个最简单的DCOM服务器,这个DCOM服务器很简单,不能被客户端自动激活(自动激活的技术后面的文章讲),只能在客户端连接之前手工启动。这样做的目的,是为了让本文能够更专注的解释C#客户端使用DCOM服务器的过程因为把COM库后台执行的操作尽可能地排除掉了。下面是这个DCOM服务器的源代码:1. #define INC_OLE22. #define STRICT3. #include 4. #include 5. #include 6. 7. DEFINE_GUID(CLSID_SimpleObject, 0x5e9ddec7, 0x5767, 0x11cf, 0xbe, 0xab, 0x0, 0xaa, 0x0, 0x6c, 0x36, 0x6);8. 9. HANDLE hevtDone;10. 11. class CClassFactory : public IClassFactory 12. public:13. STDMETHODIMP QueryInterface (REFIID riid, void* ppv);14. STDMETHODIMP_(ULONG) AddRef(void) return 1; ;15. STDMETHODIMP_(ULONG) Release(void) return 1; 16. 17. STDMETHODIMP CreateInstance (LPUNKNOWN punkOuter, REFIID iid, void *ppv);18. STDMETHODIMP LockServer (BOOL fLock) return E_FAIL; ;19. ;20. 21. class CSimpleObject : public IStream 22. public:23. STDMETHODIMP QueryInterface (REFIID iid, void *ppv);24. STDMETHODIMP_(ULONG) AddRef(void) return InterlockedIncrement(&m_cRef); ;25. STDMETHODIMP_(ULONG) Release(void) if (InterlockedDecrement(&m_cRef) = 0) delete this; return 0; return 1; 26. 27. STDMETHODIMP Read(void *pv, ULONG cb, ULONG *pcbRead);28. STDMETHODIMP Write(VOID const *pv, ULONG cb, ULONG *pcbWritten);29. STDMETHODIMP Seek(LARGE_INTEGER dbMove, DWORD dwOrigin, ULARGE_INTEGER *pbNewPosition)30. return E_FAIL; 31. STDMETHODIMP SetSize(ULARGE_INTEGER cbNewSize)32. return E_FAIL; 33. STDMETHODIMP CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)34. return E_FAIL; 35. STDMETHODIMP Commit(DWORD grfCommitFlags)36. return E_FAIL; 37. STDMETHODIMP Revert(void)38. return E_FAIL; 39. STDMETHODIMP LockRegion(ULARGE_INTEGER bOffset, ULARGE_INTEGER cb, DWORD dwLockType)40. return E_FAIL; 41. STDMETHODIMP UnlockRegion(ULARGE_INTEGER bOffset, ULARGE_INTEGER cb, DWORD dwLockType)42. return E_FAIL; 43. STDMETHODIMP Stat(STATSTG *pstatstg, DWORD grfStatFlag)44. return E_FAIL; 45. STDMETHODIMP Clone(IStream *ppstm)46. return E_FAIL; 47. 48. CSimpleObject() m_cRef = 1; 49. CSimpleObject() SetEvent(hevtDone); 50. 51. private:52. LONG m_cRef;53. ;54. 55. CClassFactory g_ClassFactory;56. 57.void58. Message(LPTSTR szPrefix, HRESULT hr)59. 60. LPTSTR szMessage;61. 62. if (hr = S_OK)63. 64. wprintf(szPrefix);65. wprintf(TEXT(n);66. return;67. 68.69. if (HRESULT_FACILITY(hr) = FACILITY_WINDOWS)70. hr = HRESULT_CODE(hr);71. 72. FormatMessage(73. FORMAT_MESSAGE_ALLOCATE_BUFFER |74. FORMAT_MESSAGE_FROM_SYSTEM |75. FORMAT_MESSAGE_IGNORE_INSERTS,76. NULL,77. hr,78. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /The user default language79. (LPTSTR)&szMessage,80. 0,81. NULL );82. 83. wprintf(TEXT(%s: %s(%lx)n), szPrefix, szMessage, hr);84. 85. LocalFree(szMessage);86. / Message87. 88.STDMETHODIMP89. CSimpleObject:QueryInterface(REFIID riid, void* ppv)90. 91. if (ppv = NULL)92. return E_INVALIDARG;93. if (riid = IID_IUnknown | riid = IID_IStream)94. 95. *ppv = (IUnknown *) this;96. AddRef();97. return S_OK;98. 99. *ppv = NULL;100. return E_NOINTERFACE;101. / CSimpleObject:QueryInterface102. 103.STDMETHODIMP104. CSimpleObject:Read(void *pv, ULONG cb, ULONG *pcbRead)105. 106. Message(TEXT(Server: IStream:Read), S_OK);107. if (!pv & cb != 0)108. return E_INVALIDARG;109. 110. / 对于读取操作,只是简单地把数组的内容设置为0xFF111. if (cb != 0)112. memset(pv, 0xFF, cb);113. 114. if (pcbRead)115. *pcbRead = cb;116. return S_OK;117. / CSimpleObject:Read118. 119.STDMETHODIMP120. CSimpleObject:Write(VOID const *pv, ULONG cb, ULONG *pcbWritten)121. 122. Message(TEXT(Server: IStream:Write), S_OK);123. if (!pv & cb != 0)124. return E_INVALIDARG;125. 126. / 不执行任何写操作,只是简单地更新pcbWritten,127. / 这样客户端就会误认为写操作已经成功。128. if (pcbWritten)129. *pcbWritten = cb;130. return S_OK;131. / CSimpleObject:Write132. 133.STDMETHODIMP134. CClassFactory:QueryInterface(REFIID riid, void* ppv)135. 136. if (ppv = NULL)137. return E_INVALIDARG;138. if (riid = IID_IClassFactory | riid = IID_IUnknown)139. 140. *ppv = (IClassFactory *) this;141. AddRef();142. return S_OK;143. 144. *ppv = NULL;145. return E_NOINTERFACE;146. / CClassFactory:QueryInterface147. 148.STDMETHODIMP149. CClassFactory:CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void* ppv)150. 151. LPUNKNOWN punk;152. HRESULT hr;153. 154. *ppv = NULL;155. 156. if (punkOuter != NULL)157. return CLASS_E_NOAGGREGATION;158. 159. Message(TEXT(Server: IClassFactory:CreateInstance), S_OK);160. 161. punk = new CSimpleObject;162. 163. if (punk = NULL)164. return E_OUTOFMEMORY;165. 166. hr = punk-QueryInterface(riid, ppv);167. punk-Release();168. return hr;169. / CClassFactory:CreateInstance170. 171.void _cdecl172. main()173. 174. HRESULT hr;175. DWORD dwRegister;176. 177. / 创建一个Windows事件,等待客户端来创建178. / CSimpleObject对象,模拟一个一直在线的DCOM服务器179. hevtDone = CreateEvent(NULL, FALSE, FALSE, NULL);180. if (hevtDone = NULL)181. 182. hr = HRESULT_FROM_WIN32(GetLastError();183. Message(TEXT(Server: CreateEvent), hr);184. exit(hr);185. 186. 187. hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);188. if (FAILED(hr)189. 190. Message(TEXT(Server: CoInitializeEx), hr);191. exit(hr);192. 193. 194. / 注册类厂 195. hr = CoRegisterClassObject(CLSID_SimpleObject, &g_ClassFactory,196. CLSCTX_SERVER, REGCLS_SINGLEUSE, &dwRegister);197. if (FAILED(hr)198. 199. Message(TEXT(Server: CoRegisterClassObject), hr);200. exit(hr);201. 202. 203. Message(TEXT(Server: Waiting), S_OK);204. 205. / 等待DCOM客户的请求206. WaitForSingleObject(hevtDone, INFINITE);207. 208. CloseHandle(hevtDone);209. 210. CoUninitialize();211. Message(TEXT(Server: Done), S_OK);212. / main这个DCOM服务器很简单,就是包含了一个实现了IStream接口的COM对象,类厂也在这个DCOM服务器中实现,main里面的逻辑就是当程序被手工启动以后,一直等待客户端的请求,当完成一个客户的请求以后,退出。第7行定义一个CSimpleObject的CLSID,58行的Message函数用来打印一个日志,跟踪各个函数的调用。然后将下面的键值写入注册表里面:HKEY_CLASSES_ROOTCLSID5e9ddec7-5767-11cf-beab-00aa006c3606 = Simple Object Server HKEY_CLASSES_ROOTCLSID5e9ddec7-5767-11cf-beab-00aa006c3606LocalServer32 =c:simplesserverWin32Debugsserver.exeC+客户端 使用下面的C+ DCOM客户端程序来验证一下DCOM服务器:1. #define INC_OLE22. #include 3. #include 4. #include 5. #include 6. #include 7. 8. DEFINE_GUID(CLSID_SimpleObject, 0x5e9ddec7, 0x5767, 0x11cf, 0xbe, 0xab, 0x0, 0xaa, 0x0, 0x6c, 0x36, 0x6);9. 10. const ULONG cbDefault = 4096; 11. 12.void13. Message(LPTSTR szPrefix, HRESULT hr)14. 15. LPTSTR szMessage;16. 17. if (hr = S_OK)18. 19. wprintf(szPrefix);20. return;21. 22.23. if (HRESULT_FACILITY(hr) = FACILITY_WINDOWS)24. hr = HRESULT_CODE(hr);25.26. FormatMessage(27. FORMAT_MESSAGE_ALLOCATE_BUFFER |28. FORMAT_MESSAGE_FROM_SYSTEM |29. FORMAT_MESSAGE_IGNORE_INSERTS,30. NULL,31. hr,32. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 33. (LPTSTR)&szMessage,34. 0,35. NULL);36. 37. wprintf(TEXT(%s: %s(%lx)n), szPrefix, szMessage, hr);38. 39. LocalFree(szMessage);40. / Message41. 42.void43. OutputTime(LARGE_INTEGER* pliStart, LARGE_INTEGER* pliFinish)44. 45. LARGE_INTEGER liFreq;46. 47. QueryPerformanceFrequency(&liFreq);48. wprintf(TEXT(%0.4f secondsn),49. (float)(pliFinish-LowPart - pliStart-LowPart)/(float)liFreq.LowPart);50. / OutputTime51. 52.void _cdecl53. main(int argc, CHAR *argv)54. 55. HRESULT hr;56. MULTI_QI mq;57. COSERVERINFO csi, *pcsi=NULL;58. WCHAR wsz MAX_PATH;59. ULONG cb = cbDefault;60. LARGE_INTEGER liStart, liFinish;61. 62. / 如果有参数的话,那第一个参数就是运行DCOM服务器的63. / 的机器名。其实这个步骤,对于C#程序来说,是没有64. / 办法支持的,但是不要着急,可以通过修改注册表65. / 的方式来实现66. if (argc 1)67. 68. MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv1, -1,69. wsz, sizeof(wsz)/sizeof(wsz0);70. memset(&csi, 0, sizeof(COSERVERINFO);71. csi.pwszName = wsz;72. pcsi = &csi;73. 74. 75. / 第二个参数是IStream读写的字节数目76. if (argc 2)77. cb = atol(argv2);78. 79. hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);80. if (FAILED(hr)81. 82. Message(TEXT(Client: CoInitializeEx), hr);83. exit(hr);84. 85. 86. Message(TEXT(Client: Creating Instance.), S_OK);87. mq.pIID = &IID_IStream;88. mq.pItf = NULL;89. mq.hr = S_OK;90. QueryPerformanceCounter(&liStart);91. hr = CoCreateInstanceEx(CLSID_SimpleObject, NULL, CLSCTX_SERVER, pcsi, 1, &mq);92. QueryPerformanceCounter(&liFinish);93. OutputTime(&liStart, &liFinish);94. 95. if (FAILED(hr)96. Message(TEXT(Client: CoCreateInstanceEx), hr);97. else98. 99. LPVOID pv;100. LPSTREAM pstm = (IStream*)mq.pItf;101. if (!pstm)102. 103. Message(TEXT(Client: NULL Interface pointer),E_FAIL);104. exit(E_FAIL);105. 106. 107. / 执行读取操作108. Message(TEXT(Client: Reading data.), S_OK);109. pv = CoTaskMemAlloc(cb);110. QueryPerformanceCounter(&liStart);111. hr = pstm-Read(pv, cb, NULL);112. QueryPerformanceCounter(&liFinish);113. OutputTime(&liStart, &liFinish);114. if (FAILED(hr)115. Message(TEXT(Client: IStream:Read), hr);116. 117. / “写入”一些数据118. Message(TEXT(Client: Writing data.), S_OK);119. QueryPerformanceCounter(&liStart);120. hr = pstm-Write(pv, cb, NULL);121. QueryPerformanceCounter(&liFinish);122. OutputTime(&liStart, &liFinish);123. if (FAILED(hr)124. Message(TEXT(Client: IStream:Write), hr);125. 126. pstm-Release();127. 128. 129. CoUninitialize();130. Message(TEXT(Client: Done), S_OK);131. / main第62行的代码,DCOM既然是远程服务器,那它就应该是可以运行在另外一台机器上,然后被其他机器的客户端所使用。所以C+的客户端代码里,你可以通过编程的方式指定服务器的名称,但是对于C#来说,因为连接到DCOM服务器并激活COM对象的操作是由CLR完成的,没有办法在代码里指定。不过不用着急,指定DCOM服务器还有另外一个方式,就是修改注册表的键值,告诉本机的COM运行库,服务器在另外一台机器上,请把下面的键值添加到客户端机器的注册表里:HKEY_CLASSES_ROOTAPPID5e9ddec7-5767-11cf-beab-00aa006c3606RemoteServerName=然后确保DCOM服务器端的机器的注册表里,下面的键值是“Y”:HKEY_LOCAL_MACHINESoftwareMicrosoftOLEEnableRemoteConnect第91行代码就是激活DCOM服务器的代码了。C#客户端 既然已经知道C+客户端是如何连接和激活DCOM对象以后,我们来看看在C#里面如何做,在C#里面,我们是通过下面的步骤来连接和激活DCOM对象的:1. 需要知道要激活的DCOM对象的CLSID,这样CLR才能让COM运行库查询注册表,启动注册表CLSID下面的LocalServer32设置的可执行程序(我们的例子里,是sserver.exe)。a) 至于COM运行库是如何根据CLSID启动DCOM服务器的,这篇文章里不讲,因为本文中我们的DCOM服务器是需要手工启动的。2. 获取已经激活的DCOM对象的指针,接着再是查询对应的COM接口,本文的例子里是IStream接口,这样在C#程序里面才能调用。但是又涉及到另外一个问题,C#是强类型语言,所有的对象调用都是要有明确的类型定义的。为了解决这个问题,我们需要在C#程序里自己定义好COM对象和接口的定义。为了解决上面两步操作,CLR团队提供了tlbimp.exe这个程序,这个程序需要一个类型库(.tlb)文件,从类型库中获取COM对象和接口的定义,然后将这些定义转换成C#的定义,最后将C#的定义封装到一个所谓的Interop Assembly里。因此在C#客户端,只需要引用这个Interop Assembly就可以了,关系图如下:生成Interop Assembly 因为需要生成一个类型库(.tlb)文件,所以我们需要手工创建一个IDL文件,显示地列出DCOM对象和接口的定义,下面是这个IDL文件的定义:1. import oaidl.idl;2. import ocidl.idl;3. 4. 5. uuid(7FF2526D-2672-4e13-9F95-93E9B1247B15),6. version(1.0),7. helpstring(Demo Simple Object 1.0 Type Library)8. 9. library DemoSimpleObjectLib10. 11. importlib(stdole2.tlb);12. 13. 14. uuid(5e9ddec7-5767-11cf-beab-00aa006c3606),15. helpstring(Demo Simple Class)16. 17. coclass SimpleObjectClass 18. default interface IStream;19. 20. 因为IStream接口是COM库自带的,所以我引入了oaidl.idl和ocidl.idl文件,将IStream接口的定义加进来。第9行声明了一个类型库DemoSimpleObjectLib,第5行指定了类型库的GUID,这个GUID会在注册表注册这个类型库的时候用到,但我们这次不需要让COM运行库知道DemoSimpleObjectLib这个类型库,所以不会注册这个类型库。第17行列出了DCOM对象SimpleObjectClass的定义,由于这个对象只实现了一个接口,所以在18行就只列出了这个接口。注意,你不需要在DCOM对象(coclass)的定义里将对象的函数全部列出,因为COM是接口式变成,知道实现什么接口以后,就知道DCOM对象里有什么函数了。把这个文件保存为demos

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论