View RSS Feed

Development Team Blog

The other side of External_Function

Rate this Entry
We're continuing the back to the basics theme for advanced VDF developers by visiting the other side of External_Function. If you haven't done so already, you should read the first part, it's a prerequisite for understanding what I'm about to talk about here.

Let's start with a simple DLL written in C++, exporting only one silly function SayHello(), which we'll call from our VDF program. The finished C++ project is attached at the end of this article, but we'll take it step by step. I will assume that you have at least some familiarity with C++, and only talk about the specific gotchas and important points for exposing DLL functions to be used from a VDF program.

Creating the C++ DLL project
Using Visual Studio 2008, start by creating a new project. Since this will be a native C++ project, you should select Win32 in the Visual C++ group to the left as in the screenshot below, and then select the Win32 Project template to the right. We'll give it the name hello, and then select OK to move on to the second step.



Before you click Finish in the second step, we have to specify that we want to create DLL project, otherwise it will be an .exe application. Select Application Settings to the left as in the screenshot below, then select DLL to the right. Leave the other checkboxes unchecked as per default. When you have carefully verified the correct settings you can click Finish to create the project.



You will now end up with a project structure in Solution Explorer like in the screenshot below.



Writing the SayHello() function
We'll look at some details later, right now we'll move right along and add our SayHello() function. Open hello.cpp if it's not already open, and add the following code:

Code:
#include <string>

extern "C" int __stdcall SayHello(const char* name)
{
    std::string sText("Hello, ");
    sText+=name;
    MessageBoxA(0,sText.c_str(),"In hello.dll",0);
    return 0;
}
There are a few things to pay attention to here. The extern "C" part is required since we're compiling C++, but we don't want C++ linkage conventions for the function that will be exported. It simply tells the C++ compiler to define the SayHello() function with standard C linkage instead, making it accessible from non-C++ code.

Next you'll notice __stdcall, and you'll remember from the previous article that stdcall calling convention is required to be able to call the function from VDF code, so we must specify that here. There are also other ways you can specify that in configuration settings, but that will change the default calling convention, rather than just for this function, which is unnecessary.

You may have noticed the trailing A in MessageBoxA(). Don't worry, it's just a cheap trick by me to defer the discussion of a topic until later and let our program compile anyway.

Exporting the SayHello() function
While this will now compile, we also have to tell the linker to export the SayHello() function so it can be called from VDF code. There are several different ways to do that, and as you may remember from the previous article, you need to watch out for unintended name decorations. The easiest way to ensure the exported name is exactly what you want is to use a .def file. Select Project -> Add New Item, and then select Module Definition File as in the screenshot below, and we'll give it the name hello.def.



Now we'll simply specify SayHello in an EXPORTS section, by adding the following code to hello.def:
Code:
EXPORTS
    SayHello
The project should now compile and link successfully, and produce hello.dll.

Calling SayHello() from VDF code
I will assume that you know how to create a Visual DataFlex project, so I will skip all that and just show the code for calling the DLL function. But first you have to make sure the DLL can be found by your VDF program, and the best way to do that for now is to copy hello.dll into your workspace Programs directory, so it's in the same directory as your .exe.

The following test code will demonstrate how to call SayHello() from VDF code:
Code:
Use DfAllEnt.pkg

External_Function SayHello "SayHello" hello.dll String sName Returns Integer

Procedure TestIt
    Integer iResult
    Move (SayHello("VDF")) to iResult
End_Procedure

Send TestIt
Note how you can declare the parameter as String in VDF code, when it's const char* in the C++ code, just as I explained in the previous article.

If all goes well, when you compile & run the VDF program you will now see something like this:



And that's really all there is to it, it's as simple as that to create your own DLLs to extend your VDF application. The possibilities are endless, External_Function allows you to combine multiple programming languages in your application by using DLLs. You can implement low-level functionality in another programming language, and integrate it seamlessly with your VDF application where most of your application logic resides, utilizing the power of the Visual DataFlex programming language and high-level framework for the rest of your application.

