You are on page 1of 14

at one point in time, before com, before atl, programmers used ordinary .dlls instead.

you
could do a lot with a .dll. if you had several programs that used the same functions or
other resources, you could save space by putting those resources in a .dll. putting code
used by multiple programs in a single .dll often saved maintenance time because the code
was all in one place. fixes and other modifications would only have to be done one time.
if you had a program which needed to run different routines at different times, you could
put those routines into .dlls and have the application load the appropriate .dll when it was
needed. there were lots of good reasons to use .dlls.
(continued)

in addition to the .dll and .lib files, your client application needs a header file for the
imported classes, functions, objects and variables. when we were exporting, we added
"__declspec(dllexport)" to our declarations. now when we are importing, we will add
"__declspec(dllimport)." so if we wanted to import the variable, object and function used
in our previous examples, our header file would contain the following:
__declspec(dllimport) int somefunction(int);
__declspec(dllexport) csomeclass someobject;
__declspec(dllexport) int someint;
remember, if you used the extern "c" specifier in the .dll, you must also use it in the
client application:
extern "c" __declspec(dllimport) int somefunction(int);
to make things more readable, we might write it like this instead:
#define dllimport __declspec(dllimport)

dllimport int somefunction(int);


dllimport csomeclass someobject;
dllimport int someint;
now that you have declared your object, variable and function in a header file inside your
client application, they are available for use.
to import an entire class, you must copy the entire .h header file into the client
application. the .dll and the client application will thus have identical header files for the
exported class, except one will say "class __declspec(dllexport) cmyclass" and one will
say "class __declspec(dllimport) cmyclass". if you are making an mfc extension .dll, you
could instead say "class afx_ext_class cmyclass" in both places.
once you are done building your client application and you're ready to turn it over to the
actual users, you should give them your release executable and the release .dll. you do not
need to give the users the .lib file. the .dll can go in the same directory as the executable,
or it can go in the windows system directory. as discussed above, you may also have to
provide your users with the correct mfc code library .dll. this .dll was loaded onto your
computer when you installed visual c++. your users, however, may not have it. it does not
come standard with windows.
a word of caution
this article should provide you with enough information to start building your own .dlls. a
word of caution is needed, however. as mentioned at the beginning of this article, there
are several serious shortcomings to .dlls. these shortcomings are the reason that we now
have com and atl. there are two main problems. first, a .dll built with one brand of
compiler may not be compatible with a client application built with a different compiler.
second, when you modify the .dll, you may have to recompile the client application, even
though you aren't changing any code in the client application. you may still have to copy
in a new .dll and .lib file and recompile.
there are ways to avoid this problem under some circumstances. i'll discuss the problem
in more detail in the next article.

as i discussed in the last article, .dlls are a useful tool for any mfc programmer. they are
subject to a number of important limitations, however, and anyone who is making .dlls
should be aware of these.
(continued)

