Integrating C++ and MFC Code With Graphical Programming
Integrating C++ and MFC Code With Graphical Programming
In your Visual C++ development environment you can use the AppWizard to
create an MFC-based DLL by selecting the MFC AppWizard(dll) project when
you create a new workspace. You can then choose to statically or dynamically
link to the MFC DLLs. When you create your project, you also need to specify
whether or not to include Automation or Windows Sockets features in your
DLL.
This CWinApp derived class is useful for adding member functions that represent
the entry points to the DLL. You can thus use this class as the interface or wrapper
for all the other objects in your DLL. This is a design pattern known as a "facade".
To add DLL entry points, you just need to add global functions that are declared as
exported:
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return theApp.MyFunction(iVal);
}
Once you build your DLL, you can call the DLL entry point from a VI using the
Call Library Function node. I find it a useful convention to paste the DLL entry
point declaration in my VI diagram below the Call Library Function. Seeing the
function declaration helps in configuring the DLL node to have all the right
parameters in the right order. It also helps any readers of the G code! This
example also shows using the error I/O clusters within the VI calling the DLL
node even though in this particular case there is no error returned from the DLL.
Using error I/O cluster in your DLL-calling VIs is usually a good idea. It makes
it easier to manage errors in your higher level G application.
• Easy to do
• Useful for special user interface of system
functions
• ActiveX controls in your G-based GUI may
be a better alternative
Once you have the dialog functionality in place, create a member variable in your
CWinApp derived class for each dialog. Then you simply need to create a member
function for your CWinApp derived class that invokes the dialog by calling the
DoModal() member function of the CDialog derived class.
extern "C" __declspec(dllexport) HRESULT BV_InvokeDialog(char*
pszMessage,BOOL* bResult)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return theApp.InvokeDialog(pszMessage, bResult);
}
Calling the "DoModal()" function on the CDialog derived class invokes the dialog and
returns the result (whether OK or Cancel was pressed).
HRESULT CDialogTestApp::InvokeDialog(char* pszMessage, BOOL* bResult)
{
// TODO: Add your command handler code here
m_myDialog.m_message = pszMessage;
int nRetVal = m_myDialog.DoModal();
*bResult = (nRetVal == IDOK);
return NOERROR;
}
You can make an MFC-based dialog appear cosmetically just like a G Window.
You need to add the BridgeVIEW or LabVIEW icon to the dialog.
Adding the dialog to the icon is done during dialog initialization. You need to
add the icon to your project resources.
BOOL TestDialog::OnInitDialog()
{
CDialog::OnInitDialog();
//Set Icon to Match BridgeVIEW
HICON hIcon = theApp.LoadIcon(IDI_BVICON);
SetIcon(hIcon, FALSE );
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Also, in the properties page for the dialog, set the 3D-look (More Styles tab) and
Client edge (Extended Styles tab) properties. The dialog will now blend in with
the rest of your G-based GUI.
• Simple dialog
• Dialog with tree control
• Passing data to and from the dialog
Demo – The DialogTest project shows a both a basic dialog and a dialog with a
tree control. This example also illustrates how data is passed to the dialogs from
the G application and returned to the G application.
In any complex C++ DLL, you inevitably end up managing diverse collections
of objects. Care must be taken when manipulating this objects from your G
application. In particular, you do not want to pass the object pointer directly to
the G application, because if the object no longer exists, a crash will likely result.
This, of course, is also true inside your C++ application. Instead, you should use
a mechanism, such as a "safe pointer", that can be verified whenever it is passed
into the DLL from the G application.
Many of the objects I access from my G applications are derived from a base
class template called CRef. This template automatically assigns a reference
number to each object during the object construction, and stores the object
reference and pointer in a global CMap. When the object is destroyed, it
removes itself from the Map. The object's refnum is always used in the G
application. In this way, you can detect stale or invalid refnums before an
object is used. Using a template provides a type-safe way of managing different
objects - each class derives from a typedef that is created from the CRef
template.
See the CRef.h file in the GObjects Project for the source code to the CRef
template.
Also, in the G application, I create a specific "type" for each object class. This
just helps in terms of type enforcement at the G diagram level, so that different
object class refnums cannot be wired to each other.
A tip from LabVIEW developer Stepan Riha to do this easily: drop down a data
log file refnum; create an enum control and give the first (and only) item the
name of your object; then place the enum inside the data log file refnum.
By using a different named enum for each object type, you can have a typed
refnum for each object, and you will not accidentally wire different object
refnums together (you will get broken wires if you do).
Make this control a strict typedef that is then used by all VIs that interact with
that particular object class. Then if you need to change it, all the VIs are
automatically updated.
You also have to pass the object reference into your Call Library Node by type.
The refnum is a 32-bit integer.
When using the Occur function in LabVIEW/BridgeVIEW, you must link your
project with LabVIEW.lib, and use extcode.h as a header file. Both are
available in the CINTOOLS directories. Some functions exported from
LabVIEW.lib may conflict with the MFC library msvcrt.lib. You can configure
your Visual C++ project settings to explicitly exclude this library from being
used in the link.
• Declare a thread
– UINT MyThread(LPVOID pParam) {}
• Spawn the thread when needed
– AfxBeginThread(MyThread, pParam);
• Terminate the thread when appropriate
– Signal the thread to terminate
– Wait for the thread to complete
- GetExitCodeThread(…);
You also must ensure that the thread terminates when your application is
finished.
Demo – The Timer Test Project illustrates the basic principles of using a multi-
threaded DLL with your G application.
It illustrates both spawning and terminating threads, signaling and
synchronization between threads, and event notification back to your G
application from a thread.
The End
Four projects are included that demonstrate some key aspects of using C++ and
MFC with G applications.
You have to know your C++ programming and MFC quite well in order to take
advantage of the examples provided here. However, with the short learning
curve of G, you can make some tradeoffs between G and C++ programming to
get the best of both worlds.