第二十八章:访问 Visual FoxPro API

如果 Visual FoxPro 中没有包含您的应用程序需要的功能,可以对其进行扩展,使用一个 32 位编译器,例如 Microsoft Visual C++® 4.0 或更高版本,创建一个 ActiveX 控件(.ocx 文件)或专用于 Visual FoxPro 的库文件(.fll 文件)。本章将讨论这两种程序。

注释 如果使用 Visual C++ version 2.x 来开发 ActiveX 控件,需要 Control Development Kit。本章假定您使用的是 Visual C++ 4.0 版本。

有关使用 ActiveX 控件或 FLL 的详细内容,请参阅第二十七章“使用外部库扩展 Visual FoxPro 的功能”

本章内容要点∶

创建一个库或者 ActiveX 控件

可以扩展 Visual FoxPro 的功能,方法是用 C 或 C++ 创建程序,此程序能完成您的应用程要求的任务。例如,如果应用程序需要直接访问 Windows 工具,您可以编写一个 C 或 C++ 程序调用 Windows API,然后向 Visual FoxPro 返回信息。

若要访问 Visual FoxPro API,可以创建三种类型的程序∶

每种类型的程序都各有优点。ActiveX 控件的优点∶

COM 对象的优点:

另一方面,一个 .fll 库∶

创建了一个基本的 ActiveX 对象

可以使用 Microsoft Visual C++ 5.0 提供的“ActiveX Template Library”创建 COM 对象。有关使用 Visual C++ 5.0 创建 COM 对象的详细内容,请在 MSDN 库中搜索“ATL”。

与其他类似控件一样,您也可以创建专用于 Visual FoxPro 的 ActiveX 控件。大多数 C++ 编译器允许创建该控件的框架,也可以用 Microsoft Visual Basic® Control Creation 5.0 版创建。

以下章节将介绍使用 Microsoft Visual C++ 创建用于 Visual FoxPro 中的 ActiveX 控件的步骤。

若要为 ActiveX 控件创建一个项目

  1. 启动 Microsoft Visual C++。

  2. 在“File”菜单中,选择“New”。

  3. 在“New”对话框中选择“Project Workspace”。

  4. 在“New Project Workspace”对话框中指定项目的名称。

  5. 在“Type”列表中选择“OLE ControlWizard”。

  6. 选择“Create”,然后按照向导的步骤操作。

向导结束时,您可以立即生成 ActiveX 控件。但是,还需要定义该控件的属性和方法程序。

若要为 ActiveX 控件添加属性和方法程序

  1. 在“View”菜单中选择“ClassWizard”。

  2. 选择“OLEAutomation”选项卡。

  3. 选择“Add Method”或者“Add Property”。

  4. 输入创建组件所需的名称、参数和其他信息,然后选择“OK”。

  5. 选择“Edit Code”显示编辑器,然后输入定义属性或创建的方法程序的代码。

例如,若要创建一个 Version 属性,以整数形式返回一个 .ocx 文件的版本号(例如 101),您可以使用返回的长整型创建该属性,并添加如下的代码∶

#define VERSION 101

long CPyCtrl::GetVersion()
{
   // 在此设置版本号
   return VERSION;
}

由于通常版本号是只读的,您不需创建 SetVersion( ) 函数。

创建一个基本的 FLL 库

实际上,FLL 库就是调用 Visual FoxPro API 的 DLL,因此您可以在开发环境中根据以下创建 DLL 的步骤创建一个 FLL 库。

若要为 FLL 库创建一个项目

  1. 启动 Microsoft Visual C/C++。

  2. 在“File”菜单中,选择“New”。

  3. 在“New”对话框中选择“Project Workspace”。

  4. 在“Project Workspace”对话框中指定项目的名称。

  5. 在“Type”列表中选择“Dynamic-Link Library”。

在创建了 DLL 的基本结构以后,可以添加需要从 Visual FoxPro 中调用的函数。下面几节简单介绍使用 C 和 C++ 创建函数的方法。

建立一个库模板

要创建的每个函数库都有相同的基本结构。借助库结构模板,只需填充适用于您的库例程的部分即可。

Visual FoxPro 库模板有五个要素:

  1. #include 语句

  2. 函数定义

  3. 函数代码

  4. FoxInfo 结构

  5. FoxTable 结构

C 模板示例

可用下列模板创建 C 语言编写的库。

#include <Pro_exh.h>

void Internal_Name(ParamBlk *parm)
{
// 该处填写函数代码。
}

FoxInfo myFoxInfo[] = {
   {"FUNC_NAME", (FPFI) Internal_Name, 0, ""},
};

FoxTable _FoxTable = {
   (FoxTable *)0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
};

C++ 模板示例

对于 C++ 例程,可用下列模板。与 C 语言模板不同的是它把 FoxTable 结构声明为外部的。

#include <Pro_exh.h>