mfc issues
this was discussed in the last article, but is worth mentioning again briefly. an mfc
extension .dll can only be used if the client application dynamically links to the same
version of mfc, and the correct mfc code library .dll is available on the client computer. a
regular .dll which dynamically links to mfc will only work if the correct mfc code library
.dll is available.
compiler incompatibility issues
one of the biggest problems with c++ based .dlls arises when a .dll is built on one brand
of compiler and called by an application built on another brand of compiler. often it won't
work without a great deal of effort.
ansi sets the standards for the c and c++ languages. that is, it specifies the c and c++
functions and data types which should be supported by a compiler. it does not, however,
provide a complete standard as to how these functions and data types should be
implemented on a binary level. as a result, compiler vendors are free to implement
language features in their own proprietary ways.
most c / c++ programmers know that different compilers handle data types differently.
one compiler will allocate 2 bytes for an int and another will allocate 4 bytes. one will use
4 byte doubles and another will use 8 bytes. an even bigger difference with c++ compilers
arises from their implementation of function and operator overloading. the differences
between compilers go far beyond this, however. the same c or c++ code may be compiled
very differently by different compilers. these differences may keep your .dll from running
with someone else's application.
of course, if you are building an mfc extension .dll, this is not an issue for you. mfc
extension .dlls are made with a microsoft compiler. as discussed in the previous article,
they can only be used by applications that are dynamically linked to mfc. these
applications are also made with a microsoft compiler.
compiler incompatibility problems can be fixed by inserting pragmas and other
precompile instructions into your code, but this is hard to do and unreliable. there will
always be the chance that someone else is using a compiler that's still incompatible.
recompiling
let's say you built a .dll that exports a class called cmyclass. you provide a copy of the
header file for cmyclass to be used by the client application. suppose that a cmyclass
object is 30 bytes in size.
now let's suppose you modify the .dll to change cmyclass. it still has the same public
functions and member variables, but now cmyclass has an additional private member
variable, an int. so now, when you create an object of type cmyclass, it's 34 bytes in size.
you send this new .dll to your users and tell them to replace the old .dll. now you have a
problem. the client application is expecting a 30 byte object, but your new .dll is creating
a 34 byte object. the client application is going to get an error.
here's a similar problem. suppose that instead of exporting cmyclass your .dll exports
several functions which use cmyclass references or pointers in their parameters or return
values. you have provided a cmyclass header file which now resides in the client
application. again, if you change the size of the cmyclass object without rebuilding the
client application, you will have problems.
at this point, the only way to fix the problem is to replace the cmyclass header file in the
client application and recompile it. once recompiled, the client application will start
looking for a 34 byte object.
this is a serious problem. one of the goals of having a .dll is to be able to modify and
replace the .dll without modifying the client application. however, if your .dll is exporting
classes or class objects, this may not be possible. you may have to recompile the client
application. if you don't have the source code for the client application, you simply can't
use the new .dll.
solutions
if there were a perfect fix for these problems, we might not have com. here are a few
suggestions:
mfc extension .dlls don't have compiler incompatibility problems. they simply can't be
used by applications built on non-microsoft compilers. as for regular .dlls, you can avoid
many compiler problems by only exporting c-style functions and using the extern "c"
specifier. exporting c++ classes and overloaded c++ functions leaves you much more
vulnerable to compiler incompatibility.
as for having to recompile your client application when you modify the .dll, there are
relatively easy ways to avoid the problem. i will describe two of them: (1) an interface
class and (2) static functions used to create and destroy your exported class.
using an interface class
the goal of an interface class is to separate the class you want to export and the interface
to that class. the way to do this is to create a second class which will serve as the interface
for the class you want to export. then, even when the export class changes, you will not
need to recompile the client application, because the interface class remains the same.
here's an example of how that would work. suppose you want to export cmyclass.
cmyclass has two public functions, int functiona(int) and int functionb(int). if i simply
export cmyclass, i'll have to recompile the client application every time i add a new
variable. instead, i'll create and export an interface class, cmyinterface. cmyinterface will
have a pointer to a cmyclass object. here's the header file for cmyinterface as it looks
inside the .dll:
#include "myclass.h"

class __declspec(dllexport) cmyinterface


{
//private pointer to cmyclass object
cmyclass *m_pmyclass;

cmyinterface( );
~cmyinterface( );

public:
int functiona(int);
int functionb(int);
};
inside the client application, the header file will look slightly different. the #include will
be gone. after all, you can't include myclass.h, because the client application doesn't have
a copy of it. instead, you will use a forward declaration of cmyclass. this will allow you
to compile even without the cmyclass header file:
class __declspec(dllimport) cmyinterface
{
//forward declaration of cmyclass
class cmyclass;

cmyclass *m_pmyclass;

cmyinterface( );
~cmyinterface( );

public:
int functiona(int);
int functionb(int);
};
inside the .dll, you implement cmyinterface as follows:
cmyinterface::cmyinterface( )
{
m_pmyclass = new cmyclass;
}

cmyinterface::~cmyinterface( )
{
delete m_pmyclass;
}

int cmyinterface::functiona( )
{
return m_pmyclass->functiona( );
}

