390.789 |
362.245 |
386.032 |
366.429 |
386.559 |
369.289 |
385.557 |
370.483 |
384.841 |
372.370 |
385.785 |
371.975 |
389.348 |
371.005 |
377.266 |
379.550 |
376.916 |
382.096 |
373.959 |
384.111 |
373.109 |
384.387 |
370.598 |
382.973 |
370.067 |
383.667 |
369.099 |
377.171 |
366.549 |
379.162 |
368.245 |
383.977 |
366.427 |
385.877 |
364.343 |
388.575 |
365.326 |
389.769 |
368.751 |
389.556 |
369.598 |
386.514 |
389.381 |
384.817 |
387.311 |
381.979 |
388.205 |
382.978 |
386.632 |
387.414 |
385.150 |
388.393 |
384.099 |
390.620 |
382.926 |
394.712 |
385.771 |
396.611 |
375.693 |
393.622 |
376.697 |
392.655 |
394.063 |
397.035 |
391.727 |
401.327 |
379.119 |
400.460 |
381.912 |
407.491 |
384.119 |
407.505 |
383.090 |
406.474 |
384.888 |
408.943 |
386.664 |
409.806 |
386.207 |
409.759 |
388.031 |
411.599 |
387.911 |
411.545 |
Go cut and paste this to a file named BugPosition.dat if you want to follow along. We first fire up Visual C++ Developers Studio and create a new project. In this case I called the project 'BugTracks'. For the app wizard options select SDI application. Keep the default settings for the rest of the choices, EXCEPT deselect printing preview and docking toolbar.
First lets figure out how to get our data into the program. Go to the ClassView and double click on your document class. It should be called CBugTracksDoc if you named your project 'BugTracks'. When you double click on the class name, the .h file will be opened in the editor. Right before the declaration of the class, lets declare a structure to hold the data.
struct SBugData
{
float x;
float y;
};
Also I want to use one of microsoft's template classes. Templates are C++ things that allow you to write a function for an arbitrary data type. The one I like to use is the CArray
class. Include the afxtempl.h before your structure declaration:
#include <afxtempl.h>
and then in a public area of your class declare a CArray
template class as follows:
CArray <SBugData, SBugData> m_BugDataArray;
Yea, it looks funny, but that is the way you want to type it. This is sort of like declaring a data type of CArray
but we have to tell the template class what type of data we want to store in the array. That is done between the brackets <,>. The first thing in the brackets is the type of data we want to put in the array. The second thing is what we will pass to the CArray
class when we want to add a new element. Since we have a list of data points, it is obvious that we want to have an array of SBugData
. The second parameter is also SBugData
meaning we will just pass the data to the array. (Alternatively we could have passed a 'reference' to the data, but that is another lesson).
Let's go to the .cpp file for the document and add the code now. Expand the CBugTracksDoc
in the ClassView. You should see the member functions for the document. Double click on OnNewDocument()
. You will jump to the function in the .cpp file. This function is called every time a new document (file) is opened. All we want to do here is to clear out the array so it will be ready for the new data. Where you see the //TODO
comment, add this line of code:
m_BugDataArray.RemoveAll();
Now to fill up the array, jump to the Serialize()
function. This is a function called when a new file is opened or saved. Instead of the good old FILE
pointers you use in C with fopen, we are going to use microsoft's CArchive
class. You will notice that a CArchive
is passed (by reference) to the Serialize()
function. This class has the same functionality as we get with the fread and fwrite functions.
void CBugTracksDoc::Serialize(CArchive& ar)
{
// if not storing the data, read it
if (!ar.IsStoring())
{
SBugData Data;
CString strOneLine;
// read data in, one line at a time
while(ar.ReadString(strOneLine))
{
// convert the text to floats
sscanf(strOneLine,"%g %g\n",&Data.x,&Data.y);
// add the data to the array
m_BugDataArray.Add(Data);
}
}
}
For the very basic display, we just need to add some code to draw the data. Go to the function OnDraw()
in the view class CBugTracksView
. This is the function that is called every time the window needs refreshed. All drawing is done through the CDC
class. The CDC
class has several drawing functions, here we will only use the MoveTo
and LineTo
calls.
void CBugTracksView::OnDraw(CDC* pDC)
{
// get a pointer to the document class
CBugTracksDoc* pDoc = GetDocument();
// get the total number of data points
int N=pDoc->m_BugDataArray.GetSize();
// draw all of the connecting lines
for(int i=0; i < N-2; i++)
{
pDC->MoveTo(pDoc->m_BugDataArray[i].x, pDoc->m_BugDataArray[i].y);
pDC->LineTo(pDoc->m_BugDataArray[i+1].x, pDoc->m_BugDataArray[i+1].y);
}
}
Well that is it! Compile and run the program. You will get a few warnings since our data is float, but screen coordinates are int, but that is harmless in this case. Once the program is running, go to File, Open and select the data file we created above. It should display the track in the lower-middle part of the window. We could call it quits here, but let's add a couple of more features.
First, I hate the File, Open menu. Lets make our application accept files that are dropped on the main window. Go to the InitInstance()
function in our CWinApp
class CBugTracksApp
. Near the end of the function add this line:
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
Now let's take advantage of the status bar and put some useful text in it. The status bar is managed by the CStatusBar
class, which is a protected member of CMainFrame
. This means that we can't touch it from other classes. We can either move its declaration to a public part of the class or just a public member function to CMainFrame
to change the status bar text. We will do the later. Right click on CMainFrame
in the class view and select 'Add Member Function'. A dialog will pop up to help add the member function. Type in "void
" (without the quotes) for the function type -- this is the return value of the function, and type in "ChangeStatusText(LPCTSTR text)
" as the function declaration. Make sure that the 'access' is set to public. Press OK. This will automagically add the declaration to the .h file and a blank function to the .cpp file of CMainFrame
. The LPCTSTR
is one of many microsoft defines for data types. We could have alternately have typed "ChangeStatusText(const char *text)
". LPCTSTR
stands for Long Pointer to a Constant T STRing. A T-string is just a string that will work on computers with different character sets (like Japanese). On computers in the char *
.
Jump to the new function in the CMainFrame
.cpp file and add the code to change the text on the status bar. To do this we'll just use the CWnd
function SetWindowText
. CStatusBar
is derived from CWnd
so we can always use any of the CWnd
functions with it. A hint on how to find out about all of these strange new functions... use the help and look at the 'class members' for the class, and then look at the class members for all of the base classes from which it was derived. Your function should now look like this:
void CMainFrame::ChangeStatusText(LPCTSTR text)
{
m_wndStatusBar.SetWindowText(text);
}
We have to call this function from somewhere, and I'll do it from the document. Go to the 'File View' which is the view I use most. Under 'Source Files' double-click on your document file "BugTracksDoc.cpp". Go to the top of that file and include the header file for CMainFrame right after the rest of the includes so that we can access the new function we just made.
#include "MainFrm.cpp"
Next go to our Serialize()
function and modify the reading code to spit some text out to the status bar. We first get a pointer to the main window, which is the CMainFrame
window in SDI applications. Since the function AfxGetMainWnd()
returns a CWnd*
, we cast it to a CMainFrame*
. Then we use the CString
's Format
function and CArray
's GetSize
function to create a text string for the status bar.
void CBugTracksDoc::Serialize(CArchive& ar)
{
if (!ar.IsStoring())
{
SBugData data;
CString line;
CString strStatus;
// get a pointer to the main window
// (which is the mainframe for SDI applications)
CMainFrame *pMain = (CMainFrame*) AfxGetMainWnd();
while(ar.ReadString(line))
{
sscanf(line,"%g %g\n",&data.x,&data.y);
// tell the user your reading points
strStatus.Format("Reading point %d",m_BugDataArray.GetSize());
pMain->ChangeStatusText(strStatus);
m_BugDataArray.Add(data);
}
// tell the user the total number of points
strStatus.Format("Loaded %d points.", m_BugDataArray.GetSize());
pMain->ChangeStatusText(strStatus);
}
}
If you run the app, you'll notice all of the default menu items. We don't need most of these. Let's clean up the menus and add an item for animating the bug track which we'll code later.
Go to the Resource View. Under the Menu resource, double click on IDR_MAINFRAME
. This is the menu resource for the main window. In the edit window, click on File. Then delete the menu entries for New, Save, and Save As. Also delete the main menu headings for Edit and View. Next, go to the empty box at the end of the menu and Add a new heading called 'Track' by selecting the empty box and
typing 'Track'. Drag the Track menu heading so that it is between File and Help. Click on the Track menu and then click on the empty sub menu box. Type in '&Animate\tAlt-A'. The & underlines the 'A' in Animate so that it is the menu Hot Key. The \t is just the scan code for a tab and the Alt-A will be our hot key to start the animation. For the ID, type in `ID_TRACK_ANIMATE
', though this will be filled in automatically if you ever forget.
In order to make Alt-A our hot key, go to the Accelerator resources and double-click on IDR_MAINFRAME
. In the edit window, double-click on the empty box at the end of the list. From the drop list for the ID, select the ID of your new menu item (ID_TRACK_ANIMATE
). Press the `Next Key Typed' button and then press Alt-A. Hit enter to close the dialog.
Before we are done with resources, you should modify the icons to something more suitable for this app. I'm sure you can figure out how to do this. The only hints here are to make user and modify the 32x32 sized icon AND the 16x16 sized icon. If you want part of the icon to be transparent, use that greenish color with the two borders around it on the color palette.
Now we can get back to coding. It's time to add fancier drawing and animating. We will animate the bug track by drawing more and more segments of the path in red as time increases. The rest of the path will be drawn in black.
In order to keep track of the last segment in the path that is to be drawn in red, we have to add a member variable to our document. Go to the Class View, right click on the document class, and select Add Member Variable. Type in 'int
' as the data type and 'm_nBugPosition
' as the variable name. Make sure that it is public and press OK.
Jump to the OnNewDocument()
in the document class. Add this line to initialize the new variable to -1. We will use the value -1 to designate that the track is not being animated.
m_nBugPosition = -1;
Next let's add the message handler for our 'Animate' hot key and menu. Press Ctrl-W to bring up the class wizard. In the class name drop box select the view class (CBugTracksView
) and in the Object ID list, select the ID of our new menu and hot key command (ID_TRACK_ANIMATE
). You'll see the two possible choices in the Messages list. Double-click on COMMAND
to add a function to handle our new command. You will be prompted for a function name. Just accept the default one OnTrackAnimate()
and press OK. You will see the function appear in the Member Function list near the bottom of the dialog. Double-click on the function to jump directly to the code. We set m_nBugPosition
to zero and start a timer that will redraw the bug tracks in intervals of 0.2 seconds.
void CBugTracksView::OnTrackAnimate()
{
// get the document
CBugTracksDoc* pDoc = GetDocument();
// set the position to the first data point
pDoc->m_nBugPosition=0;
// create a timer with id=1 and delay of 200 milliseconds
SetTimer(1,200, NULL);
Next we need to handle the timer message. Ctrl-W back to the class view. Make sure you are looking at the view class, select the class as the Object ID, then double-click WM_TIMER
in the message list to handle the timer message. Again, double-click on the function name to jump to the code. In the OnTimer
function we will first check the ID of the timer to make sure we are responding to the correct timer. In this case we set the timer ID to 1. Then we will invalidate the window so that it will be repainted.
void CBugTracksView::OnTimer(UINT nIDEvent)
{
if(nIDEvent==1)
{
// tell windows the view needs redrawn
// note: the last parameter is the erase flag.
// if it is TRUE, things will flicker like crazy.
InvalidateRect(NULL,FALSE);
}
CView::OnTimer(nIDEvent);
}
All that is left now is to fix up the OnDraw()
function in the view class. We need to first draw the red tracks, then the blue ones, then increment the position m_nBugPosition
. If m_nBugPosition
is larger than the number of positions we will set it to -1 and kill the timer.
One of the new things in this code is the CPen
class that is needed to change the color of the line. The way these graphical objects work is that you 'select' the object in to the CDC
class. When you are done with it, you select the old one that was in there previously and delete the one you just used.
void CBugTracksView::OnDraw(CDC* pDC)
{
CBugTracksDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// make pens for solid lines of thickness 2
CPen RedPen(PS_SOLID, 2, RGB(255,0,0));
CPen BluePen(PS_SOLID, 2, RGB(0,0,255));
CPen *pOldPen = pDC->SelectObject(&RedPen);
int i, N=pDoc->m_BugDataArray.GetSize();
// draw any tracks which need animated
for(i=0; i < pDoc->m_nBugPosition-1; i++)
{
pDC->MoveTo(pDoc->m_BugDataArray[i].x,)pDoc->m_BugDataArray[i].y);
pDC->LineTo(pDoc->m_BugDataArray[i+1].x,pDoc->m_BugDataArray[i+1].y);
}
// change pens
pDC->SelectObject(&BluePen);
// start drawing non animated tracks, but need to check for a
// valid starting postion
int start=pDoc->m_nBugPosition;
if(start<0) start=0;
for(i=start; i < N-2; i++)
{
pDC->MoveTo(pDoc->m_BugDataArray[i].x,)pDoc->m_BugDataArray[i].y);
pDC->LineTo(pDoc->m_BugDataArray[i+1].x,pDoc->m_BugDataArray[i+1].y);
}
// deselect pens and delete them
pDC->SelectObject(pOldPen);
RedPen.DeleteObject();
BluePen.DeleteObject();
// move to next position or quit animating
if(pDoc->m_nBugPosition!=-1) pDoc->m_nBugPosition++;
if(pDoc->m_nBugPosition>=N)
{
pDoc->m_nBugPosition=-1;
// stop timer 1
KillTimer(1);
// redraw and erase so all lines are in initial state (blue)
InvalidateRect(NULL);
}
}
Ctrl-F5 the program to build and run it. Fix any bugs and you are done! Of course many improvements can be made, like scaling and centering the path to better fit the view, printing of the path, etc... but I think you have enough to go on. Good luck! (and don't be afraid of that F1 key)
This article was contributed by Andrew Fenster.
Environment: Visual C++
Most of the time when you are creating a new MFC application, you'll start with the MFC App Wizard. The App Wizard generates a basic skeleton application which you will flesh out into a full-fledged, useful program.
Even the skeleton application you get from the App Wizard contains a lot of very arcane looking code, and there's a great deal of hidden code as well. The purpose of this article is to demystify some of that code. I'll show you how to build a simple MFC application without the App Wizard. By the time you are done, the mysterious looking code that comes from the App Wizard will no longer be so mysterious, and you'll be better prepared to modify it to suit your own purposes.
Every 32 bit Windows application has two essential program elements: WinMain and WndProc. Your program will have one WinMain for the entire program, and one WndProc for each window in the program. Although MFC creates these for you, you still need to know a little about them.
WinMain is the function that starts your application. Once your application is running, the Windows operating system will start placing your application's messages in a message queue. WinMain makes three Windows API calls to get these messages from the operating system and to process them. First it calls GetMessage to retrieve a message. Then it calls TranslateMessage to perform any necessary conversion of the message. Finally, WinMain calls DispatchMessage, which tells the operating system to send the message to the appropriate WndProc for handling.
Once WndProc receives a message, it looks through its message handlers for instructions on what should be done with the message. Your job as a Windows application programmer, it to write the handlers.
Next we'll look at a simple, bare-bones MFC application. MFC provides the WinMain and WndProc. We must provide an MFC-derived application class and window management class.
Our application class will be derived from the MFC class CWinApp. CWinApp provides all the member variables and functions to initialize, start, run and close an application. CWinApp contains a pointer called m_pMainWnd which will point to an object of our derived window management class. Each MFC application has one and only one object of derived directly from CWinApp. In the example below, that class is called "CMyApp."
Our window management class will be derived from CFrameWnd. CFrameWnd has all the member variables and functions to create and manage windows. Note that when you create an object of our derived windows class, you have not created an actual window. The new object uses its Create() function to create windows.
Here's what happens when we start our program. You can follow along in the code:
Here is the code. Try it - it works!
#include <afxwin.h>
//derive my own window class from CFrameWnd
class CMyWin: public CFrameWnd
{
public:
CMyWin( );
DECLARE_MESSAGE_MAP( )
};
//define my window class' constructor:
CMyWin::CMyWin( )
{
Create(0, "This Text Will Appear in the Title Bar");
}
//derive my own application class from CWinApp
class CMyApp: public CWinApp
{
public:
virtual BOOL InitInstance( );
};
//define my application class' InitInstance( )
BOOL CMyApp::InitInstance( )
{
m_pMainWnd = new CMyWin( );
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
//here is my application's message map
BEGIN_MESSAGE_MAP(CMyWin, CFrameWnd)
// any messages to be processed by
// CMyWin get listed here.
END_MESSAGE_MAP( )
//declare an instance of application
//class for WinMain to use.
CMyApp app;
Here's what you get when you compile and run:
Your actual window will be bigger.
The code above creates a simple, bare bones application. The code you will find in a single document interface application is more complicated, but still works along the same lines. You still have an application class derived from CWinApp. You still have a window management class derived from CFrameWnd. In this case, however, the derived window management class is called CMainFrame.
Your derived application class still has an InitInstance() function, but the function looks a lot more complicated. Among other things, it contains something like this:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
Here, the application object creates a single document template pointer and points it to a new single document template object. There are four parameters passed to the CSingleDocTemplate constructor. The first is an integer, the resource ID for the IDR_MAINFRAME resource. The next three parameters pass class information for my document class, frame class and view class. Then the pointer to this new CSingleDocTemplate is added to the list of document templates maintained by the application object. (In an SDI application, there's only one template.)
IDR_MAINFRAME is a resource containing:
The document string contains up to seven pieces of information in substrings separated by '\n' characters:
Here's a sample string:
"My Big Program\n\n\nMy Big Program(*.mbp)\n.mbp\nBigProgram\nMBP Document."
So there you have it. Now that you know a little more about what your wizard-generated code is doing, you can go in and start modifying it. For example, if you want to change the size and positioning of your application's main window, you can modify the Create( ) function. Try substituting the following code for the Create( ) function listed above. You'll get a much smaller window positioned in the upper left corner of the screen.
RECT x;
x.top = 30;
x.left = 30;
x.bottom = 300;
x.right = 300;
Create(NULL, "My New Window", WS_OVERLAPPEDWINDOW, x);
There are a lot of settings and functions you can play with. You might be the type who never changes the default code. Even so, the more you know about what's going on, the better your programs will be.
This article was contributed by Andrew Fenster.
Environment: Visual C++
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.
There are still a lot of good reasons to use .DLLs. They haven't gone away. Sure, whatever you can do with a .DLL, you can probably do with a COM object. Granted, there are a number of shortcomings to .DLLs, some of them serious, which is why we ended up with COM in the first place. Still, .DLLs remain a very useful tool. Compared to COM and ATL, they are much easier to make. Learning COM or ATL requires a serious investment of time and effort. Making a .DLL is relatively easy. Modifying one is easy too. If you know some C++ and MFC, you could be making .DLLs today.
This article will review the types of .DLLs you can make with MFC, including when to use each type and how to make them. In the next article there will be a discussion of the limitations of .DLLs (which led to the rise of COM and ATL), and how these can be partially avoided. In the third article, there will be more coding details and examples.
There are two kinds of .DLLs you can make using MFC: an MFC extension .DLL or a regular .DLL. Regular .DLLs in turn come in two varieties: dynamically linked or statically linked. Visual C++ also allows you to make a generic Win32 .DLL, but in this article I'm only going to discuss the MFC-based .DLL types.
MFC extension .DLLs
Every .DLL has some kind of interface. The interface is the set of the variables, pointers, functions or classes provided by the .DLL which you can access from the client program. They are the things that allow the client program to use the .DLL. An MFC extension .DLL can have a C++ style interface. That is, it can provide ("export") C++ functions and entire C++ classes to be used by the client application. The functions it exports can use C++ or MFC data types as parameters or as return values. When it exports a class, the client will be able to create objects of that class or derive new classes from it. Inside the .DLL, you can also use MFC and C++.
The MFC code library used by Visual C++ is stored in a .DLL. An MFC extension .DLL dynamically links to the MFC code library .DLL. The client application must also dynamically link to the MFC code library .DLL. As the years have gone by the MFC library has grown. As a result, there are a few different versions of the MFC code library .DLL out there. Both the client program and the extension .DLL must be built using the same version of MFC. Therefore, for an MFC extension .DLL to work, both the extension .DLL and the client program must dynamically link to the same MFC code library .DLL, and this .DLL must be available on the computer where the application is running.
Note: If you have an application which is statically linked to MFC, and you wish to modify it so that it can access functions from an extension .DLL, you can change the application to dynamically link to MFC. In Visual C++, select "Project | Settings" from the menu. On the "General" settings tab you can change your application to dynamically link to MFC.
MFC extension .DLLs are very small. You can build an extension .DLL which exports a few functions or small classes and has a size of 10-15 KB. Obviously, the size of your .DLL depends on how much code you store in it, but in general MFC extension .DLLs are relatively small and quick to load.
Regular .DLLs
The MFC extension .DLL only works with MFC client applications. If you need a .DLL that can be loaded and run by a wider range of Win32 programs, you should use a regular .DLL. The downside is that your .DLL and your client application cannot send each other pointers or references to MFC-derived classes and objects. If you export a function, it cannot use MFC data types in its parameters or return values. If you export a C++ class, it cannot be derived from MFC. You can still use MFC inside your .DLL, but not in your interface.
Your regular .DLL still needs to have access to the code in the MFC code library .DLL. You can dynamically link to this code or statically link. If you dynamically link, that means the MFC code your .DLL needs in order to function is not built into your .DLL. Your .DLL will get the code it needs from the MFC code library .DLL found on the client application's computer. If the right version of the MFC code library .DLL is not there, your .DLL won't run. Like the MFC extension .DLL, you get a small .DLL (because the .DLL doesn't include the MFC code), but you can only run if the client computer has the MFC code library .DLL.
If you statically link to the MFC code library, your .DLL will incorporate within itself all the MFC code it needs. Thus, it will be a larger .DLL, but it won't be dependent on the client computer having the proper MFC code library .DLL. If you can't rely on the host computer having the right version of MFC available, this is the way to go. If your application users are all within your own company, and you have control over what versions of the MFC .DLLs are lurking on their computers, or if your installation program also loads the right MFC .DLL, this might not be an issue.
You can make an MFC-based .DLL with the App Wizard. Select "File | New" from the menu. On the "Projects" tab, select "MFC AppWizard (.DLL)." Pick a name for your new project and click "OK." On the next screen, you will have the choice to create an MFC extension .DLL, a regular .DLL "using shared MFC .DLL" (i.e., a regular .DLL dynamically linked to MFC), or a regular .DLL statically linked to MFC. Pick the one you want and click "Finish."
App Wizard builds a .DLL which doesn't do anything. The new .DLL will compile, but since it doesn't export any classes or functions yet, it is still essentially useless. You now have two jobs: (1) add functionality to make your .DLL useful; and (2) modify your client application to use your .DLL.
Export a class
Once you're done with the App Wizard, you can add classes to your .DLL by adding the .cpp and .h files from another project, or you can create them from scratch within your current project. To export a class, you add "__declspec(dllexport)" to the class declaration so it looks like this:
class __declspec(dllexport) CMyClass
{
//class declaration goes here
};
If you are making an MFC extension .DLL, you can instead use the AFX_EXT_CLASS macro:
class AFX_EXT_CLASS CMyClass
{
//class declaration goes here
};
There are other ways to export a class, but this is the easiest. If your exported class requires a resource which is located in the .DLL, for example a class derived from CDialog, the process is more involved. I'll cover this subject in tutorial #3. Below I'll discuss what to do to the client application so that it can use your exported class.
Export variables, constants and objects
Instead of exporting a whole class, you can have your .DLL export a variable, constant or object. To export a variable or constant, you simply declare it like this:
__declspec(dllexport) int MyInt;
__declspec(dllexport) extern const COLORREF MyColor =
RGB(50,50,50);
When you want to export a constant, you must use the "extern" specifier. Otherwise you will get a link error.
You can declare and export a class object in the exact same manner:
__declspec(dllexport) CRect MyRect(30, 30, 300, 300);
Note that you can only export a class object if the client application recognizes the class and has its header file. If you make a new class inside your .DLL, the client application won't recognize it without the header file.
When you export a variable or object, each client application which loads the .DLL will get its own copy. Thus, if two different applications are using the same .DLL, changes made by one application will not affect the other application.
It's important to remember that you can only export objects and variables which are of global scope within your .DLL. Local objects and variables cease to exist when they go out of scope. Thus, if your .DLL included the following, it wouldn't work.
MyFunction( )
{
__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;
}
As soon as the object and variable go out of scope, they will cease to exist.
Export a function
Exporting functions is similar to exporting objects or variables. You simply tack "_declspec(dllexport)" onto the beginning of your function prototype:
__declspec(dllexport) int SomeFunction(int);
If you are making an MFC regular .DLL which will be used by a client application written in C, your function declaration should look like this:
extern "C" __declspec(dllexport) int SomeFunction(int);
and your function definition should look like this:
extern "C" __declspec(dllexport) int SomeFunction(int x)
{
//do something
}
If you are building a regular .DLL which is dynamically linked to the MFC code library .DLL, you must insert the AFX_MANAGE_STATE macro as the first line of any exported function. Thus, your function definition would look like this:
extern "C" __declspec(dllexport) int AddFive(int x)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
return x + 5;
}
It doesn't hurt to do this in every regular .DLL. If you you switch your .DLL to static linking, the macro will simply have no effect.
That's all there is to exporting functions. Remember, only an MFC extension .DLL can export functions with MFC data types in the parameters or return value.
Export a pointer
Exporting an uninitialized pointer is simple. You do it the same way you export a variable or object:
__declspec(dllexport) int* SomeInt;
You can also export an initialized object this way:
__declspec(dllexport) CSomeClass* SomePointer =
new CSomeClass;
Of course, if you declare and initialize your pointer you need to find a place to delete it.
In an extension .DLL, you will find a function called DllMain( ). This function gets called when the client program attaches your .DLL and again when it detaches. So here's one possible way to handle your pointers in an extension .DLL:
#include "SomeClass.h"
_declspec(dllexport) CSomeClass* SomePointer = new CSomeClass;
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
}
else if (dwReason == DLL_PROCESS_DETACH)
{
delete SomePointer;
}
}
A regular .DLL looks more like an ordinary MFC executable. It has an object derived from CWinApp to handle opening and closing your .DLL. You can use the class wizard to add an InitInstance( ) function and an ExitInstance( ) function.
int CMyDllApp::ExitInstance()
{
delete SomePointer;
return CWinApp::ExitInstance();
}
A .DLL can't run on its own. It requires a client application to load it and use its interface. Making a client application that can do so is not difficult.
When you compile your .DLL, the compiler creates two important files: the .DLL file and the .lib file. Your client application needs both of these. You must copy them into the project folder of your client application. Note that the .DLL and .lib files that are created when you build in Debug are different that those built when you build in Release. When you are building your client application in Debug, you need the Debug versions of the .DLL and .lib files, and when you are building in Release you need the Release .DLL and .lib. The easiest way to handle this is to put the Debug .DLL and .lib files in your client application's Debug folder and the Release .DLL and .lib in the Release folder.
The next step is to go into your client project settings and tell the linker to look for your .lib file. You must tell the linker the name of your .lib file and where it can be found. To do this, open the project settings, go to the "Link" tab and enter your file name and path in the "Object/library modules" box. It should look something like this:
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.
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.
This article was contributed by Andrew Fenster.
Environment: Visual C++
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.
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.
This article was contributed by Andrew Fenster.
Environment: Visual C++
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.
You may wish to create a .DLL which exports resources such as dialog templates, bitmaps, icons or strings. You may, for example, wish to make your application multilingual, with one .DLL for English strings and dialog templates, another with Spanish strings, etc. Exporting resources is relatively easy to do.
Your first step in building a .DLL which exports resources is (of course) to build a .DLL and give it some resources. You can use a regular or extension .DLL. Use the App Wizard to create your .DLL, as described in Article 1. To add a resource using the Visual C++ menu, select "Insert" and "Resource" and then select the type of resource you wish to add.
Once you've built your .DLL and given it all the resources it is to import, you must build a header file to be used by the client application. You can add a new header file using the Visual C++ menu by selecting "File | Add To Project | New | C/C++ Header File." Now open your .DLL's Resource.h file. Assuming you have compiled your .DLL, the top of this file will look something like this:
#define IDS_SOME_STRING1 1
#define IDS_SOME_STRING2 2
#define IDS_SOME_STRING3 3
#define IDD_SOME_DIALOG 11000
#define IDB_SOME_BITMAP 11001
As you can see, each of these #defines gives one of your resources an identifying integer value. Copy the complete #define for each resource you wish to export into your newly created header file.
At this point, your .DLL is almost ready to go. Only one more step is required. In articles 1 and 2, I demonstrated how to use a .DLL with implicit linking. When you are using implicit linking, the client application will attempt to connect to your .DLL automatically. If it cannot do so, the client application won't run. When you use implicit linking, it is important to remember that the client application will not attempt to connect to your .DLL unless your .DLL exports at least one class or function which is used by the client. So even if your .DLL's only purpose is to provide resources, you still must export at least one function or class which is used by the client. It doesn't matter whether your exported class or function does anything useful. It simply has to be there in order to force the client to connect to the .DLL. So all that's left to make your .DLL complete is to export a "do-nothing" function. See Article 1 for an explanation of how to export a function.
Article 1 describes how to build a client application which will connect to your .DLL. Remember, your client application must actually call at least one function from your .DLL or create at least one instance of an imported class. If not, your client application will not automatically connect to your .DLL at runtime. Be sure to add a copy of the header file you created earlier to your client application project.
In some respects, an extension .DLL is easier to use when you are importing resources. This is because an extension .DLL will try to find a resource in your .DLL if it cannot find it in its own resources. Suppose your client application included the following code:
CString str;
str.LoadString(IDS_SOME_STRING1);
AfxMessageBox(str);
The first thing your client application will do is look for a definition of IDS_SOME_STRING1. Suppose the header file you imported defines IDS_SOME_STRING1 as 1. The client application will look in its own resources for resource #1. If it cannot find resource #1 in its own resources, it will next look in the extension .DLL. If you built a regular .DLL, the client application won't take this extra step.
Note that there is a big potential for problems here. If your client application has some other resource which is also defined as resource 1, it will attempt to load its own resource. The client only looks to your extension .DLL if it can't find the properly numbered resource within its own resources. One way to avoid this problem is to renumber your resources to avoid conflicts.
When you use a regular .DLL, the client application must be told explicitly to look to the .DLL to find the resource. Here's how you do it:
//Store the current resource handle
HINSTANCE hClientResources = AfxGetResourceHandle();
//Tell the client to use the .DLL's resources
AfxSetResourceHandle(::GetModuleHandle("SomeDll.dll"));
//Do something with the .DLL's resources
CString str;
strRes.LoadString(IDS_SOME_STRING1);
//Restore the client application resource handle
AfxSetResourceHandle(hClientResources);
You can use this technique in your client application even if you are using an extension .DLL. With a regular .DLL it is necessary. While not absolutely necessary with an extension .DLL, it gives you the certainty that the right resources will be loaded.
Exporting a class derived from the CDialog class is only slightly more complicated than exporting other classes. The extra complication comes from the fact that you must also export the dialog template, which is a resource.
When you export a class, you must provide a copy of the class header file to be used by the client application. When you export a class derived from CDialog, which has a resource, you must also export the #define for the resource ID. So the easiest thing to do is simply tack the #define onto the top of the header file like this:
#ifndef IDD_HELLO_DIALOG
#define IDD_SOME_DIALOG 9000
#endif
As with other exported resources, you can run into problems if the client application also has a resource with the same number. Either be careful to avoid conflicts, or use the code shown above to tell the client application explicitly where to find the correct resource.
Note that the same technique used with CDialog bases classes can also be used with any other class you wish to export which uses resources. Be sure to define the resources in the header file you provide for the client application, and take whatever steps are necessary to avoid conflicts.