Notwithstanding, there are times when I feel limited with VFP. One of the great limitations I find in it is the lack of low-level manipulation I can obtain from it. For many, this might not be an inconvenience, but several times I have encountered a problem which can't be solved with Fox, and it is then that I use a DLL or an FLL. This is what we will try to do today.
To develop a library of this type, we will have to use Visual C++. In my case, I will use VC++ 7.0 in Spanish, but it can be done, without any limitation, with VC++ 6.0.
A little bit of history
Since Fox version 2.0, in the DOS environment, this language has the possibility of using PLB libraries, and in Windows, FLL libraries, With these libraries, the Fox can be powered to really high limits, up to the point where you can develop any type of low-level routine which would be practically impossible otherwise.
Objectives
I consider the following points to be the objectives of this article:
FLL vs. DLL
An FLL is basically a DLL. However, it exposes two structures of its own ("FoxInfo" and "FoxTable") where it specifies the functions it exports, and that can be used from VFP. The advantage of an FLL over a DLL is that it can easily manage native Fox data, as if we were within a method of a VFP class; a disadvantage is that the library can only be used from VFP, unlike a DLL that can be used from any development tool that is capable of using the Win32 API.
On the other hand, as we said, the FLL can manage cursors and tables easily, and even in a manner similar to the Fox; but it can also send native Fox error messages to the calling application, and even use the API of VFP itself.
The FoxInfo structure is used to communicate VFP with the FLL functions. With this structure, VFP determines the name of the function, and the number and type of its parameters. The FoxTable structure is a linked list that follows the FoxInfo structures, but we will analyze it later.
Developing an FLL
To develop an FLL we will use, as I had already stated, Visual C++, but first we need sume utilities that VFP itself already provides us. These utilities are simply two files:
These files are located in the API directory, in the VFP installation. They participate in our project and have to be copied into the directory where it is created.
Creating the FLL/DLL project:
Now, you have to add a new file, so that you can start working on it.
With these simple steps, we already have a DLL project made with VC++, but now you must modify it so that it respects the FLL structure, so that it can be read from Fox.
Figure 1: Creating the project
Figure 2: Creating the file
But before we start programming, we must analyze the FoxInfo and FoxTable structures, to see how they work, so that when we start coding, they don't seem strange to us.
FoxInfo Structure
FoxInfo is the structure that is used as a vehicle to communicate the library's internal functions, and their parameter descriptions, between it and Visual FoxPro. A generic FoxInfo structure looks like this:
FoxInfo arrayname[ ] = { {funcName1, FPFI function1, parmCount1, parmTypes1} {funcName2, FPFI function2, parmCount2, parmTypes2} {funcNameN, FPFI functionN, parmCountN, parmTypesN} };
Variable of type FoxInfo, that contains the functions exposed to VFP, the pointers to the library's internal functions and the number of parameters received by the function.
Contains the name of the function exposed to VFP. With this name, the user can invoke the function from Visual FoxPro.
Pointer to the library function. This is the function's name within the library. It has to be written carefully, for C++ distinguishes between uppercase and lowercase.
Specifies the number of parameters that the function can receive, or any of the following constants:
Specifies the data type of each parameter. The valid data types are the following:
You have to include as many types of parameters as the function receives. For example, if you have a function that receives a logical value and a date value, you have to specify parmTypes as "LD". If a parameter is optional, you have to prefix it with a period; however, only the final parameters can be omitted.
FoxTable Structure
The FoxTable structure is a list that keeps information of all the FoxInfo structures that it has for a certain library:
FoxTable _FoxTable = {nextLibrary, infoCount,infoPtr};
Pointer used internally by Visual FoxPro; it should be initialized to zero.
The number of external Visual FoxPro routines defiend in this library.
Address of the first element of an array of FoxInfo structures. This name must match the name listed in the FoxInfo instruction.
FoxInfo myFoxInfo[] = { {"MyFunction", (FPFI) InternalName, 0, ""}, }; extern "C" { FoxTable _FoxTable = { (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo };
Adapting the DLL to an FLL
As a first step, to achieve this, we use the two files which VFP provides, copying them to the directory where the project was created.
Next, we have to configure the project. There are four steps that we have to carry out in the configuration:
First, we will access the configuration. For this purpose, we start through the menu "Project\Properties", next we select the option "C/C++", and from here, "Code generation". At this point, we remain on "Runtime library", and select, from the list, "
Multiprocess DLL (/MD)".
Figure 3: Project configuration
As a second step, we have to select the option "Advanced", and remain on "Calling convention"; from the list, we select "__fastcall (/Gr)"
Figure 4: Calling convention
As a third step, we have to define the output file. We do this selecting the option "Links", and within this, "General". Here, we select "Result file", and modify the default name; for our case, we would change "BasicFll.dll" to "BasicFll.fll". We do this so that we don't have to rename the file after compiling it.
Figure 5: Output file
The fourth and last step is to add one of the files which Fox provides: "winapims.lib". We do this selecting the option "Entry", we stay on "Additional dependencies" and select the file "winapims.lib".
Figure 6: Linking winapims.lib
Remember that you have to do these configuration steps for every possible compilation mode associated with the project, "Release", "Debug", etc.
With the files in place and the project configured, we can start to program. We will do so on the basis of the blank file we created, defining the basic structure which is required so that the DLL can be recognized as an FLL.
#include "pro_ext.h" void FAR OnInit(ParamBlk FAR *parm) { MessageBox( NULL, "OnInit", "BasicFll", MB_OK); } void FAR OnDestroy(ParamBlk FAR *parm) { MessageBox( NULL, "OnDestroy", "BasicFll", MB_OK); } FoxInfo myFoxInfo[] = { {"Init", (FPFI) OnInit, CALLONLOAD, ""}, {"Destroy", (FPFI) OnDestroy, CALLONUNLOAD, ""}, }; extern "C" { FoxTable _FoxTable = { (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo }; }
With these steps, we have developed the basic structure of our FLL. Now, we can press "F7" if we have our .NET profile configured as "Visual C++ 6", or we can access the command through the menu "Generate\Generate BasicFll".
Now we only have to use the library from VFP; for this purpose, we use the command SET LIBRARY.
Set Library To "BasicFll.fll"
However, if we use the library, we will encounter an error message like the following:
Figure 7: No functions in the FLL
The reason is that we haven't exposed any function in our library. Therefore, before we can use it from VFP, we have to define and export a function so that it can be used. We will do this with a simple function that creates a file on drive "C:" in the machine, and saves a message.
For this purpose, we have to modify the FoxInfo structure, adding the new function, and then declare it. It will look like this:
FoxInfo myFoxInfo[] = { {"Init", (FPFI) OnInit, CALLONLOAD, ""}, {"Destroy", (FPFI) OnDestroy, CALLONUNLOAD, ""}, {"CreateFile", (FPFI) CreateFile, 0, ""}, }; void FAR CreateFile(ParamBlk FAR *parm) { FILE *fo; if( (fo = fopen( "C:\\BasicFll.txt", "a+")) != NULL ) { char msg[] = "I have been called from VFP\r\n"; fwrite( msg, sizeof( char ), strlen(msg), fo ); fclose( fo ); _RetInt(1,10); // Successful exit.- } else _RetInt(0,10); // Exit with error.- }
Figure 8: Loading and unloading the FLL
#include #include "pro_ext.h"
Once the function is loaded, we can call our function so that it creates the file in case it doesn't exist, and then save our nice message.
? CreateFile()
We hereby fulfill one of our objectives, which is to create our first basic FLL, so that we can understand how it works and how it is interpreted by VFP. So, if you haven't yet fled in fright, we can continue with this article.
Passing and receiving parameters
VFP can transfer parameters to the FLL by value or by reference. By default, it will respect what is specified in the command SET UDFPARMS, but you can force a parameter to be passed by reference, with the prefix "@", or force the value to be passed by value, enclosing it in parentheses.
In the library, type numer and type of parameters are defined in the FoxInfo structure, but the real function defined in the library only receives a parameter of type ParmBlk, which is a pointer to the parameter block received from VFP. In the following example, we can see how the function "CreateFile" is defined in the FoxInfo structure; this function receives two parameters, but the real function only receives one, a pointer to a parameter block.
FoxInfo myFoxInfo[] = { {"CreateFile", (FPFI) CreateFile, 2, "CC"}, }; void FAR CreateFile(ParamBlk FAR *parm) { }
// A paramter list to a library function. typedef struct { short pCount; /* Number of Parameters PASSED.*/ FoxParameter p[1]; /* pCount Parameters.*/ } ParamBlk;
The structures are defined in the file Pro_ext.h with the following properties:
/* A parameter to a library function. */ typedef union { Value val; Locator loc; } Parameter // An expression's value. Typedef struct { char ev_type; char ev_padding; short ev_width;
unsigned ev_length; long ev_long; double ev_real; CCY ev_currency; MHANDLE ev_handle; ULONG ev_object; } Value; typedef struct { char l_type; short l_where, /* Database number or -1 for memory */ l_NTI, /* Variable name table offset*/ l_offset, /* Index into database*/ l_subs, /* # subscripts specified 0 <= x <= 2 */ l_sub1, l_sub2; /* subscript integral values */ } Locator;
The following table is also from the VFP help, and shows what types of values the structure Locator can receive when a parameter is passed by reference from VFP.
Now, after getting a feel of the structures, we realice that the function "CreateFile" is responsible of managing the parameters; it will have to access the parameters one position as a time, as with an array.
To try this out, we create a new project called FllParam. The way it is created and configured is the same as we already saw, to be able to pass it parameters and receive values.
#include #include "pro_ext.h" char FAR *NullTerminate(Value FAR *cVal) { char *NewChar; if (!_SetHandSize(cVal->ev_handle, cVal->ev_length + 1)) _Error(182); ((char FAR *) _HandToPtr(cVal->ev_handle))[cVal->ev_length] = '\0'; NewChar = (char FAR *) _HandToPtr(cVal->ev_handle); return NewChar; } void FAR CrearArchivo(ParamBlk FAR *parm) { char FAR *sFileName = NullTerminate(&parm->p[0].val); char FAR *sMsg = NullTerminate(&parm->p[1].val); FILE *fo; if( (fo = fopen( sFileName, "a+")) != NULL ) { fwrite( sMsg, sizeof( char ), strlen(sMsg), fo ); fclose( fo ); _RetInt(1,10); // Exit successfully } else _RetInt(0,10); // Exit with error. }
FoxInfo myFoxInfo[] = { {"CreateFile", (FPFI) CreateFile, 2, "CC"}, }; extern "C" { FoxTable _FoxTable = { (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo }; }
If we run the following instructions from VFP, we can see that now we can pass the parameters we define, and that they really do what we intended.
set library to "FllParam.fll" ? CreateFile("C:\LogRoberto.txt", "Ianni - Message from VFP (With parameters)") set library to
Now, we want to analyze the changes made in more detail. You can see that the parameters are in the array "parm->p", received as parameter, as we have seen, but when we assign the parameter value to our local variables, we do it through an internal function called NullTerminate.
char FAR *sFileName = NullTerminate(&parm->p[0].val);
_SetHandSize: Used to change the amound of memory assigned to the memory block controller. This is similar to redimensioning the size of the variable.
_HandToPtr: Converts a VFP memory controller to a FAR (32-bit) pointer, that points to the memory assigned to this controller. In summary, a pointer to the variable.
_Error: Indicates the error specified by the value 'code', when Visual FoxPro executes.
char FAR *NullTerminate(Value FAR *cVal) { char *NewChar; if (!_SetHandSize(cVal->ev_handle, cVal->ev_length + 1)) _Error(182); ((char FAR *) _HandToPtr(cVal->ev_handle))[cVal->ev_length] = '\0'; NewChar = (char FAR *) _HandToPtr(cVal->ev_handle); return NewChar; }
What our function does, then, is the following: first, redimension the string received as a parameter, then, add an additional character, which will be used to place the string terminator required by C++. Then, it uses the function _HandtoPtr to know which is the initial memory position used by the string; on the basis of this position, it goes to the right as many bytes as the redimensioned string has, and in this position, it marks the end of the string. Finally, it uses the function _HandToPtr to obtain the starting position of the string, assigning it to a variable and returning it to the calling function.
In this function we also see that if we don't redimension the variable, we will return to VFP with an error indicating insufficient memory.
We have thus resolved the problem of passing parameters by value from VFP to the function, but we still have to resolve the problem of passing by reference; that is, pass a value, have the function modify it, and return it, modified, to VFP. Let's remember that for this purpose we have the assistance of the Locator structure, in the parameters. Let's see an example.
void FAR MiniProper(ParamBlk FAR *parm) { char FAR *pString; Value val; _Load(&parm->p[0].loc, &val); // Verify if it is a character if (val.ev_type != 'C') _Error(9); // "Data type mismatch" pString = (char*)_HandToPtr(val.ev_handle); if( val.ev_length > 0 ) if ('a' <= *pString && *pString <= 'z') pString[0] = pString[0] - 32; _Store(&parm->p[0].loc, &val); _FreeHand(val.ev_handle); } FoxInfo myFoxInfo[] = { {"MiniProper", (FPFI) MiniProper, 1, "R"}, }; extern "C" { FoxTable _FoxTable = { (FoxTable FAR *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo }; }
The first one is its declaration in the FoxInfo structure. Here, we have to define the parameter type as "R".
{"MiniProper", (FPFI) MiniProper, 1, "R"},
The function _Load copies the value of the variable passed by reference to a variable of type "Value", so that we can work with it in the normal manner.
Next, function _Store does the opposite of _Load. It copies the value of a variable of type "Value" to a reference variable. Finally, the function _FreeHand frees a memory block.
Now that we know what these functions do, the code block is quite simple in the way it works. If we observe a little more, we will see that what the function does is replace the first letter of the string by the same letter in uppercase, the equivalent of the Proper() function in VFP, but only with the first letter.
Var = "Ianni, Roberto" ? MiniProper( @Var ) ? Var && Ianni, Roberto
Returning parameters from the function
As we have seen up until now, in our examples, the function CreateFile() returns an integer value (1 or 0) to VFP, indicating the the function finished its work correctly, or not. But this is not the only the only type of value an FLL can return. It can return values of type string, float, logical, etc.
To return these values, we can't use the native C or C++ commands; instead, we have to make use of the VFP API functions. Here is a list of functions to use:
Once we know these details, we can create a few small functions that return this type of information, to see how they would be coded. For this purpose, we create a single project called FllRetVals, that returns this data type based on the examples in the VFP manuals.
#include "pro_ext.h" #include void FAR ReturnChar(ParamBlk FAR *parm) { char message[] = "Hello, I am Roberto"; _RetChar(message); } void FAR ReturnCurrency(ParamBlk FAR *parm) { CCY money; money.HighPart = (long) (parm->p[0].val.ev_real*10000 / pow(2,32)); money.LowPart = (unsigned long) ((parm->p[0].val.ev_real - (double) (money.HighPart * pow(2,32)/10000.0)) *10000); _RetCurrency(money,25); }
void FAR ReturnDate(ParamBlk FAR *parm) { MHANDLE mh; char FAR *instring; if ((mh = _AllocHand(parm->p[0].val.ev_length + 1)) == 0) { _Error(182); // "Insufficient memory" } _HLock(parm->p[0].val.ev_handle); instring = (char*)_HandToPtr(parm->p[0].val.ev_handle); instring[parm->p[0].val.ev_length] = '\0'; _RetDateStr(instring); _HUnLock(parm->p[0].val.ev_handle); } void FAR ReturnDateTime(ParamBlk FAR *parm) { MHANDLE mh; char FAR *instring; if ((mh = _AllocHand(parm->p[0].val.ev_length + 1)) == 0) { _Error(182); // "Insufficient memory" } _HLock(parm->p[0].val.ev_handle); instring = (char*)_HandToPtr(parm->p[0].val.ev_handle); instring[parm->p[0].val.ev_length] = '\0'; _RetDateTimeStr(instring); _HUnLock(parm->p[0].val.ev_handle); } void FAR ReturnFloat(ParamBlk FAR *parm) { _RetFloat(parm->p[0].val.ev_real, 20, 4); } void FAR ReturnInt(ParamBlk FAR *parm) { _RetInt(rand(),10); } void FAR ReturnLogical(ParamBlk FAR *parm) { _RetLogical(rand() % 2); } FoxInfo myFoxInfo[] = { {"ReturnChar", (FPFI) ReturnChar, 0, ""}, {"ReturnCurrency", (FPFI) ReturnCurrency, 1, "N"}, {"ReturnDate", (FPFI) ReturnDate, 1, "C"}, {"ReturnDateTime", (FPFI) ReturnDateTime, 1, "C"}, {"ReturnFloat", (FPFI) ReturnFloat, 1, "D"}, {"ReturnInt", (FPFI) ReturnInt, 0, ""}, {"ReturnLogical", (FPFI) ReturnLogical, 0, ""}, }; extern "C" { FoxTable _FoxTable = { (FoxTable FAR *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo }; }
SET LIBRARY TO FllRetVals.fll ? ReturnChar() && Hello, I am Roberto ? ReturnCurrency(200.1) && 200.1000 ? ReturnDate("06/06/04") && 04/06/04 ? ReturnDateTime("06/06/04 12:07am") && 06/06/04 12:07AM ? ReturnFloat({06/06/04}) && 2416638.0000 ? ReturnInt() && 1..2..2..9 ? ReturnLogical() && .T. .T. .F. .F. .F. .T. .T. .F. SET LIBRARY TO
Conclusion
You see that developing an FLL isn't complex, you only have to know some C++ and know what it is you want to do.
At this point, we should have all the concepts clear, about how to develop an FLL, and how to interact with it, from VFP. In the following article, we will explain some advanced techniques for programming an FLL that allows multiple threads, access to Fox data, and other things.
Source Code