在Stata中编写评估命令:编写C++插件

2018-12-02 13:13

在Stata中编写评估命令:编写C++插件



  这篇文章是这系列文章的第三篇,说明了如何用另一种语言(如C、C 或Java)编写的代码插入Stata,这种技术被称为编写插件,或者为Stata编写一个动态链接库(DLL)。这篇文章介绍在C 中编写插件,实现了在mymean11.ado中由mymean_work()执行的计算。

  这篇文章类似于《在Stata中编写估计命令:编写插件》。不同之处在于,插件代码是用C 而不是c语言,如果读者熟悉那篇文章的内容,您会发现它在这里有很多的重复。

在Stata中编写评估命令:编写C++插件

  第6行执行插件,其句柄是hellocpp。第10行加载hellocpp中实现的插件。插件到句柄hellocpp。执行语句在加载语句开始之前就出现了。Stata ado-files被完整读取,每个ado-program、Mata函数或插件句柄都被加载到主ado-program的行之前。第10行实际上是在第6行之前执行的。

  在这种情况下,插件句柄的名称hellocpp,必须与主ado编程的名称不同,在本例中是myhellocpp,并且跟这个.ado文件中定义的任何其他ado编程不同。

在Stata中编写评估命令:编写C++插件

  第2行包括Stata插件头文件stplugin.h。第6行是Stata C 插件的入口函数的标准声明。在stata_call(),argc包含了传递给插件的参数的数量,而字符串argv将包含参数本身。

  第8行声明并为C 字符数组msg分配空间。使用字符数组可能看起来很奇怪,因为C 程序通常使用C 字符串来操纵字符串。我们需要字符数组,因为在Stata程序接口(SPI)中接受字符串参数的函数使用C/C 字符数组。

  在这篇文章的附录中,我提供了在其他平台创建hellocpp.plugin的说明。为编写和编译C 插件提供完整的文档。

在Stata中编写评估命令:编写C++插件

  hellocpp.plugin使Stata显示在插件内创建的内容。下一步是让插件访问Stata中的数据。为了说明这个过程,我将讨论mylistcpp.ado,它使用一个插件来列出对指定变量的观测。

在Stata中编写评估命令:编写C++插件

  在第6行,syntax创建三个本地宏。它将用户指定的变量放入本地宏varlist中,将用户指定的if条件放入本地宏if中,将用户指定的in范围放入到本地宏in中。我将max=3指定为syntax,变量的限制数量为3。这个限制是多余的,我不需要它作为Stata/Mata程序的例子,但是它简化了示例C 插件。

  在第7行中,marksample创建了一个包含样本的变量,并将其名称放在本地宏touse中。每个排除观测值包含样本的变量为0,每个包含观测值的变量为1。marksample使用本地宏varlist中的变量,如果本地宏中的if条件,以及本地宏in的范围来创建样本包容变量(所有三个本地宏都是由syntax创建的)。如果本地宏varlist中的任何变量都包含缺失值,则排除一个观测结果。如果它被本地宏if的条件排除,或者它被本地宏的范围排除在外,则会将观测排除在外。

  第10行,plugin调用mylistwcpp.plugin。因为“varlist”是指定的,SPI函数SF_vdata()将能够访问本地宏varlist中包含的变量。因为if “touse”是指定的,如果touse中的包容样本变量为0,那么SPI函数SF_ifobs()将返回0; 如果包含样本变量为1,则函数将返回1。因为“in”被指定,SPI函数SF_in1()和SF_in2()分别返回在用户指定范围的第一个和最后一个观测值。

  指定“In”不是识别用户指定的样本所必需的,因为if“touse已经指定了这个包含样本的信息。然而,指定“in”可以极大地减少循环中对数据的观测范围,从而加快代码的速度。

在Stata中编写评估命令:编写C++插件

在Stata中编写评估命令:编写C++插件

  在这里,讨论的是mylistw.cpp如何说明Stata中C 插件的结构,并解释了代码中使用的Stata插件接口(SPI)所定义的类型和功能。

  如果Stata运行顺利的话,mylistw.cpp会返回0。如果出错,它会返回一个非零错误代码。

  每次调用mylistw.cpp中一个可能失败的函数时,可以检查它的返回代码。如果该函数失败,我会让Stata显示一个错误消息,并且将一个非零错误代码返回到Stata中。这种逻辑提供了mylisw.cpp的总体结构。大多数代码处理错误条件,或不要将更多的字符放入字符串缓冲区中。

  C 插件使用SPI中定义的函数或写入Stata对象。mylistw.cpp并未返回任何结果,因此它有一个简单的结构:

  � 使用标准的C 和SPI函数来列出对指定样本的观测值,并保持指定样本中有多少观测值的计数器。

  � 使用标准的C 和SPI函数来显示样本中的第一个观测值、样本中最后的观察值,以及在指定的样本中有多少观测值。

  在第10-12行中,我使用SPI定义类型ST_int、ST_double和ST_retcode,用于SPI函数返回的变量,或者是Stata插件接口函数的参数。使用这些定义的类型是必要的,因为它们对原始C 类型的映射会随着时间的变化而变化。

  rc保存返回代码,插件会返回到Stata中。在第17行,我将rc初始化为0。如果一个可能失败的SPI函数执行请求,则返回一个0的返回代码。如果SPI函数不能执行请求,那么它会返回一个非零返回码。每当我调用一个可能失败的SPI函数时,我都会把它保存在rc返回代码中。如果rc不是0,我让Stata显示一条错误消息,并让插件返回保存在rc中的非零值。

  第19、21和23行使用SPI函数。SF_in1()将in范围指定的第一个观测值放在第一位。SF_in2()将in范围指定的最后一个观测值放到最后。如果in范围没有被指定为插件,那么first包含1,而last包含数据集中观测值的数量。SF_nvars()把varlist中的变量数量指定到nVars中。第3133行确保我们跳过了在mylistcpp.ado第10行中被if限制排除的观测结果。为进一步说明细节,请参看示例2。