void Internal_Name(ParamBlk  *parm)
{
// 该处填写函数代码。
}

   FoxInfo myFoxInfo[] = {
      {"FUNC_NAME", (FPFI) Internal_Name, 0, ""},
   };

extern "C" {
   FoxTable _FoxTable = {
      (FoxTable *)0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
   };
}

使用模板

若要使用头文件并创建一个已编译库,需要∶

在安装 Visual FoxPro 时,这两个文件都安装在 API 子目录下。

函数定义返回 void,并需要下列参数:ParamBlk *parmParamBlk 结构将在本章稍后的“传递和接收参数”中讨论。

除了以上文件之外,Visual FoxPro 库还有其他两个必需的要素是 FoxInfo 和 FoxTable 结构。

使用 FoxInfo 和 FoxTable 结构

库函数通过 FoxInfo 结构与 Visual FoxPro 进行通信。Visual FoxPro 可以通过这个结构确定函数名、参数的个数和类型。FoxTable 结构是一个链接的列表,保持对 FoxInfo 结构的跟踪。有关 FoxInfo 和 FoxTable 结构的定义,请参阅 Visual FoxPro 在 API 目录中的 Pro_ext.h 文件。

FoxInfo 结构

FoxInfo 结构用于在 Visual FoxPro 和库之间传递函数名和参数描述信息。一个通用的 FoxInfo 结构如下所示:

FoxInfo arrayname[ ] = {
{funcName1, FPFI function1, parmCount1, parmTypes1}
{funcName2, FPFI function2, parmCount2, parmTypes2}
. . .
{funcNameN, FPFI functionN, parmCountN, parmTypesN}
};

其中的各个占位符定义如下:

arrayname

一个数据类型为 FoxInfo 的变量。注意在该数组中可以包含多个 FoxInfo 结构行。

funcName

供 Visual FoxPro 用户调用时使用的函数名称(大写字母并且不得超过 10 个字符)。

function

您的 C 语言例程的地址。这是函数的真正名称(区分大小写)。

parmCount

指定在 parmTypes 字符串中描述的参数个数,或者下列标志值之一。

说明
INTERNAL 表示该函数不能直接从 Visual FoxPro 调用。
CALLONLOAD 表示在加载库时调用例程。CALLONLOAD 不能调用那些返回结果给 Visual FoxPro 的函数。
CALLONUNLOAD 表示该例程在卸载库时,或者发出 Visual FoxPro 的 QUIT 命令时被调用。CALLONUNLOAD 不能调用那些返回结果给 Visual FoxPro 的函数。

parmTypes

描述每个参数的数据类型。下表列出 parmTypes 的有效值。

说明
""
无参数
"?"
能传递任意类型。在函数体中,必须检查传递过来的参数类型。
"C"
字符型参数
"D"
日期型参数
"I"
整型参数
"L"
逻辑型参数
"N"
数值型参数
"R"
引用
"T"
日期时间型参数
"Y"
货币型参数
"O"
对象类型参数

应该为每个传递给库的参数指定一个类型值。例如,若创建的函数接受一个字符和一个数值参数,那么可用“CN”作为 parmType

注释 若要标明一个参数是可选的,请在前面加上一个句点“.”。只有后面的参数才能省略。

下列 FoxInfo 结构定义了只有一个函数的库,内部叫“dates”,外部用“DATES”来访问,并且接受一个字符类型的参数:

FoxInfo myFoxInfo[] = {
   { "DATES", (FPFI) dates, 1, "C" }
};

编译具有这个 FoxInfo 结构的库,并且在 Visual FoxPro 中使用 SET LIBRARY TO 命令加载该库,此后便可在 FoxPro 中用下列代码行来调用该函数:

=DATES("01/01/95")

FoxTable 结构

FoxTable 是一个链接的列表结构,保持对给定库中所有 FoxInfo 结构的跟踪:

FoxTable _FoxTable = {nextLibrary, infoCount,infoPtr};

其中各占位符定义如下:

nextLibrary

Visual FoxPro 内部使用的一个指针,必须初始化为 0。

infoCount

在本库中定义的 Visual FoxPro 外部例程数目。

infoPtr

FoxInfo 结构中第一个数组元素的地址。这个名称必须与 FoxInfo 语句中列出的数组名相匹配。

下面是 FoxTable 语句的一个示例。如果 FoxInfo 数组名是 myFoxInfo,那么您不必更改这个语句,直接就可以使用。

FoxTable _FoxTable = {
   (FoxTable  *) 0,
   sizeof( myFoxInfo) / sizeof( FoxInfo ),
   myFoxInfo
};

添加 Visual FoxPro API 调用

为了将程序与 Visual FoxPro 集成,可以调用 Visual FoxPro API 例程。这些 API 例程是可以从任何 C 或 C++ 程序中调用的函数,包括 .ocx 或 .fll 文件,它们使您可以访问变量,管理数据库操作,并且完成很多其他 Visual FoxPro 特定的任务。

