Introduction:
Every book, and every single tutorial, teaches you how to use the basic things
of COM with very good C samples. That's pretty good to begin with, but the problem
comes when you want to pass from your client to your COM server/control
something more than one numeric value, or one string, or... for
example a Metafile, or a Bitmap... then you start having problems
with the Variant type...
That was my nightmare during some days. I wanted to pass a metafile from
my MFC client to my ATL ActiveX control. Here I show you how I did
it (in the same process space and in different ones).
I have added some basic info about how to make both projects, if are used to it you can skip it,
if you need more info about them, you can check out some basic
tutorials that you can find in codeproject as well.
Create the ATL Control:
The ATL control that we are going to create is a full ActiveX control, with
one interface that will allow us assign one metafile to the it ( then that
metafile will be shown, in the space of the ActiveX control):
- Go to "File --> new"
- Select "ATL Com Appwizard"
- Click next...
- When the project is created, just go to the Workspace
docking and select the tab "Class View"
- Then go to the root of the control shown in that view,
show the context menu and select the option "New Atl Object"
- From the wizard select the type of control "Full
Control" ( and name the control "PicShower").
- Now go to the ClassView tab ( in the workSpace
docking), and select the interface IPicShower, press with the right button,
and select the option "Add Method", then add a method called
"SetDirectMeta" with the param "LPUNKNOWN Picture", and repeat the same
operation to add a new method, this time called "SetMeta", with the param "LPUNKNOWN Stream".
Ok, we will add now a member variable to our brand new object, that will hold the metafile,
so go to "PicShower.H", and add this variable:
CComQIPtr<IPICTURE> _Pict;
The "CComQIPtr" thing, it's one of the smart pointers that ATL offers to us. They
make it a lot easier to handle COM objects as it makes the code look like Visual
Basic (it wraps all the
QueryInterface
, AddRef,
Release
...).
And the
IPicture
? It�s the interface to a standard component that allows you to
display Metafiles, Bitmaps ( jpg, gif, tiff...). Cool isn�t it ?.
Now let's code a little. Go to the implementation of the method "SetDirectMeta
" (the wizard implemented an empty skeleton for us, in the file
"PicShower.cpp", the name of the method is CPicShower::SetDirectMeta(LPUNKNOWN
Picture)
and add this code:
STDMETHODIMP CPicShower::SetDirectMeta(LPUNKNOWN Picture)
{
_Pict = Picture;
FireViewChange();
return S_OK;
}
This method call will allow us to assign the metafile, but this only will work if
we use the component in the same space process ( you can not share a DC, between
different process spaces), so if you use this code in an EXE server, this method
won't work, or if for example you paste the ActiveX in an automated Word instance
and you
use it from your application.
To solve this we can use this other method: go to the implementation, of the method
"SetMeta", CPicShower::SetMeta(LPUNKNOWN
Picture)
and add this code:
STDMETHODIMP CPicShower::SetMeta(LPUNKNOWN Stream)
{
CComQIPtr<ISTREAM> pStream = Stream;
if(pStream) {
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;
}
FireViewChange();
}
return S_OK;
}
Here what we receive as a parameter is one stream, then we only have to load that
stream ( normally it will be in memory), and load the picture.
Let�s modify the drawing code, in order to check if the picture variable is not
empty and draw it then ( in the file "PicShower.H", the method HRESULT OnDraw(ATL_DRAWINFO& di)
),
we set it like this:
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;
}
In the Render we have to use negative value -(lPicHeight)
, because we
are working in Himetric coordinates.
Ok, now it seems that we have completed all the code for our ATL ActiveX
control, let�s go for the MFC part.
Create the MFC Client:
To use our control in our MFC client App, we can do it in
several ways, the two that I like best are:
- Use Smart pointers: quite powerful and easy to work with
smart pointers, it wraps all the QueryInterface/AddRef/Release for
you. You can find very good articles about that, for example you
can search this one in the MSDN "Calling COM Objects with Smart Interface
Pointers"
- Just import the class: This method is quite EASY, just go to the main
menu of Visual Studio, and select the option "Project >> Add to Project >>
Components and Controls" and from there check the folder "Registered
ActiveX Controls" ( in our case we would select then the PicShower class).
It creates you a wrapper class that inherits from CWnd, and all the Dispatch
methods are wrapped as well as normal methods that belongs to that class... that's
pretty cool to use it in one example, but in real life, is better not to use
it, because you will make all your call through the IDispatch interface, and
that�s quite slow, and not very professional ( we are programming with VC++ not
VB Script :-) ).
Well, in the sample I use the second method ( the EASY
one), just to make the test I think it's OK, but if you want to have it with
Smart Pointers, just write me a line and I will do that. I suposse that we
are all more or less used to MFC, so I won't explain too much how the client code
works ( again if you think it would be interesting to explain it more tell
me). The sample is a Dialog based application, that shows one ActiveX, and when
you press the "Show Metafile" button it pass a metafile ( that I generated with
a drawing tool, and then stored in the resources, load it from a file would be
possible too) to the ActiveX, and it is shown.
One thing, before try the MFC client, compile the ATL project ( then the ActiveX
DLL will be autoregistered and all will work fine).
About this article:
To write this article I used MSDN to search Info, and one very good article, called
"Using Picture Objects in ATL", found on vbpj April 1999.
A lot of people helped me in the message board, to make this, special thanks to:
Joao Vaz, Joaqu�n M L�pez Mu�oz, Mazdak, ...