int cmyinterface::functionb( )
{
return m_pmyclass->functionb( );
}
thus, for every public function in cmyclass, cmyinterface will provide its own
corresponding function. the client application has no contact with cmyclass. if it wants to
call cmyclass::functiona, it instead calls cmyinterface::functiona. the interface class then
uses its pointer to call cmyclass. with this arrangement, you're free to modify with
cmyclass. it doesn't matter if the size of the cmyclass object changes. the size of the
cmyinterface object will remain the same. if you add a private member variable to
cmyclass, the size of cmyinterface will remain unchanged. if you add a public member
variable to cmyclass, you can add "getter" and "setter" functions for the new variable in
cmyinterface without fear. adding new functions to cmyinterface will not cause recompile
problems.
creating a separate interface class avoids some compiler incompatibility problems and
most recompile problems. as long as the interface class doesn't change, there should be
no need to recompile. there are still two relatively minor problems with this solution.
first, for every public function and member variable in cmyclass, you must create a
corresponding function or variable in cmyinterface. in the example there are only two
functions, so that's easy. if cmyclass had hundreds of functions and variables, this would
be a more tedious, error-prone process. second, you are increasing the amount of
processing that must be done. the client application no longer calls cmyclass directly.
instead, it calls a cmyinterface function which calls cmyclass. if this is a function that will
be called thousands of times by the client application, the extra processing time may
begin to add up.
static functions
a different way to avoid having to recompile uses static functions to create and destroy
the exported class. this solution was sent to me by ran wainstain in a comment to my
previous article.
when you are creating a class which you intend to export, you add two public, static
functions, createme( ) and destroyme( ):
class __declspec(dllexport) cmyclass
{
cmyclass( );
~cmyclass( );

public:
static cmyclass* createme( );
static void destroyme(cmyclass *ptr);
}
createme( ) and destroyme( ) are implemented as follows:
cmyclass* cmyclass::createme( )
{
return new cmyclass;
}

void cmyclass::destroyme(cmyclass *ptr)


{
delete ptr;
}
you export cmyclass as you would any other class. in the client application, you must be
sure to use the createme( ) and destroyme( ) functions. when you want to create a
cmyclass object, you don't declare it in the usual fashion, i.e.:
cmyclass x;
instead, you do this:
cmyclass *ptr = cmyclass::createme( );
when you are done, you must remember to delete the object:
cmyclass::deleteme(ptr);
using this technique, you can modify the size of cmyclass without having to recompile
the client application.
conclusion
this article is not a complete review of every issue concerning .dlls, nor does it cover
every possible solution. good discussions of these issues can be found in inside com by
dale rogerson and essential com by don box. for a more detailed understanding of the
issue, that's where i would go. this article should at least serve to make you aware of the
biggest issues and possible fixes and get you started on your way.
in part 1 of this series, i covered what types of .dlls you can make with mfc and how to
build mfc regular and extension .dlls. in part 2, i covered potential problems that can arise
when you use .dlls and suggested several ways to avoid them. in this article, i'll build
upon the first two articles by providing more coding examples and technical details.
(continued)

the code of the ontimer function is rather straightforward. every time a timer is called in
this class, it will run through this function. therefore, the value passed through the
function automatically houses an unsigned integer corresponding to the timer you create
manually. using a select statement, i created the case for the timer you will render with.
the basic "draw then swap buffers" routine is now placed inside this timer. in the first
steps of this tutorial, though, you won't actually draw images to the screen, but only
correctly swap the buffers to the clear color.
openglcontrol.cpp:
void copenglcontrol::ontimer(uint nidevent)
{
switch (nidevent)
{
case 1:
{
// clear color and depth buffer bits
glclear(gl_color_buffer_bit | gl_depth_buffer_bit);

// draw opengl scene


// ogldrawscene();

// swap buffers
swapbuffers(hdc);

break;
}

default:
break;
}

cwnd::ontimer(nidevent);
}
step 12: adding the onsize function
the last message function you need to add will be the onsize function. like you've done
before, select the wm_size message and select <add> onsize from the dropdown to
create the function.
the onsize function is called when the window is resized. to prevent the opengl rendering
from getting all wacky from resizing the window, a few things such as the perspective
and viewport need to be tweaked. in the tutorial, you won't actually resize the control
according to the window, but that could be achieved in this function if the need arises.
openglcontrol.cpp:
void copenglcontrol::onsize(uint ntype, int cx, int cy)
{
cwnd::onsize(ntype, cx, cy);

if (0 >= cx || 0 >= cy || ntype == size_minimized) return;

// map the opengl coordinates.


glviewport(0, 0, cx, cy);

// projection view
glmatrixmode(gl_projection);

glloadidentity();

// set our current view perspective


gluperspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f);