下表列出了 Visual FoxPro 中可用的 API 调用的常用分类。有关每个 API 函数的详细内容,请参阅“API 库例程 A-Z”“API 库例程的分类”

若要使用 Visual FoxPro API 例程,必须包含 Visual FoxPro API 目录下的 Pro_ext.h 文件。该文件包含了允许您与 Visual FoxPro 共享信息的函数和结构的原型。

如果想编写一个 ActiveX 控件,也必须添加调用以初始化 API 和清除 API。

若要向自己的 ActiveX 对象添加 Visual FoxPro API 例程

  1. 使用 #INCLUDE 包含 Pro_ext.h 文件,以及其他必需的头文件。

  2. 在控件的 Constructor (Init) 方法程序中,使用如下代码调用 _OCXAPI( ) 来初始化对于 Visual FoxPro 的接口程序∶
    _OCXAPI(AfxGetInstanceHandle(),DLL_PROCESS_ATTACH);
    
  3. 如果需要,可以在对象中包含对 Visual FoxPro API 的调用。

  4. 在对象的 Destructor (Destroy) 方法程序中,使用如下代码再次调用 _OCXAPI( ),来释放在 Constructor 中创建的过程∶
    _OCXAPI(AfxGetInstanceHandle(),DLL_PROCESS_DETACH);
    

有关包含调用 Visual FoxPro API 的 .ocx 文件的示例,请参阅 Foxtlib.ocx。有关包含调用 Visual FoxPro API 的 .fll 库的示例,请参阅 Vfp98\Api\Samples 目录下具有 C 扩展名的示例程序∶ EVENT.C,HELLO.C 等等。

如果在您的 ActiveX 控件、COM 对象或FLL 库中调用 Visual FoxPro API ,则包含这个调用的代码与其他应用程序是不兼容的。因此,需要在程序中进行或多或少的测试,来确定该对象是否从 Visual FoxPro 中调用。

例如,如果使用 Microsoft Foundation Classes 创建了一个 ActiveX 控件,您可以改变该控件的结构代码来包含一个测试,如果一个不是 Visual FoxPro 的程序调用了该控件,则警告用户∶

if (!_OCXAPI(AfxGetInstanceHandle(),DLL_PROCESS_ATTACH))
{
   ::MessageBox(0,"This OCX can only be hosted by Visual Foxpro","",0);
      // 当宿主程序(使用程序)不是 VFP 时,此处您可以书写需要的程序:
      // 您可能拒绝载入或
      // 可能需要设置属性
      // 说明宿主程序不是 VFP,控件将使用其他方式
      // 来达到目的。
}

如果您使用 Microsoft ActiveX Template Library 创建 ActiveX 控件,请使用下列代码:

if (!_OCXAPI(_Module.GetModuleInstance(),DLL_PROCESS_ATTACH))
{
   ::MessageBox(0,"This OCX can only be hosted by Visual Foxpro","",0);
      // 当宿主程序(使用程序)不是 VFP 时,此处您可以书写需要的程序:
      // 您可能拒绝载入或
      // 可能需要设置属性
      // 说明宿主程序不是 VFP,控件将使用其他方式
      // 来达到目的。
}

在本示例中,控件并不退出,而是在通知用户后继续运行。您选择的方法取决于您想怎样使用该控件。例如,如果检测到该控件正在 Visual FoxPro 之外被使用,可以先设置一个标志,然后在控件中每次调用 Visual FoxPro API 的地方测试这个标志。如果标志表明该控件正在 Visual FoxPro 之外运行,可以在调用 API 前利用条件分支去选择其它方式完成相同的任务。

传递和接收参数

从 Visual FoxPro 中调用您的程序时,它可以接收参数。例如,当调用其中的一个方法程序时,一个 ActiveX 控件可以接收参数。类似地,一个 Visual FoxPro 程序可以调用您的 FLL 库中的一个函数,并且向它传递参数。

Visual FoxPro 可以按值或按引用向您的应用程序传递参数。在默认情况下,参数遵守 SET UDFPARMS 的设置。其他变量(例如数组或字段)和表达式按值传递。

若要想使一个参数强制按引用传递,请在变量引用前加上 @ 操作符。要想使一个参数强制按值传递,请将它放在括号里。

注释 在 Visual FoxPro 中,单个的数组元素都是按值传递的。当 SET UDFPARMS 设置为 VALUE,并且没有指定数组元素时,数组名本身就指向了数组的第一个元素(除非有前缀 @)。

由于 ActiveX 控件和 COM 对象是标准的 Windows 程序,所以从 Visual FoxPro 和您的程序传递参数不需特别的机制。可以象从任何 C 或 C++ 程序接收参数那样编写程序。

