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






4.60/5 (5投票s)
2002 年 3 月 13 日
5分钟阅读

186560

1795
如何直接或通过流将图元文件传递给 ATL 服务器
引言
每一本书、每一个教程都教你如何使用 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、...