龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > C/C++开发 >

在客户端重新创建对象

时间:2009-12-22 15:42来源:未知 作者:admin 点击:
分享到:
至今我们还没有涉及到客户部分的操作,现在就讨论一下。客户端通过调用服务器端的GetArray()方法来开始整个处理。客户端将会接收我们在服务器创建的安全对象。客户端的程序则负责

  至今我们还没有涉及到客户部分的操作,现在就讨论一下。客户端通过调用服务器端的GetArray()方法来开始整个处理。客户端将会接收我们在服务器创建的安全对象。客户端的程序则负责将这块字节流变成为一个有效的C++对象。以下摘录了做这部分工作的客户端代码:

  

  // create COM smart pointer from CLSID string

  

  IBlobDataPtr pI( "TestServer.BlobData.1" );

  

  SAFEARRAY *psa ;

  

  // Get the safearray from the server

  

  pI->GetArray( &psa );

  

  file:// create a pointer to an object

  

  CSimpleObj *dummy=NULL;

  

  file:// blob object to eXPand

  

  CBlob blob;

  

  file:// use the blob to expand the safearray into an object

  

  blob.Expand( (CObject*&)dummy, psa );

  

  file:// call a method on the object to test it

  

  dummy->Show(this->m_hWnd, "Client Message");

  

  // delete the object

  

  delete dummy;

  

  这段代码挺简单的。它使用一个COM智能指针与服务器进行连接。假如你对智能指针不熟悉,这些方便的小对象通过调用CoCreateInstance()就可做所有的工作,与服务器进行连接。

  

  CBlob类再一次完成这个步骤,这个类与服务器端的那一个是完全相同的。该类代码事实上是由服务器工程中得到的。将一个安全数组变成一个CSimpleObject的工作由Expand()方法完成。该方法与服务器端调用的Load()方法是相对的。

  

  // Re-create an object from a SAFEARRAY

  

  BOOL CBlob::Expand(CObject * &rpObj, SAFEARRAY *psa)

  

   {

  

  CMemFile memfile; // memory file for de-serialize

  

  long lLength; // number of bytes

  

  char *pBuffer; // buffer pointer

  

  file:// lock Access to array data

  

  SafeArrayAccessData( psa, (void**)&pBuffer );

  

  // get number of elements in array. This is the number of bytes

  

  lLength = psa->rgsabound->cElements;

  

  // attach the buffer to the memory file

  

  memfile.Attach((unsigned char*)pBuffer, lLength);

  

  file:// start at beginning of buffer

  

  memfile.SeekToBegin();

  

  file:// create an archive with the attached memory file

  

  CArchive ar(&memfile, CArchive::loadCArchive::bNoFlushOnDelete);

  

  // document pointer is not used

  

  ar.m_pDocument = NULL;

  

  file:// inflate the object and get the pointer

  

  rpObj = ar.ReadObject(0);

  

  // close the archive

  

  ar.Close();

  

  file:// Note: pBuffer is freed when the SAFEARRAY is destroyed

  

  file:// Detach the buffer and close the file

  

  pBuffer = (char*) memfile.Detach();

  

  file:// release the safearray buffer

  

  SafeArrayUnaccessData( psa );

  

  return TRUE;

  

  }

  

  这个方法的大部分代码你也在前面见过了,该方法接收一个安全数组的输入。我们由安全数组中取出缓冲的数据。CArchive对象负责将缓冲的数据重新创建为对象。

  

  以下就是我们在刚才的Expand()方法中完成的事情

  

   1、锁定访问安全数组结构,并且得到它的长度(以字节计)。

  

   2、将安全数组的数据缓冲与一个CMemFile关联

  

   3、将CMemFile与一个archive相关联

  

   4、使用Archives的ReadObject()方法重新创建对象

  

   5、将SAFEARRAY的数据缓冲由CMemFile脱离

  

   6、返回一个指向新创建对象的指针给调用者

  

  我们在这里谈到了许多基层的东西。CBlob类负责串行化和还原对象的繁重工作。COM部分的程序确实是很易懂的。

  

  性能问题

  

  我并不是劝说你不要使用这里提出的技巧,不过在性能方面,我要提醒你一下。以上我们提到的东西,对于小和中等大小的对象,可工作得很好的。不过,大型的对象将会有问题,我指的是超过一兆的大型对象。

  

  问题是,这些大型对象必须放在内存中,它可令系统开始使用分页。你可能也知道,分页的效率是很低的,非凡是对于内存少的机器。

  

  这些问题产生的原因是几个这样大的对象都必须同时放在内存中。假如客户和服务器应用都运行在同一部机器(或者一个进程中),那么在内存中同时可有高达三个这些对象的拷贝。这样系统开销就会很大,但这又是不可避免的。

  

  对于由许多小对象组成的大对象,在串行化处理方面可以作一些调整。你可以调用自己的CArchive,并且用更大的哈希表调用SetStoreParams和SetLoadParams。CArchive使用一个哈希表来存储类的信息。假如哈希表填满了,那么串行化的处理将会变得很慢。默认的大小适合用在1000或者更少的对象,要了解如何做到这一点,可看一下sample目录的CBigArchive类。你可以使用这个类来代替CArchive。在这个工程的CBigArchive类中,我也包含了它的一个例子(在我的例子中并没有使用CBigArchive)。

  

  安全数组外的选择

  

  安全数组并不是传送二进行数据的唯一方法。我还简要提一下其它三个办法。我可以肯定还有其它的方式。

  

  1、使用一个VARIANT

  

  调用VariantInit API函数可创建一个VARIANT数据。该函数初始化VARIANT的数据结构。你还需要设置variant的类型,这可以通过设置它的"vt"成员做到。以下的代码片段创建一个variant并且将它放到一个安全数组中。

  

  VARIANT *pVar

  

  // initialize the variant

  

  VariantInit( pVar );

  

  file:// set the variant type to an array of bytes

  

  pVar->vt = VT_ARRAY + VT_UI1;

  

  file:// create the safe array pointer

  

  SAFEARRAY *psa;

  

  psa = SafeArrayCreateVector( VT_UI1, 0, llen );

  

  file://Code to load the SAFEARRAY with data

  

   ...

  

  file:// assign the SAFEARRAY pointer to the VARIANT

  

  pVar->parray = psa;

  

  2、使用MIDL提供的非自动类型

  

  我们使用SAFEARRAY和VARIANT的原因是可以利用MIDL产生的类库。该类库可以简化调用数据的操作,还提供COM的智能指针。使用一个类库并不是必须的,但是不这样做的话,你需要做一些额外的工作。

  

  MIDL答应一些"custom"(自定义)的数据类型。这些数据类型并不兼容类库和"automation"的接口。一个二进制对象的MIDL接口可以使用一个byte *,而不是一个安全数组。该接口规范需要包含一些关于调用的明确信息,这些信息在[IN]和[OUT]属性中定义。

  

  在TesServer.IDL文件中,我提到了一个关于如何定义MIDL接口的例子。该串行化的处理与这篇文章谈到的是一样的,不过缓冲分配有点不同。以下是IDL的定义。

  

  file:// get data from server

  

  HRESULT GetData([out] long* pcLen,[out,size_is(,*pcLen)] byte **pBuffer );

  

  file:// send data to server

  

  HRESULT SetData([in] long cLen,[in,unique,size_is(cLen)] byte Buffer[]);

  

  要使用这些接口你将必须做一些调用。"size_is"属性告诉MIDL如何调用数据。(IDL属性定义的信息与安全数组的成员变量存储的信息是一样的)。调用通过一个由MIDL产生的Proxy/Stub DLL完成。Proxy/Stub DLL需要建立,并且在客户和服务器端使用。

  

  3、使用IStream接口

  

   一个完全不同的方法是使用标准的IStream接口。我没有研究过,不过一些编程者坚持说这是唯一的方法。我觉得安全数组也不错,因此没有尝试过,这可能会在未来的文章中提到。

  

精彩图集

赞助商链接