相反,FLL 库中的函数使用 FoxInfo 结构来接收来自 Visual FoxPro 的数据。FoxInfo 结构列出了库函数以及它们所需参数的个数和类型。例如,下面的 FoxInfo 结构属于一个库,该库中包含了一个函数,这个函数内部称之为 dates,该函数接收一个字符型参数∶

FoxInfo myFoxInfo[] = {
   { "DATES", (FPFI) dates, 1, "C" }
};

在您的库里定义的函数实际上只接收了一个参数,也就是参数块的指针。这个参数块是在 ParamBlk 结构中定义的,它包含了从 Visual FoxPro 函数调用中传递来的所有参数信息。函数声明采用如下方式∶

void function_name(ParamBlk *parm)

例如,dates 函数的定义是∶

void dates(ParamBlk *parm)

ParamBlk 结构包含了一个整数,它代表了参数的个数,在它后面紧接着一个参数联合数组。该结构定义包含在 Pro_ext.h 中∶

/* 库函数的参数列表。      */
typedef struct {
   short int pCount;      /* 传递的参数个数 */
   Parameter p[1];         /* pCount 个参数 */
} ParamBlk;

ParamBlk 结构中包含的 Parameter typedef 是一个 Value 结构和 Locator 结构的联合。按值传递时按 Value 结构处理;按引用传递时,则按 Locator 结构处理。在 Visual FoxPro 中调用您的函数时,使用这些结构来访问传递给函数的参数。

下面的信息是从 Pro_exh.h 文件中提取出来的,显示了 Parameter 类型的定义:

/* 给库函数的参数。         */
typedef union {
   Value val;
   Locator loc;
} Parameter;

Value 结构的定义

如果按值向您的函数传递一个参数,则使用 Value 结构来访问这个参数。下面的 Value 结构定义是从 Pro_ext.h 文件中提取出来的∶

// 一个表达式的值。
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;

Value 结构的域

下表说明了对于不同的数据类型,您可以在 Value 结构中传递和接收值。只有数据类型后面所列的结构域才能用于这种数据类型。

不同数据类型的 Value 结构内容

数据类型 结构域
字符型
ev_type
‘C’
ev_length
字符串长度
ev_handle
指向字符串的 MHANDLE
数值型
ev_type
‘N’
ev_width
显示宽度
ev_length
小数位
ev_real
双精度
整型
ev_type
‘I’
ev_width
显示宽度
ev_long
长整型
日期型
ev_type
‘D’
ev_real
日期1
日期时间型
ev_type
‘T’
ev_real
日期 + (秒/86400.0)
货币型
ev_type
‘Y’
ev_width
显示宽度
ev_currency
货币值2
逻辑型
ev_type
‘L’
ev_length
0 或 1
备注型
ev_type
‘M’
ev_wdith
FCHAN
ev_long
备注字段的长度
ev_real
备注字段的偏移量
通用型
ev_type
‘G’
ev_wdith
FCHAN
ev_long
通用字段的长度
ev_real
通用字段的偏移量
对象型
ev_type
‘O’
ev_object
对象识别符
Null
ev_type
‘0’
ev_long
数据类型

1 日期以双精度浮点公历算法表示,使用“Collected Algorithms of the ACM”的“算法 199”来计算。
2 货币值为长整型值,在最后四位数字前有一个隐含的小数点。

注释 ev_length 是字符串长度的真正指示器。字符串不能以 null 作为终止符,因为字符串可以包含内嵌的 null 字符。

Locator 结构的定义

使用 Locator 结构可以处理按引用传递的参数。下列的 Locator 结构定义是从 Pro_ext.h 文件中抽取出来的。

typedef struct {
  char  l_type;
  short l_where, /* 数据库编号,如果是内存变量则为 -1 */
  l_NTI,      /* 变量名称表的偏移量。*/
  l_offset,  /* 在数据库中的索引。*/
  l_subs,  /* # 指定的下标 0 <= x <= 2 */
  l_sub1, l_sub2; /* 下标整型值。 */
} Locator;

Locator 结构的域

下表说明了 Locator 结构中的域。

Locator 域 域的用途
l_type 'R'
l_where 包含此域的表的编号,-1 代表一个内存变量。
l_NTI 名称表索引。Visual FoxPro 内部使用。
l_offset 表中域的编号。Visual FoxPro 内部使用。
l_subs 只用于内存变量,下标的数目 (0 - 2)。
l_sub1 只用于内存变量,若 l_subs 非 0,则代表第一个下标。
l_sub2 只用于内存变量,若 l_subs 为 2,则代表第二个下标。

注释 最好检查 ev_type 中的参数类型,因为这样可以确定要通过 Value 结构访问的域是否正确。

访问 FLL 库中参数的示例

下列示例使用 _StrCpy( ) 返回一个字符类型数据给 Visual FoxPro,该类型的数据是两个字符型参数的连接。必须注意,虽然对每个参数的 Value 结构使用内存句柄进行连接操作,但是对该内存分配的更改并不影响按值传递的 Visual FoxPro 参数。

