65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将图片从 MFC 客户端传输到 ATL ActiveX 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (5投票s)

2002 年 3 月 13 日

5分钟阅读

viewsIcon

186560

downloadIcon

1795

如何直接或通过流将图元文件传递给 ATL 服务器

Sample Image

引言

每一本书、每一个教程都教你如何使用 COM 的基本知识,并且提供了非常好的 C 语言示例。这对于入门来说非常好,但问题在于当你想要将一个数字值、一个字符串或… 例如一个图元文件或一个位图… 从客户端传递到你的 COM 服务器/控件时,你就会在 Variant 类型上遇到问题…

这曾是我几天的噩梦。我想将一个图元文件从我的 MFC 客户端传递到我的 ATL ActiveX 控件。在这里,我将向你展示我是如何做到的(在同一进程空间内和不同进程空间内)。

我添加了一些关于如何创建这两个项目的基本信息,如果你熟悉的话可以跳过,如果你需要更多关于它们的信息,你也可以在 codeproject 上找到一些基本教程。

创建 ATL 控件

我们将要创建的 ATL 控件是一个完整的 ActiveX 控件,它有一个接口,允许我们为其分配一个图元文件(然后该图元文件将在 ActiveX 控件的区域内显示)。

  • 转到“文件 -> 新建”
  • 选择“ATL Com Appwizard”
  • 单击下一步…
  • 项目创建后,只需转到工作区停靠窗口并选择“类视图”选项卡。
  • 然后转到该视图中显示的控件的根目录,显示上下文菜单并选择“新建 Atl 对象”选项。
  • 从向导中选择“完整控件”类型的控件(并将控件命名为“PicShower”)。
  • 现在转到工作区停靠窗口中的类视图选项卡,选择 IPicShower 接口,按右键,然后选择“添加方法”选项,然后添加一个名为“SetDirectMeta”的方法,参数为“LPUNKNOWN Picture”,并重复相同的操作添加一个新方法,这次名为“SetMeta”,参数为“LPUNKNOWN Stream”。

好的,现在我们将为我们崭新的对象添加一个成员变量,它将存储图元文件,所以转到“PicShower.H”,并添加这个变量。

// Better to use smart pointers, just forget about AddRef, Release, QInterface...
CComQIPtr<IPICTURE>  _Pict;
“CComQIPtr”是 ATL 提供给我们的智能指针之一。它们使得处理 COM 对象更加容易,因为它们使得代码看起来像 Visual Basic(它封装了所有的 `QueryInterface`、AddRef、`Release`…)。而 `IPicture`?它是允许你显示图元文件、位图(jpg、gif、tiff…)的标准组件的接口。是不是很酷?

现在让我们编写一些代码。转到“`SetDirectMeta`”方法的实现(向导为我们实现了一个空的框架,在“PicShower.cpp”文件中,方法的名称是 `CPicShower::SetDirectMeta(LPUNKNOWN Picture)`),并添加以下代码。

/* -------------------------------------------------------------------------
      This only works, if it´s in the same space process
   ------------------------------------------------------------------------- */

STDMETHODIMP CPicShower::SetDirectMeta(LPUNKNOWN Picture)
{
   _Pict = Picture;        // Assign the picture
   FireViewChange();    // Force to redraw the ActiveX

   return S_OK;    
}

此方法调用将允许我们分配图元文件,但这仅在我们使用同一进程空间中的组件时才有效(你不能在不同进程空间之间共享 DC),所以如果你在 EXE 服务器中使用此代码,此方法将不起作用,或者如果你将 ActiveX 粘贴到自动化的 Word 实例中并从你的应用程序中使用它。

为了解决这个问题,我们可以使用这个方法:转到方法“SetMeta”的实现,`CPicShower::SetMeta(LPUNKNOWN Picture)`,并添加此代码。

/* -------------------------------------------------------------------------
      Ok, this method works in all the places ( in proccess, out of
   process, ...). The Picture is saved in memory in one stream, then 
   we open that stream and load the picture
   ------------------------------------------------------------------------- */

