CDF Re-invented! Part 2 advanced functions.
Download the full example here
In this document we will concentrate on the supported function types, parameters and return types. We will show examples of all return types, how to define them and how to add input parameters and get them to work as well.
First of all, when you want to create CDFs using parameters and not just returning values, you have to take into consideration the calling convention used when calling CDFs. In Windows you have 4 different types and all have a different way to handle memory for the calling functions. All Windows API in the Windows SDK use, Pascal calling convention (aka. WINAPI or stdcall). This is also what you need to use for creating a CDF for PRISM (the database engine shared by DfW and DG3). In modern compilers and linkers, the exported functions automatically get some extra information added to their names called mangling. This is to help the user of the dll to understand what calling convention is used. To get rid of the full function description we can add extern "C" to our definition, but if you want parameters in to your functions, you still get an @X where the X tells the function how much memory the function needs for the exchange of parameters. Since DfW CDF do not at the present time understand mangling parameters, but needs to have them presenteted in the same manner as the Windows API dlls, we have to find a way to get rid if the mangling. To do this we can define an old style .DEF file or use #pargma commands to the compiler. Event if the first is to prefer to get cleaner looking DLL, we chose to use the later way since it is easier en quicker.
Here is how it is done
The first thing to do when wanting to use parameters in to a CDF is to change the calling convention to the functions. The easiest way to do that is to change the project settings. Right click on your project and select properties form the right click menu. Locate Configuration Properties->C/C++->Advanced and change Calling Convention to __stdcall (/Gz). Make sure you do it on both Release and Debug.
The new calling convention
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the EXAMPLECDF_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// EXAMPLECDF_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#define EXAMPLECDF_API extern "C" __declspec(dllexport)
#define EXAMPLECDF_API extern "C" __declspec(dllimport)
// Simple function that returns values
EXAMPLECDF_API int returnint(void);
EXAMPLECDF_API long returnlong(void);
EXAMPLECDF_API float returnfloat(void);
EXAMPLECDF_API double returndouble(void);
EXAMPLECDF_API LPCSTR returnstring(void);
// Examples of functions that takes input parameters and returns a processed value
EXAMPLECDF_API LPCSTR whatis(LPCSTR textin,int meaning);
#pragma comment(linker, "/EXPORT:returnint=_returnint@0")
#pragma comment(linker, "/EXPORT:returnlong=_returnlong@0")
#pragma comment(linker, "/EXPORT:returnfloat=_returnfloat@0")
#pragma comment(linker, "/EXPORT:returndouble=_returndouble@0")
#pragma comment(linker, "/EXPORT:returnstring=_returnstring@0")
#pragma comment(linker, "/EXPORT:whatis=_whatis@8")
The extended header file is the same as for the simple example in the first document. The change is that we have added more functions with all types of return values. There is a new function that takes input parameters. That then triggers the need to change the calling convention to make sure we use the same way of handling input parameters as the Windows SDK. That triggers more mangling of parameters names as we can see in the DLL Export Viewer:
The functions without any measures to remove the mangling. If you look at the last function, it have the @8 in the parameter, this means that this function takes two parameters as input (8/4=2 as each parameter takes 4 bytes of memory). The others have @0 and they takes no parameters.
To make PRISM able to use the functions, we need to get rid of these @X at the end of the names. The easiest way of doing that is to add a #pragma comment that tells the liker to add a new export with an alias to the first. To do that, simply use the DLL Export Viewer to see the names that is outputted by you function and add the name that you want to use for the CDF like this #pragma commant(linker, "/EXPORT:nameforcdf=_manglednameindll@X"). When you now compile your project and look in the DLL Export Viewer, you will see that a new set of you function is added in a way PRISM is able to find them.
The unmangled versions of the functions together with the mangled.
// examplecdf.cpp : Defines the exported functions for the DLL application.
// The return buffer that is used by all methods that need to return a string
// This is an example of an exported function that returns int
EXAMPLECDF_API int returnint(void)
// This is an example of an exported function that returns long
EXAMPLECDF_API long returnlong(void)
// This is an example of an exported function that returns a floating point
EXAMPLECDF_API float returnfloat(void)
// This is an example of an exported function that returns a double
EXAMPLECDF_API double returndouble(void)
// This is an example of an exported function that returns int
EXAMPLECDF_API LPCSTR returnstring(void)
// This example takes a string and an int and returns a string after the parameters are put together
EXAMPLECDF_API LPCSTR whatis(LPCSTR textin, int meaning)
The new cpp file with all the function including the whatis function that takes one string and one int as parameter in and return a string that combines the two.
Using the functions in DfW
First you need to add all the dll in your app folder. To simplify the process when I do develop CDF for use with DfW, I simply add my project and the DfW application in two subfolders in the same folder and changes the output directory of the compiled project to match the name of the DfW application folder. This means that you can do a change to your CDF and recompile, and then the change is ready for you when you reloads your DfW application. It will also help you later if you want to debug your CDF in the debugger while running inside DfW.
Changed output folder of the CDF project
The next step is to get all your CFDs loaded into the Custom Functions table. You can do that in several way, like manually entering in each of them, use the new CDF ini import or simply write a DQL doing it for you like I have done. Since I only want my own CDFs, I first run a clean up of all the ones that comes with a new DfW project, and then add my own.
-- only add this if you wnat to get rid of the other functions -- delete records in Custom Functions . enter a record in Custom Functions Function Name := "returnint" ; Description := "retun an int" ; Library Name := "examplecdf.dll" ; Rettype := Int . enter a record in Custom Functions Function Name := "returnfloat" ; Description := "retun a float" ; Library Name := "examplecdf.dll" ; Rettype := Float . enter a record in Custom Functions Function Name := "returndouble" ; Description := "retun a double" ; Library Name := "examplecdf.dll" ; Rettype := Double . enter a record in Custom Functions Function Name := "returnstring" ; Description := "retun a string" ; Library Name := "examplecdf.dll" ; Rettype := String . enter a record in Custom Functions Function Name := "whatis" ; Description := "retun a string" ; Library Name := "examplecdf.dll" ; Rettype := String ; Name1 := "textin" ; Type12 := String ; Name2 := "meaning" ; Type2 := Int .
The dql to import my CDFs. The name of the first paramter is Type12 (a bug from the early days, that now is stuck), but the rest of the fields a sequenced properly.
You should now your CDFs functions registered in Custom Functions
The last step now is to test your new functions in a form. To do this I have added to virtual editable field that requires data and that have a default value for holding my parameters and a virtual field to call the function.
The field definitions
Her is the result