有关使用 Locator 结构管理按引用传递的参数的示例,请参阅本章稍后的“从一个 FLL 库中返回值”

#include <Pro_exh.h>

Example(ParamBlk *parm)
{
// 使用 #define 可以使 paramBlk 结构易于管理
#define p0 (parm->p[0].val)
#define p1 (parm->p[1].val)

// 确保有足够的内存
if (!_SetHandSize(p0.ev_handle, p0.ev_length + p1.ev_length))
   _Error(182); // "内存不足"

// 锁定句柄
_HLock(p0.ev_handle);
_HLock(p1.ev_handle);

// 将句柄转换为指针并确保字符串是由 null 作终止符
((char *)_HandToPtr(p0.ev_handle))[p0.ev_length] = '\0';
((char *)_HandToPtr(p1.ev_handle))[p1.ev_length] = '\0';

//  API 函数 _StrCpy 连接字符串
_StrCpy((char *)_HandToPtr(p0.ev_handle) + p0.ev_length,
_HandToPtr(p1.ev_handle));

//  Visual FoxPro 返回已连接的字符串
_RetChar(_HandToPtr(p0.ev_handle));

// 解除句柄锁定
_HUnLock(p0.ev_handle);
_HUnLock(p1.ev_handle);
}

FoxInfo myFoxInfo[] = {
   {"STRCAT", Example, 2, "CC"},
};