在Stata中编写评估命令:编写C++插件

  在第31行,SF_ifobs(i)返回1时,指定到插件中的if限制是1,观测值是i,否则是0.

  在mylistcpp.ado的第10行,我们看到传递给插件的if限制是“touse”。正如上文所讨论的,本地宏touse中样本包含变量对于排除的观测值是0,并且对于包含的观测值是1。

  mylistcpp.ado中第10行的in范围被包含,因此,mylistw.cpp第28行的观测值是循环的,在in范围内可以从任何指定的地方开始或结束。在示例2中,mylistw.cpp第28行的循环只从2到10,而不是在自动数据集中的74个观测值进行循环。

  在示例2中,6个观测值的样本包含变量为1,而其他68个观测值的变量为0。in 2/10的范围不包括观测值1和11-74的观测值。在前10个观测值中,2被排除在外,因为rep78缺失。排除一个观测值,因为trunk是21。为了进行比较,在示例3中列出了2到10之间的9个观测值。

在Stata中编写评估命令:编写C++插件

  回到mylistw.c的第39行, rc_st=SF_vdata(j,I,&value)将观测值i在变量j中的值放入value中,并将SF_vdata()返回代码放入rc中。如果一切顺利,rc包含0,就不会输入42-44行中的错误块。如果SF_vdata()不能将数据保存到value中,那么就会输入42—44行中的错误块。误差块使Stata显示错误消息,并导致mylistw.plugin退出rc包含的错误代码。在误差块中,SF_error()使Stata显示红色的C 字符数组的内容。

  在第47行,如果value是缺失值,那么SF_is_missing(value)返回1,否则它将返回0。如果其中一个变量中的任何一个观测值包含缺失值,那么47-51行会导致mlistw.plugin退出,错误信息为416。这些行是多余的,因为样本包含变量传递给mylistw.plugin排除包含缺失值的观测。我包含这些行来说明如何安全地从插件内部排除缺失值,并重申C 代码必须小心处理缺失值。Stata缺失的值是C 中有效的双精度数。如果在计算中包含Stata缺失值,则会得到错误的结果。剩下的行构造C 字符数组,传递给Stata来显示每个观测结果,并显示关于样本的摘要信息。

在Stata中编写评估命令:编写C++插件

  这个程序的大概结构跟mymean10.ado和mymean11一样,这个内容在《在Stata中编写估计命令:编写插件》讨论过。整体来看,mymeancpp.ado可以

  第10-12行创建Stata矩阵来保存结果。我们使用由tempname创建的临时名称用于这些矩阵。

  第16-18行将变量名称放在估计方法向量的列条纹上,并在VCE矩阵的行和列条纹上。第19-21行将结果保存在e()中,第22行显示结果。

  现在,我将讨论创建mycalcs.plugin的代码。在讨论细节之前,让我们创建插件并运行一个示例。

  将Stata对象的名称作为参数传递给C 字符数组,这些数组可以传递给SPI函数。

  像mylistw.cpp, mycalcs.cpp使用返回码rc来处理错误条件。如果函数进行得很好,每个函数都返回0,并且如果每个函数执行请求的作业,则每个函数返回非零错误代码。如果返回的代码不是0,mycalcs.cpp立即返回到Stata_call()中,这反过来又将非零代码返回到Stata中。与错误条件相关联的错误消息由工作函数显示。

  在point(2)中,我注意到bmat和vmat是MyMatrix类的实例。样本平均值和VCE最好保存在矩阵中。为了使事物简单且自包含,我定义了一个使用矩阵行保存和仅需要的成员函数的裸矩阵类MyMatrix。除了成员函数copyCtoStataMatrix()之外,MyMatrix的代码是标准C ,可以在code block 7中看到。

在Stata中编写评估命令:编写C++插件

在Stata中编写评估命令:编写C++插件

  在第8行,我使用了一个预处理器宏来简化引用矩阵元素的代码。我在第79行上定义了宏。

  工作函数MyAve()是C 在Mata中MyAve()的实现,具体参看《在Stata中编写估计命令:编写插件》。它将样本平均值放入MyMatrix类的bmat实例中,并将样本中观测的数量放入nobs中。这个函数的大部分代码都是标准C ,或者它使用了我已经讨论过的SPI函数。

  MyV()是Mata函数MyV()的C 实现,这个内容在《在Stata中编写估计命令:编写插件》有讲到。它将VCE放入MyMatrix类的vmat实例中。这个函数的大部分代码要么是标准C ,要么是我们已经讨论过的技术。第71、78和87行使用MyMatrix成员函数Storevalue()和GetValue(),这两个函数由mymatrix.h定义。

  本文展示了如何使用命令行开发者工具在OS 10 Mac上编译和链接一个插件。在这里给出了gcc编译器在Windows 10和RedHat Linux上的命令。

  这部分提供了在一个64位Windows 10系统上编译和链接Cygwin环境中的插件的命令。与其他平台不同,我们不能只使用gcc编译器。在Cygwin中,gcc编译了在Cygwin POSIX/Unix环境中运行的应用程序。我们希望使用Cygwin来编译一个库,它将链接到一个本地的Windows应用程序,并在其中运行。Cygwin为Windows(MinGW)提供了极简的GNU编译器来完成我们想做的事情。合适的编译器是根据平台不同而不同的。在我的64位x86-intel机器上,我使用了x86 64-w64-mingw32-g 编译器。

  这个子部分提供了gcc命令来编译和链接RedHat Linux上的插件。