STDMETHODIMP CPicShower::SetMeta(LPUNKNOWN Stream)
{
   CComQIPtr<ISTREAM> pStream = Stream;
   if(pStream) {
      // Using one smart pointer to get the Picture Dispatch
      CComPtr<IPICTUREDISP> pic;    
      LARGE_INTEGER l;
      l.QuadPart  = 0;
      pStream->Seek(l, STREAM_SEEK_SET, NULL);                                 
   
      OleLoadPicture(pStream, l.LowPart, FALSE, IID_IPictureDisp, 
                     (void **) &pic);

      if(pic) {
         _Pict = pic;              // Ok, QInterface smart pointer...
      }
      
      FireViewChange();            // Force to redraw
   }
   return S_OK;
}

在这里,我们收到的参数是一个流,然后我们只需加载该流(通常它会在内存中),然后加载图片。

让我们修改绘图代码,以便检查图片变量是否不为空,然后进行绘制(在“PicShower.H”文件中,方法 `HRESULT OnDraw(ATL_DRAWINFO& di)`),我们将其设置为如下:

    HRESULT OnDraw(ATL_DRAWINFO& di)
    {
      RECT& rc = *(RECT*)di.prcBounds; 
      if(_Pict) {             
         RECT r = rc;
         long lPicWidth  = 0;
         long lPicHeight = 0;                  
         if(_Pict){
            _Pict->get_Width(&lPicWidth);_Pict->get_Height(&lPicHeight);
            HRESULT hres = _Pict->Render(di.hdcDraw, 0, 0, rc.right, rc.bottom, 
                                      0, lPicHeight, lPicWidth, -(lPicHeight), &r);
         }
      } else {           
         Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);

         SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
         LPCTSTR pszText = _T("PicShower: No picture assigned");
         TextOut(di.hdcDraw, 
               (rc.left + rc.right) / 2, 
               (rc.top + rc.bottom) / 2, 
               pszText, 
               lstrlen(pszText));
      }
      return S_OK;
    }

在 Render 中,我们必须使用负值 `-(lPicHeight)`,因为我们正在使用 Himetric 坐标。

好的,现在似乎我们已经完成了 ATL ActiveX 控件的所有代码,让我们进入 MFC 部分。

创建 MFC 客户端

要在我们的 MFC 客户端应用程序中使用我们的控件,我们可以通过几种方式实现,我最喜欢的两种方式是:

  • 使用智能指针:非常强大,易于使用智能指针,它为你封装了所有 QueryInterface/AddRef/Release。你可以找到关于它的非常好的文章,例如,你可以在 MSDN 中搜索这篇《Calling COM Objects with Smart Interface Pointers》。
  • 仅导入类:这种方法非常简单,只需转到 Visual Studio 的主菜单,然后选择“项目 >> 添加到项目 >> 组件和控件”选项,然后从中检查“已注册的 ActiveX 控件”文件夹(在我们的情况下,我们将选择 PicShower 类)。它会为你创建一个继承自 CWnd 的包装类,并且所有的 Dispatch 方法以及属于该类的普通方法都被包装好了……这对于在示例中使用来说非常棒,但在实际生活中,最好不要使用它,因为你将通过 IDispatch 接口进行所有调用,这非常慢,而且不专业(我们用 VC++ 编程,而不是 VB 脚本 :-))。

好吧,在示例中,我使用了第二种方法(简单的那种),只是为了进行测试,我认为这没问题,但如果你想使用智能指针,请给我写一封邮件,我会帮你解决。我猜我们大多数人都熟悉 MFC,所以我就不详细解释客户端代码是如何工作的了(同样,如果你认为详细解释它会很有趣,请告诉我)。该示例是一个基于对话框的应用程序,它显示一个 ActiveX,当你按下“显示图元文件”按钮时,它会将一个图元文件(我用绘图工具生成,然后存储在资源中,也可以从文件中加载)传递给 ActiveX,然后显示它。

还有一件事,在尝试 MFC 客户端之前,请编译 ATL 项目(然后 ActiveX DLL 将被自动注册,所有事情都会正常工作)。

关于本文

撰写这篇文章时,我使用了 MSDN 来搜索信息,以及一篇非常好的文章,名为“Using Picture Objects in ATL”,发表在 1999 年 4 月的 vbpj 上。

很多人在论坛上帮助了我,使这篇文章得以完成,特别感谢:Joao Vaz、Joaquín M López Muñoz、Mazdak、...

© . All rights reserved.