FoxTable _FoxTable = {
   (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
};

返回值给 Visual FoxPro

用于从程序向 Visual FoxPro 返回值的方法,取决于创建的是 ActiveX 控件还是 FLL 库。

从一个 ActiveX 控件返回值

若要从一个 ActiveX 控件向 Visual FoxPro 返回值,请使用该控件的 RETURN 语句,返回单个的值,如下例所示∶

#define VERSION 101

// 此处是其他的代码

long CPyCtrl::GetVersion()
{
   // 在变量 fVersion 中设置版本号。
   return VERSION;
}

从一个 FLL 库中返回值

若要从一个 FLL 库中返回值,请使用 API 函数,而不要使用 C 或 C++ 本身的命令。下表所列的函数允许您向 Visual FoxPro 返回值。

注释 不要使用下列 API 函数从一个 OCX 文件返回值;请使用 RETURN 语句。API 返回的函数只能用于 FLL 库中。

函数 说明
_RetChar(char *string) 将函数的返回值设置为一个以 null 值结尾的字符串。
_RetCurrency(CCY cval, int width) 将函数的返回值设置为一个货币值。
_RetDateStr(char *string) 将函数的返回值设置为一个日期值。该日期按 mm/dd/yy[yy] 格式表示。
_RetDateTimeStr(char *string) 将函数的返回值设置为一个日期时间值。该日期时间按 mm/dd/yy[yy] hh:mm:ss 格式表示。
_RetFloat(double flt, int width, int dec) 将函数的返回值设置为一个浮点值。
_RetInt(long ival, int width) 将函数的返回值设置为一个整型值。
_RetLogical(int flag) 将函数的返回值设置为一个逻辑值。零被认为是 FALSE。任何非零值被认为是 TRUE。
_RetVal(Value *val) 传递一个完整的 Visual FoxPro Value 结构;除了备注字段,任何 Visual FoxPro 数据类型都可以返回。若要返回一个包含内嵌零字符的字符串,或者返回一个 .NULL. 值,您必须调用 _RetVal( )。

注释 若要返回一个对象数据类型的值,请使用 _RetVal() 函数,此函数将填充在 Value 结构的 ev_object 域中。

下面的示例 Sum 接收对表中一个数值字段的引用,并用 _RetFloat 返回字段值的总和:

#include <Pro_exh.h>

Sum(ParamBlk *parm)
{
// 声明变量
double tot = 0, rec_cnt;
int i = 0, workarea = -1; // -1 是当前工作区
Value val;

// 记录指针指向表头
_DBRewind(workarea);

// 获得记录数
rec_cnt = _DBRecCount(workarea);

// 循环遍历表
for(i = 0; i < rec_cnt; i++)
{ 
   //将字段的值放入 Value 结构
   _Load(&parm->p[0].loc, &val);

   // 求和
   tot += val.ev_real;

   // 指针指向工作区的下一个记录
   _DBSkip(workarea, 1);
} 

// 将总计值返回给 Visual FoxPro
_RetFloat(tot, 10, 4); 
}
// Sum 函数接收一个引用参数
FoxInfo myFoxInfo[] = {
   {"SUM", Sum, 1,"R"} 
};
FoxTable _FoxTable = {
   (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
};

假定在当前打开的表中有一个叫 amount 的数值型字段,可在 Visual FoxPro 程序中用下列代码调用该函数;

? SUM(@amount)

向 Visual FoxPro API 函数传递参数

Visual FoxPro API 例程经常需要特定 Visual FoxPro 数据结构的参数。下面几节提供了 Visual FoxPro 数据类型和附加数据结构的列表。有关类型和结构的实际定义,请参阅 Pro_ext.h 文件。

Visual FoxPro API 数据类型

下列数据类型可在 Visual FoxPro API 例程中使用。

数据类型 说明
EDLINE
用于编辑窗口中打开的文件,指明文件中某一行的行号,首行为 1。
EDPOS
用于编辑窗口中打开的文件,指明文件中某个字符的偏移位置。在文件或备注中,首字符的偏移位置为0。
FCHAN
文件通道。每个由 Visual FoxPro 打开的文件,或者通过 API 用 _FCreate( )_FOpen( ) 打开的文件,都分配一个 FCHAN。
FPFI
指向一个返回整数值的函数的 32 位指针。
ITEMID
为菜单中某一命令指定唯一标识。
MENUID
指定给一个菜单的唯一标识。
MHANDLE
一个唯一标识,每一个由 Visual FoxPro 分配的内存块,或通过 API 用 _AllocHand( ) 分配的内存块都具有这样一个标识。可以使用 _HandToPtr( ) 放弃对此指针的引用
NTI
命名表索引。每个内存变量和表字段的名称在该表中都有一项。
WHANDLE
窗口句柄。每一个由 Visual FoxPro 打开的窗口,或者通过 API 用 _WOpen( ) 打开的窗口都有这样一个唯一的标识。

注释 因为 FAR 指针对 32 位编译器不适用,请在 Pro_ext.h 文件中的 #define 语句中将 FAR_far__far 作为 null 值重新设置。

Visual FoxPro API 数据结构

下表列出了在 Visual FoxPro API 库中使用的主要数据结构。

结构 说明
EventRec 此结构用于描述在某个给定的时刻,系统正在进行的操作。
FoxInfo 在 FLL 库中使用,用于 Visual FoxPro 与应用程序之间的通讯,不能在 .ocx 文件中使用。已在本章前面“使用 FoxInfo 和 FoxTable 结构”中讨论过。
FoxTable 在 FLL 库中使用,用于 Visual FoxPro 与应用程序之间的通讯,不能在 .ocx 文件中使用。已在本章前面“使用 FoxInfo 和 FoxTable 结构”中讨论过。
Locator 用于访问参数值 (FLL)、Visual FoxPro 变量或字段(FLL 和 OCX)的结构。
ParamBlk 在 FLL 库中使用,用于 Visual FoxPro 与应用程序之间的通讯,不能在 .ocx 文件中使用。已在本章前面“使用 FoxInfo 和 FoxTable 结构”中讨论过。
Parameter 在 FLL 库中使用,用于 Visual FoxPro 与应用程序之间的通讯,不能在 .ocx 文件中使用。已在本章前面“使用 FoxInfo 和 FoxTable 结构”中讨论过。
Point 此结构定义了屏幕上一个点的水平和垂直坐标。坐标按行号和列号指定。
Rect 此结构定义了屏幕上一个矩形的坐标。该矩形的左上角由(top,left)定义,右下角由(bottom-1,right-1)定义。坐标按行号和列号指定。
Value 用于访问参数值 (FLL)、Visual FoxPro 变量或字段(FLL 和 OCX)的结构。

访问 Visual FoxPro 变量和字段

可以在您的 ActiveX 控件或 FLL 函数中访问 Visual FoxPro 变量或字段,进行读取或设置。另外,还可以创建可供 Visual FoxPro 访问的新变量。

变量和字段保存在 Visual FoxPro 的一个命名表中,它是一个数组,包含了所有当前定义的变量和字段的名称。可以使用命名表索引 (NTI) 来访问数组中的单个元素。有一个特殊的 API 函数,_NameTableIndex( ),根据您提供的名称返回一个已存在的变量和字段的索引。当确定了一个给定变量的 NTI 时,您可以使用 _Load( ) API 函数读取它,或者使用 _Store( ) 函数设置它。若要创建一个新变量,可以调用 API 函数 _NewVar( )

若要访问 Visual FoxPro 变量或字段,可以使用在 Pro_ext.h 中定义的 Value 和 Locator 结构。如果创建一个 FLL 库,您可以使用访问传递给函数的参数的相同技术。有关 Value 和 Locator 结构的详细内容,请参阅本章稍前的“传递和接收参数”

下面的示例是从 Vfp98\Api\Samples\Foxtlib 目录中的 Foxtlibctl.cpp 程序中提取的,它演示了在一个 ActiveX 控件中,如何使用 Value 和 Locator 结构来访问 Visual FoxPro 变量∶

long CFoxtlibCtrl::TLGetTypeAttr(long pTypeInfo, LPCTSTR szArrName)
{
  int nResult = 1;
  TYPEATTR *lpTypeAttr;
  Locator loc;
  Value val;
  OLECHAR szGuid[128];
  char *szBuff;
__try {
   if (_FindVar(_NameTableIndex(( char *)szArrName),-1,&loc)) {
      ((ITypeInfo *)pTypeInfo)->GetTypeAttr(&lpTypeAttr);
      if (_ALen(loc.l_NTI, AL_ELEMENTS) < 16) {
         _Error(631); //Array argument not of proper size.
      }

      //1 = Guid
      StringFromGUID2(lpTypeAttr->guid, (LPOLESTR )&szGuid,sizeof(szGuid));
      OLEOleToAnsiString(szGuid,&szBuff);
      val.ev_type = 'C';
      val.ev_length = strlen(szBuff);
      val.ev_handle = _AllocHand(val.ev_length);
      _HLock(val.ev_handle);
      _MemMove((char *) _HandToPtr( val.ev_handle ), szBuff, val.ev_length);
      OLEFreeString((void **)&szBuff);
      _HUnLock(val.ev_handle);
      loc.l_sub1 = 1;
      _Store(&loc,&val);
      _FreeHand(val.ev_handle);

      //2 = LCID
      loc.l_sub1 = 2;
      val.ev_type = 'I';
      val.ev_long = lpTypeAttr->lcid;
      _Store(&loc,&val);

      // code for values 3 - 16 here
      ((ITypeInfo *)pTypeInfo) -> ReleaseTypeAttr(lpTypeAttr);
      }
   } __except  (EXCEPTION_EXECUTE_HANDLER) {
      nResult = 0;
   }
return nResult;

管理内存

Visual FoxPro API 提供了对 Visual FoxPro 动态内存管理器的直接访问。对于请求内存分配的 API 例程,返回一个内存标识(或称作句柄)。Visual FoxPro 使用句柄而不是指针分段加载结构,这样可以提高管理内存的效率。

注释 本节所述的使用 Visual FoxPro API 管理内存的技术,同时适用于 ActiveX 控件和 FLL 库。

使用句柄

这里句柄是指一个内存句柄,本质上是一个指针数组的索引。数组中的这些指针指向由 Visual FoxPro 掌握的各内存块。在 API 中几乎所有对内存的引用都通过句柄来实现, 而不是传统的 C 指针。

若要在库中分配并使用内存

  1. _AllocHand( ) 分配一个句柄。

  2. _HLock( ) 锁定该句柄。

  3. _HandToPtr( ) 把句柄转换为一个指针。

  4. 使用该指针引用内存。

  5. _HUnLock( ) 解锁句柄。

注释 为了避免破坏备注文件,在调用 _AllocMemo( ) 之前不要对备注文件进行写操作。

为了确定分配内存的地址,API 例程必须调用 _HandToPtr( ) 函数把句柄转换为指针。即使当 Visual FoxPro 内存管理器需要重新组织内存,以便后来的内存请求能获得更多的连续内存时,句柄也保持不变。同时还提供了增加、减少、释放和锁定内存分配的例程。

在创建外部例程时,应尽量减少内存使用。如果创建一个动态分配内存的外部例程,则请尽量少用内存。长时间锁定一个大的内存块时要特别小心。在不再需要锁定内存时,请记住使用 _HUnLock( ) 解锁内存句柄,因为锁定内存句柄会对 Visual FoxPro 的性能有不利的影响。

注意 过多地使用动态内存会侵占 Visual FoxPro 为缓冲区、窗口、菜单及其他系统资源分配的内存,降低 Visual FoxPro 的性能,因为为 API 例程分配的内存由 Visual FoxPro 内存管理器进行管理。分配大量的句柄并且一直保留这些句柄会导致 Visual FoxPro 内存不足并异常终止。

Visual FoxPro 环境没有内存保护。外部 API 例程不能提供有效性检查,而这种检查在一个标准 Visual FoxPro 程序中是固有的。如果内存崩溃,就会得到一些错误信息,比如“跨越句柄”、“内部一致性错误”和“压缩过程出现跨越节点”。

下面的 FLL 库中的函数演示了内存是如何分配的。假定示例中的字符型参数是正确的日期值,下面使用 _RetDateStr( ) 返回一个 Visual FoxPro 日期型值。

#include <Pro_exh.h>

void dates(ParamBlk  *parm)
{
   MHANDLE mh;
   char *instring;

   if ((mh = _AllocHand(parm->p[0].val.ev_length + 1)) == 0) {
      _Error(182); // "内存不足"
   }
   _HLock(parm->p[0].val.ev_handle);
   instring = _HandToPtr(parm->p[0].val.ev_handle);
   instring[parm->p[0].val.ev_length] = '\0';
   _RetDateStr(instring);
   _HUnLock(parm->p[0].val.ev_handle);
}
FoxInfo myFoxInfo[] = {
   {"DATES", (FPFI) dates, 1, "C"}
};
FoxTable _FoxTable = {
   (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
};

理解堆栈

您创建的控件和库不具有自己的堆栈。相反,它使用调用程序的堆栈,在这里也就是 Visual FoxPro 的堆栈。您不能控制 Visual FoxPro 堆栈的大小,或者影响一个 ActiveX 或 .fll 文件可用的堆栈空间大小。

在正常情况下,这个差别并不重要。Visual FoxPro 的堆栈通常很大,足以保存在控件和库中需要为其分配内存的自动变量。如果遇到堆栈溢出的问题,可以动态地在堆栈中分配额外的内存。

遵守句柄规则

下面说明了获取句柄和释放句柄的规则:

连编和调试库与 ActiveX 控件

在创建了一个项目之后,您就可以连编并调试它。

连编项目

在连编之前,需要建立项目的各种设置。有些设置取决于您是想创建控件或库的测试版还是发行版。通常,在对程序的工作状况感到满意前创建程序的测试版,然后再创建一个发行版。

若要指定测试版或发行版

  1. 在“Build”菜单中,选择“Set Default Configuration”。

  2. 选择是创建控件的测试版还是发行版。

  3. 选择“OK”。

若要建立项目设置

  1. 从“Build”菜单中选择“Settings”命令。

  2. 在“Settings For”下,选择是创建程序的测试版还是发行版。

  3. 单击“C/C++”选项卡,然后进行下列设置∶
  4. 选择“Link”选项卡,然后在“Object/Library Modules”文本框中添加下列库∶
  5. 设置“Ignore all default libraries”。

  6. 选择“OK”。

若要保证编译器能找到所需文件

  1. 从“Tools”菜单选择“Options”。

  2. 单击“Directories”选项卡。

  3. 在“Show directories for”列表框中选择“Include files”。

  4. 在“Directories”工具栏中,单击“Add”按钮。

  5. 添加具有 Pro_ext.h 的目录。

  6. 在“Show directories for”列表框中选择“Library files”。

  7. 在“Directories”工具栏中,单击“Add”按钮。

  8. 创建一个控件时,添加 Visual FoxPro API 目录中带有 OCXAPI.LIB 文件的目录;或者在创建一个 FLL 库时,添加 Visual FoxPro API 目录中的 WINAPIMS.LIB 文件。

  9. 在“Options”对话框中选择“OK”。

在指定了这些设置之后,可以编译并链接您的程序。

若要编译并链接一个 .ocx 文件

在编译并链接 .ocx 文件时,Visual C++ 自动在创建控件的计算机上注册该控件。如果由于某些原因必须人工注册控件,可以采用下列过程∶

若要注册 ActiveX 控件

调试一个 ActiveX 控件或 FLL 库

在一个完整的 Visual FoxPro 应用程序中调试一个控件或库,通常比单独地调试控件或库更为困难。一种较好的方法是创建一个简单的测试程序来测试控件或库的操作。

用 Microsoft Visual C++ 调试

Microsoft Visual C++ 4.0 或更高版本提供一个集成调试环境,可以方便地设置断点并单步调试所有代码。甚至可从 Visual C++ 中运行 Visual FoxPro。

若要启动 Microsoft Visual C++ 的调试功能

  1. 从“Build”菜单选择“Settings”命令。

  2. 在“Project Settings”对话框中单击“Debug”选项卡。

  3. 在“Executable for debug session”文本框中,输入路径和 VFP6.EXE。

    例如,输入
    C:\Program Files\Microsoft Visual Studio\Vfp98\Vfp6.exe。

  4. 选择“OK”。

  5. 在库中设置一个断点。

  6. 在“Build”菜单中,选择“Debug”。然后,在子菜单中选择“Go”。

  7. 当显示信息“VFP.EXE does not contain debugging information.”时,选择“Yes”继续。

有关在 Visual C++ 中调试的详细内容,请参阅 Visual C++ 文档集。

用其他调试器进行调试

可用任何调试器来调试一个库,只要该调试器能够正确处理嵌套在程序中的 INT 3 (_BreakPoint( ))。只要调试器能够做到下列几点,那么便可用来进行象征性的调试:

若要调试一个库

  1. 在函数要开始调试的地方添加一个 _BreakPoint( ) 调用。

  2. 连编该控件或库。

  3. 启动调试器。

  4. 如果调试器支持符号调试,请加载库的符号表。

  5. 启动 Visual FoxPro。

  6. 从 Visual FoxPro 中调用库例程。

  7. 当运行到断点时,调整符号基址,把符号与库的实际加载位置对齐。

  8. 给指令指针 (IP) 寄存器加 1,跳过 INT 3 指令。

  9. 如同调试普通程序一样继续调试。

    注释 在发布产品之前,一定要删除调试器中所有指定的断点。