WEB开发网
开发学院软件开发C++ 在客户端重新创建对象 阅读

在客户端重新创建对象

 2008-03-08 12:41:03 来源:WEB开发网   
核心提示:至今我们还没有涉及到客户部分的操作,现在就讨论一下,在客户端重新创建对象,客户端通过调用服务器端的GetArray()方法来开始整个处理,客户端将会接收我们在服务器创建的安全对象,我觉得安全数组也不错,因此没有尝试过,客户端的程序则负责将这块字节流变成为一个有效的C++对象,以下摘录了做这部分工作的客户端代码: //

  至今我们还没有涉及到客户部分的操作,现在就讨论一下。客户端通过调用服务器端的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接口。我没有研究过,不过一些编程者坚持说这是唯一的方法。我觉得安全数组也不错,因此没有尝试过,这可能会在未来的文章中提到。

Tags:客户端 重新 创建

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接