// model view
glmatrixmode(gl_modelview);
}
you are now done with the copenglcontrol class (the initial setup at least). so, the next
step is to integrate the class into your main mfc window, the coglmfcdialogdlg class.
step 13: customizing the main mfc dialog class
your last step is to integrate your opengl class into the main mfc dialog. this is done rather
painlessly. first, inside the oglmfcdialogdlg.h file you will need to #include the created
opengl class header file and also create an instance of the copenglcontrol class as a local
variable to this class.
oglmfcdialogdlg.h:
#include "openglcontrol.h"

class coglmfcdialogdlg : public cdialog


{
private:
copenglcontrol m_oglwindow;
.
.
.
next, inside of the oglmfcdialogdlg.cpp file, add to the already existing oninitdialog
function before the return statement the following code to create your opengl class inside
the picture control you created and start up the timer to render the scene.
note: for initial test purposes, i set the timer to 1 millisecond to call your timer, or
a non-clamp.
oglmfcdialogdlg.cpp::oninitdialog:
.
.
.
crect rect;

// get size and position of the picture control


getdlgitem(idc_opengl)->getwindowrect(rect);

// convert screen coordinates to client coordinates


screentoclient(rect);

// create opengl control window


m_oglwindow.oglcreate(rect, this);

// setup the opengl window's timer to render


m_oglwindow.m_unptimer = m_oglwindow.settimer(1, 1, 0);
.
.
.
step 14: initial opengl control results
and there you have it. now you should have the black clear color constantly being
swapped through the buffers! now, from here you can add a drawing function to do all
your rendering work and place that in the ontimer function where the commented line
suggests.
(full size image)

part ii: extending the opengl functionality (extras)


step 15: drawing geometrical shapes
the next step would be to start drawing some test shapes in your opengl control.
unfortunately, you won't be able to actually completely see them yet until you set up
some kind of camera system. without a camera pointing us to a certain point to look at the
shapes you draw, the shapes created will just be placed on the origin of the camera and
thus won't be viewable. so, if you want to see what you draw first instead, skip down to
step 16, setting up a maya-style camera, in which you will create an alias|wavefront
maya-style camera (rotate/zoom/translate with mouse buttons). but, i chose to start
drawing and setting the camera up afterwards instead for this tutorial.
anyway, drawing the shapes on this tutorial will be very simple; just create a standard
cube by drawing the six sides with quads (4-vertex shapes). if you recall in the ontimer
function, there's a commented-out call to a non-existant ogldrawscene function; you now
will create this function.
openglcontrol.h:
void ogldrawscene(void);
openglcontrol.cpp:
void copenglcontrol::ogldrawscene(void)
{
// wireframe mode
glpolygonmode(gl_front_and_back, gl_line);

glbegin(gl_quads);
// top side
glvertex3f( 1.0f, 1.0f, 1.0f);
glvertex3f( 1.0f, 1.0f, -1.0f);
glvertex3f(-1.0f, 1.0f, -1.0f);
glvertex3f(-1.0f, 1.0f, 1.0f);

// bottom side
glvertex3f(-1.0f, -1.0f, -1.0f);
glvertex3f( 1.0f, -1.0f, -1.0f);
glvertex3f( 1.0f, -1.0f, 1.0f);
glvertex3f(-1.0f, -1.0f, 1.0f);

// front side
glvertex3f( 1.0f, 1.0f, 1.0f);
glvertex3f(-1.0f, 1.0f, 1.0f);
glvertex3f(-1.0f, -1.0f, 1.0f);
glvertex3f( 1.0f, -1.0f, 1.0f);

// back side
glvertex3f(-1.0f, -1.0f, -1.0f);
glvertex3f(-1.0f, 1.0f, -1.0f);
glvertex3f( 1.0f, 1.0f, -1.0f);
glvertex3f( 1.0f, -1.0f, -1.0f);

// left side
glvertex3f(-1.0f, -1.0f, -1.0f);
glvertex3f(-1.0f, -1.0f, 1.0f);
glvertex3f(-1.0f, 1.0f, 1.0f);
glvertex3f(-1.0f, 1.0f, -1.0f);

// right side
glvertex3f( 1.0f, 1.0f, 1.0f);
glvertex3f( 1.0f, -1.0f, 1.0f);
glvertex3f( 1.0f, -1.0f, -1.0f);
glvertex3f( 1.0f, 1.0f, -1.0f);
glend();
}
now, one last thing you'll need to do is uncomment the commented-out call to the
ogldrawscene function in the ontimer function.
openglcontrol.cpp::ontimer:
.
.
.
// clear color and depth buffer bits
glclear(gl_color_buffer_bit | gl_depth_buffer_bit);

// draw opengl scene


ogldrawscene();

// swap buffers
swapbuffers(hdc);
.
.
.
step 16: setting up a maya-style camera
the maya-style camera refers to the camera system used in alias|wavefront maya in
which the mouse button->drag performs a certain operation: left: rotate, right: zoom, and
middle: translate. to do this, you'll need to make an onmousemove function call:
select the wm_mousemove message and select from the dropdown, <add>
onmousemove to create the function. inside this newly created function, you'll need to
add the code to perform each of the buttons' given operations. if you recall from early on
in the tutorial, you created some member variables for rotate, zoom, and position. you'll
use these variables and set their new values according to the position variables passed
into the function by windows.
openglcontrol.h:
afx_msg void onmousemove(uint nflags, cpoint point);
openglcontrol.cpp:
void copenglcontrol::onmousemove(uint nflags, cpoint point)
{
int diffx = (int)(point.x - m_flastx);
int diffy = (int)(point.y - m_flasty);
m_flastx = (float)point.x;
m_flasty = (float)point.y;

// left mouse button


if (nflags & mk_lbutton)
{
m_frotx += (float)0.5f * diffy;

if ((m_frotx > 360.0f) || (m_frotx < -360.0f))


{
m_frotx = 0.0f;
}

m_froty += (float)0.5f * diffx;

if ((m_froty > 360.0f) || (m_froty < -360.0f))


{
m_froty = 0.0f;
}
}

// right mouse button


else if (nflags & mk_rbutton)
{
m_fzoom -= (float)0.1f * diffy;
}

// middle mouse button


else if (nflags & mk_mbutton)
{
m_fposx += (float)0.05f * diffx;
m_fposy -= (float)0.05f * diffy;
}
ondraw(null);

cwnd::onmousemove(nflags, point);
}
furthermore, i have put a few initial variable settings into the constructor of the
copenglcontrol class, which help the camera work properly.
openglcontrol.cpp:
copenglcontrol::copenglcontrol(void)
{
m_fposx = 0.0f; // x position of model in camera view
m_fposy = 0.0f; // y position of model in camera view
m_fzoom = 10.0f; // zoom on model in camera view
m_frotx = 0.0f; // rotation on model in camera view
m_froty = 0.0f; // rotation on model in camera view
}
now, the last step to do is, if you recall from when you first created the ondraw function,
to change the commented todo of the camera controls to the following:
openglcontrol.cpp::ondraw:
.
.
.
glloadidentity();
gltranslatef(0.0f, 0.0f, -m_fzoom);
gltranslatef(m_fposx, m_fposy, 0.0f);
glrotatef(m_frotx, 1.0f, 0.0f, 0.0f);
glrotatef(m_froty, 0.0f, 1.0f, 0.0f);
.
.
.
(full size image)
and that's about it. quite simple, no? feel free to post any comments whatsoever on this
article. thanks for viewing; hope it helps!

You might also like