• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C++ DCOM服务器和C#客户端互操作完全解释

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

今天有个网友问我如何编写一个DCOM服务器,可以在C#的客户端中调用。看起来还是有很多人在用COM技术,趁这个机会,就把DCOMC#之间的互操作好好讲讲。

 

实际上,C#调用DCOM服务器的时候,只需要在C#这边做一些手脚,对于原先的C++ DCOM服务器来说,是不需要做任何改动的。道理很简单,C#后于C++ DCOM技术出现,作为前辈的DCOM技术不可能预知采用什么技术支持小辈C#。在C#里面使用DCOM的服务,跟 C++COM客户端的步骤是一样的,即:

1.         查询注册表,启动CLSID对应的COM服务器,并激活COM对象。

2.         根据IID获取COM的指针,然后调用COM对象提供的服务。

 

C#尝试调用DCOM服务的时候,实际上步骤是一样的,只不过前面两步的工作由所谓的PIAPrimary Interop Assembly)做了,更精确地说,是创建了一个只包含抽象函数的类来实现的。每次C#程序调用这个类的抽象函数的时候,CLR会自动将调用转换成对应的COM调用。

 

DCOM服务器

为了简单起见,我们先来写一个最简单的DCOM服务器,这个DCOM服务器很简单,不能被客户端自动激活(自动激活的技术后面的文章讲),只能在客户端连接之前手工启动。这样做的目的,是为了让本文能够更专注的解释C#客户端使用DCOM服务器的过程—因为把COM库后台执行的操作尽可能地排除掉了。

 

下面是这个DCOM服务器的源代码:

1. #define INC_OLE2

2. #define STRICT

3. #include <stdio.h>

4. #include <windows.h>

5. #include <initguid.h>

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. void

58. 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 language

79.         (LPTSTR)&szMessage,

80.         0,

81.         NULL );

82.

83.     wprintf(TEXT("%s: %s(%lx)\n"), szPrefix, szMessage, hr);

84.    

85.     LocalFree(szMessage);

86. } // Message

87.

88. STDMETHODIMP

89. 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::QueryInterface

102.

103. STDMETHODIMP

104. 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.     // 对于读取操作,只是简单地把数组的内容设置为0xFF

111.     if (cb != 0)

112.         memset(pv, 0xFF, cb);

113.

114.     if (pcbRead)

115.         *pcbRead = cb;

116.     return S_OK;

117. } // CSimpleObject::Read

118.

119. STDMETHODIMP

120. 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::Write

132.

133. STDMETHODIMP

134. 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::QueryInterface

147.

148. STDMETHODIMP

149. 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::CreateInstance

170.

171. void __cdecl

172. 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行定义一个CSimpleObjectCLSID58行的Message函数用来打印一个日志,跟踪各个函数的调用。

 

然后将下面的键值写入注册表里面:

HKEY_CLASSES_ROOT\CLSID\{5e9ddec7-5767-11cf-beab-00aa006c3606} = Simple Object Server

HKEY_CLASSES_ROOT\CLSID\{5e9ddec7-5767-11cf-beab-00aa006c3606}\LocalServer32 =c:\simple\sserver\Win32\Debug\sserver.exe

 

C++客户端

使用下面的C++ DCOM客户端程序来验证一下DCOM服务器:

1. #define INC_OLE2

2. #include <stdio.h>

3. #include <windows.h>

4. #include <initguid.h>

5. #include <tchar.h>

6. #include <conio.h>

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. void

13. 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. } // Message

41.

42. void

43. OutputTime(LARGE_INTEGER* pliStart, LARGE_INTEGER* pliFinish)

44. {

45.     LARGE_INTEGER liFreq;

46.

47.     QueryPerformanceFrequency(&liFreq);

48.     wprintf(TEXT("%0.4f seconds\n"),

49.         (float)(pliFinish->LowPart - pliStart->LowPart)/(float)liFreq.LowPart);

50. } // OutputTime

51.

52. void __cdecl

53. 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, argv[1], -1,

69.             wsz, sizeof(wsz)/sizeof(wsz[0]));

70.         memset(&csi, 0, sizeof(COSERVERINFO));

71.         csi.pwszName = wsz;

72.         pcsi = &csi;

73.         }

74.

75.     // 第二个参数是IStream读写的字节数目

76.     if (argc > 2)

77.         cb = atol(argv[2]);

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.     else

98.         {

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_ROOT\APPID\{5e9ddec7-5767-11cf-beab-00aa006c3606}\RemoteServerName=<机器名>

 

然后确保DCOM服务器端的机器的注册表里,下面的键值是“Y”:

HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\EnableRemoteConnect

 

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 DemoSimpleObjectLib

10. {

11.     importlib("stdole2.tlb");

12.

13.     [

14.         uuid(5e9ddec7-5767-11cf-beab-00aa006c3606),

15.         helpst


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C/C++中数组转换成指针的情况发布时间:2022-07-13
下一篇:
C++基于EasyX制作贪吃蛇游戏(五)第三版文档发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap