zoukankan      html  css  js  c++  java
  • C++ DCOM服务器和C#客户端互操作完全解释

    今天有个网友问我如何编写一个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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    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行定义一个CSimpleObject的CLSID,58行的Message函数用来打印一个日志,跟踪各个函数的调用。

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

    双击代码全选
    1
    2
    HKEY_CLASSES_ROOTCLSID{5e9ddec7-5767-11cf-beab-00aa006c3606} = Simple Object Server
    HKEY_CLASSES_ROOTCLSID{5e9ddec7-5767-11cf-beab-00aa006c3606}LocalServer32 =c:simplesserverWin32Debugsserver.exe

      C++客户端

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

    双击代码全选
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    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 secondsn"),
    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_ROOTAPPID{5e9ddec7-5767-11cf-beab-00aa006c3606}RemoteServerName=<机器名>

      然后确保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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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.         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对象里有什么函数了。把这个文件保存为demosimpleobject.idl。

      下一步就是生成类型库文件,并生成Interop Assembly了,毕竟C#程序不理解类型库文件,需要Interop Assembly这个中介才能跟COM打交道。下面的步骤生成类型库和Interop Assembly:

      1.         打开Visual Studio 2008 Command Prompt窗口

      2.         执行下面的命令从IDL文件生成类型库文件:

      midl demosimpleobject.idl

      3.         执行下面的命令从类型库文件生成Interop Assembly:

      tlbimp demosimpleobject.tlb

      Interop Assembly生成好了以后,就可以在C#程序中引用了,下面是C#的客户端源代码(program.cs):

    双击代码全选
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5.
    6. using DemoSimpleObjectLib;
    7. using System.Runtime.InteropServices;
    8. using IStreamOfficial = System.Runtime.InteropServices.ComTypes.IStream;
    9.
    10. namespace CSharpClient
    11. {
    12.     class Program
    13.     {
    14.         static void Main(string[] args)
    15.         {
    16.             var stream = new SimpleObjectClassClass() as IStreamOfficial;
    17.             var cb = 4096;
    18.             var buffer = new byte[cb];
    19.             stream.Read(buffer, cb, IntPtr.Zero);
    20.         }
    21.     }
    22. }

      第6行将Interop Assembly里面的COM对象和接口定义引入进来,第16行连接到DCOM服务器并创建一个DCOM对象,最后查询对象的IStream指针。我在第8行里将IStream重命名为IStreamOfficial,因为在Interop Assembly里也会生成IStream的C#定义,但是那个定义不对。第17行到19行就是正常地通过IStream来操作DCOM对象了。第20行,程序退出的时候,CLR会自动释放掉DCOM对象的引用计数。

      编译命令:

      Csc /debug:full /r:DemoSimpleObjectLib.dll program.cs

      运行

      一切都做好了以后,要测试的话,请按照下面的步骤来操作:

      1.         在一个命令行窗口中启动sserver.exe。

      2.         然后启动C#客户端,在调试器中运行到第20行的时候,你会看到buffer的内容都是0xFF。

    本文示例代码或素材下载

  • 相关阅读:
    Oracle 函数
    ecplise 修改编码
    mysql json 使用 类型 查询 函数
    java 子类强转父类 父类强转子类
    java UUID
    Java Number & Math 类
    java String类
    apache StringUtils 工具类
    apache ArrayUtils 工具类
    java Properties
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2453232.html
Copyright © 2011-2022 走看看