Summary
This was a very basic demonstration of the other side of External_Function, but it hits on the most common stumbling blocks when creating DLLs and using them from VDF code. The major points and gotchas are:

  • Creating the appropriate type of project, Win32 native C++ project. You can use many other advanced features in your DLL as well, including MFC & ATL, COM objects and if you're clever you can even use .Net framework classes, but the actual exported functions must be in native C/C++ code, ala Win32 API style.

  • The exported functions should be using C linkage and the __stdcall calling convention in order to be accessible from your VDF program. Sometimes you can actually call __cdecl functions, but it can be problematic, so stick to __stdcall.

  • Beware of functions exported with decorated names, and use a .def file to ensure the functions are exported with the proper names.


What about the MessageBoxA() trick, what was that all about? As I mentioned in the previous article, the Win32 SDK has both Unicode and Ansi versions of many API calls, including MessageBoxA() and MessageBoxW(). The SDK header files also defines MessageBox() to map to one or the other depending on whether you have chosen to use Unicode or Ansi (multi-byte) in your project configuration. Visual Studio 2008 projects use Unicode by default. You can change this project setting in the Project Properties dialog, as shown in the screenshot below:



Once that's been changed to Multi-Byte you can go back and change the code to use MessageBox() without the trailing A. That's obviously the preferred solution if you're not planning on using Unicode in your DLL. Picking and choosing the A and W versions yourself in code is just asking for trouble, not to mention not always possible when using MFC & ATL etc. It was just an easy way to defer the discussion to the end.

Some advanced project setting gotchas:

  • Beware of the selected configuration at the top of the dialog, most of the time you want to change the settings for All Configurations.

  • Beware of the Character Set Unicode/Multi-Byte setting as mentioned above. Since you will be using the DLL from a VDF application, you will almost certainly want to set it to Multi-Byte. Just remember to keep track of OEM/Ansi translations in your VDF code, VDF is OEM and your DLL will expect Ansi.

  • Beware of the Use of MFC and Use of ATL project settings on the same page.

  • Beware of the Common Language Runtime support setting on the same page. Usually this is set to No, but if you
    want to use .Net framework classes, you can set this differently to mix native and managed code in your dll. Just remember that the exported functions must still be in native code in order to be used from your VDF program.

  • Beware of the Runtime Library setting on the C/C++ Code Generation page. This controls whether you want to use a shared DLL version of the C and C++ libraries. If you do, you need to deploy those DLLs as well with your VDF application. If you use the static library (non-DLL versions) you simplify deployment and do not have to install any of the C and C++ runtime DLLs.
Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	hello-project.png 
Views:	2337 
Size:	20.1 KB 
ID:	3809   Click image for larger version. 

Name:	hello-project2.png 
Views:	2233 
Size:	24.0 KB 
ID:	3810   Click image for larger version. 

Name:	hello-project3.png 
Views:	2187 
Size:	12.2 KB 
ID:	3811   Click image for larger version. 

Name:	hello-project4.png 
Views:	2211 
Size:	31.9 KB 
ID:	3812   Click image for larger version. 

Name:	hello-project5.png 
Views:	2197 
Size:	25.2 KB 
ID:	3813   Click image for larger version. 

Name:	hello-project6.png 
Views:	2205 
Size:	1.6 KB 
ID:	3814  
Attached Thumbnails Attached Files

Comments

  1. pergh's Avatar
    Nice walkthrough. Clear and thorough as ever, Mr Falk.

    Will we see a clever followup on interacting with manged code through externals? Perhaps also (oh horrors) callbacks? :-)


    Best regards,
    Per Gunnar Hansų
  2. Sam Neuner's Avatar
    Sonny:

    I am trying to learn to develop dlls. I have no experience with C programming. I downloaded the 2008 Visual Studio C++ Express. I compiled your hello.dll project and it works fine. I created my hello.dll to your specs but cannot get VDF to recognize the exported function "SayHello". I get a VDF error 4355 "SayHello". I think this error indicates that it does not recognize the "SayHello" function. I compared all of my code with yours and can not see any differences. However, there obviously is something different. I do notice that when my dll is compiled there is no "hello.exp" or "hello.lib" as there is with yours. Is there some setting that tells the project to use the def file to expose the function?
  3. Sam Neuner's Avatar
    Just to add, I am choosing the "Multi-threaded Debug DLL" in the Runtime Library.