教育装备采购网
第七届图书馆 体育培训

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

教育装备采购网 2018-11-27 17:35 围观2220次

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

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

编写一个hello-world C 插件

在进行任何计算之前,我都会演示如何编写和编译一个与Stata通信的C 插件。Code block 1包含myhellocpp.ado的代码。调用C 插件hellocpp,它只在Stata中显示Hello。

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

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

Code block 2中的Hello.cpp代码。

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

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

例如,在第12行,SPI函数SF_display(),它使Stata从C 中显示Hello,接受了C 字符数组msg。SF_display()不接受在第10行创建的C 字符串mystring。出于这个原因,我在第11行中复制了C mystring包含的字符数组msg。第14行返回0作为返回代码。注意,我把字面上的0作为预期的ST_retcode。

现在,将讨论如何从hello.cpp插件创建hellocpp.plugin插件。目录中包含了myhellocpp.ado和hello.cpp,还有stplugin.cpp。stplugin.cpp是stplugin.c的副本。我必须把stplugin.c复制到stplugin.cpp中,因为有些C 编译器不会把.c文件当作C 源文件来处理。stplugin.c定义的函数,在Stata中可以用它调用stata_call()函数。

不要更改stplugin.h或stplugin.c的内容。事实上,您甚至不需要管它们。在我的OS X Mac上安装了命令行开发者工具,我使用g 从stplugin.cpp和hello.cpp创建hellocpp.plugin,通过输入g -bundle -DSYSTEM=APPLEMAC stplugin.cpp hello.cpp -o hellocpp.plugin

上面的g 命令编译两个.cpp文件并将它们链接起来创建DLL hellocpp.plugin,可以被myhellocpp.ado调用。

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

已经创建了hellocpp.plugin,就可以在Stata中执行myhellocpp。

示例2: myhellocpp

为简单起见,stplugin.h, stplugin.cpp, hello.cpp, myhellocpp.ado和hellocpp.plugin在同一个目录中。对于较大的项目,我把.ado和.plugin文件放到Stata ADOPATH目录中,并使用我的编译器环境来管理我放置头文件和C 源文件。对于本文中的示例,我将所有的.ado文件、头文件、C 源文件和创建的.plugin文件放入单个目录中。

在插件中访问Stata数据

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

让我们先看一下ado-code。

在第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的条件排除,或者它被本地宏的范围排除在外,则会将观测排除在外。

第9行,进一步简化了C 插件,通过显示由插件列出的值的变量的名称。

第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”可以极大地减少循环中对数据的观测范围,从而加快代码的速度。

在包含mylistw.cpp, stplugin.cpp和stplugin.h的目录中,我通过输入下面的内容在Mac上创建了mylistwcpp.plugin,

g -bundle -DSYSTEM=APPLEMAC stplugin.cpp mylistw.cpp -o mylistwcpp.plugin

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

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

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

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

� 使用SPI函数从Stata中指定的数据样本中读取。

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

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

现在,我将讨论mylistw.cpp的具体部分。

在第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中。第31–33行确保我们跳过了在mylistcpp.ado第10行中被if限制排除的观测结果。为进一步说明细节,请参看示例2。

示例2: mylistcpp

在第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个观测值。

回到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 字符数组的内容。

SF_vdata()只能访问数值变量之一,Stata的数据类型(byte、int、long、float或double)。(使用SF_vdata()的字符串数据。)不管变量的Stata数值类型是什么,SF_vdata()把结果作为ST_double来保存。在示例2中,mpg,trunk,rep78在Stata中都是int,但是每个都作为ST_double保存到value中。

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

C 插件中的均值估计

现在讨论ado命令mymeancpp,使用mycalcscpp.plugin实现由mymean11.ado中的mymean_work()执行的计算,这个内容在《在Stata中编写估计命令:编写插件》讨论过。

Code block 5中mymeancpp.ado的mymeancpp代码,如下:

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

� 解析用户输入

� 创建文件名称和对象来保存结果

� 调用一个工作程序进行计算

� 将工作程序返回的结果存储在e()中,并且

� 显示结果

mymeancpp.ado和mymean11.ado的主要区别这个工作程序是一个C 插件,而不是一个Mata函数。

第6行和第7行与mylistcpp.ado中的代码相同。关于这些行如何创建本地宏varlist的描述,本地宏touse中样本包含变量,其中本地宏包含范围内指定的任何用户,具体可以参考 Getting access to the Stata data in your plugin中的mylistcpp.ado。

第8行将临时名称放入本地的宏b、V和N中。我们使用这些名称来计算由C 插件计算的结果,并且我们知道,我们不会覆盖用户保存在全局Stata内存中的任何结果(回想Stata矩阵和标量是Stata中的全局对象);请参阅Programming an estimation command in Stata: A first ado-command中的Using temporary names for global objects讨论的就是这方面的主题)。此外,Stata会在mymeancpp终止时,删除由tempname创建的临时名称中的对象。

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

mymeancpp中第14行类似于mylistcpp.ado第10行的对应部分。在这种情况下,plugin调用mycalcspp.plugin来做这个工作。varlist的详细信息,if “touse”和“in”上文已经讨论过了。 新的情况是,我们传递参数'b'V'‘N’,将临时名称传递给mycalcs.plugin。

mycalcscpp.plugin可以:

� 执行计算

� 将估计的方法放入Stata矩阵中,它的名称在本地宏b中

� 将VCE放入Stata矩阵中它的名称在本地宏V中,并且

� 将样本内的观测数量放入Stata标量中,其名称位于本地宏N中

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

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

在包含mycalcs.cpp, mymatrix.cpp, mycalcsv.cpp, mymatrix.h, mycalcsv.h, stplugin.cpp和stplugin.h的目录中,我创建了mycalcscpp.plugin在我的Mac上,通过输入

g -bundle -DSYSTEM=APPLEMAC mycalcs.cpp mymatrix.cpp mycalcsv.cpp stplugin.cpp -o mycalcscpp.plugin

创建好mycalcscpp.plugin之后,我运行示例3。

现在,我将讨论用于创建mycalcscpp.plugin的C 代码的一些问题。我从code block 6中的mycalcs.cpp开始,包含入口函数代码stata_call()。

总之,mycalcs.cpp中的代码可以执行以下任务:

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

  2. 创建了MyMatrix类的bmat和vmat实例,并保存结果。

  3. 使用工作函数MyAve()和MyV()来计算保存在bmat、vmat和nobs中的结果。

  4. 使用MyMatrix类的成员函数CopyCtoStataMatrix(),SPI函数SF_scal_save()从bmat,vmat和nobs中复制结果到Stata对象中,它的名称在步骤1中已经被解析。

mycalcs.cpp很容易读懂,因为我把所有的细节都放到了MyMatrix类和工作函数中。MyMatrix类是在mymatrix.cpp中定义的,而工作函数是在mycalcsv.cpp中定义的。

像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中看到。

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

第57-77行包括了CopyCtoStataMatrix()代码。第68行使用了一个我还没有讨论过的SPI函数。SF_mat_store(char *sname, ST_int i, ST_int j, ST_double val)将值val保存在Stata矩阵中的行i和列j中。矩阵的名称包含在字符数组sname中。在一个基于索引的行中给出行i和列j。

第67行使用成员函数GetValue(i,j),在MyMatrix类的实例中返回矩阵的(i,j)元素。GetValue()在mymatrix.h中定义,这是包含MyMatrix类声明的头文件。Code block 8包含mymatrix.h中的代码。

这段代码只使用标准C 和我已经讨论过的编码技术。在point(3)中,我注意到mycalcs.cpp使用工作函数MyAve()和MyV()来计算结果。这些函数是在code block 9中的mycalcsv.cpp定义的。

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

第35行使用MyMatrix的IncrementByValue()成员函数。当计算样本均值并将其保存在一个向量b的jth元素中时,需要将b[j] value保存到b[j]中。换句话说,b中jth元素增量的数量由vaule来表示。bmat中的bmat.IncrementByValue(0,j, value)增量元素j由value表示。

第39行使用MyMatrix的成员函数DivideByScalar()。bmat.DivideByScalar(z)将bmat的每个元素替换为能被z除的元素。

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

vmat.StoreValue(i, j, z)将z值保存在MyMatrix的vmat实例(i, j)元素中。vmat.GetValue(i, j)返回保存在MyMatrix的vmat实例(i, j)元素中的值。

附录

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

Windows 10

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

hellocpp.plugin

在包含stplugin.h, stplugin.cpp和hello.cpp的目录中,通过输入以下内容创建hellocpp.plugin,

x86_64-w64-mingw32-g -shared -static stplugin.cpp hello.cpp -o hellocpp.plugin

mylistwcpp.plugin

在包含stplugin.h,stplugin.cpp和mylistw.cpp的目录中,通过输入下面内容创建mylistwcpp.plugin

x86_64-w64-mingw32-g -shared -static stplugin.cpp mylistw.cpp -o mylistwcpp.plugin

mycalcscpp.plugin

在包含stplugin.cpp,stplugin.h,mycalcs.cpp,mycalcsv.h和mycalcsv.cpp的目录中,通过输入以下内容创建mycalcscpp.plugin,

x86_64-w64-mingw32-g -shared -static stplugin.cpp mycalcsv.cpp mycalcs.cpp -o mycalcscpp.plugin

RedHat Linux

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

hellocpp.plugin

在包含stplugin.h, stplugin.cpp和hello.cpp的目录中,通过输入以下内容创建hellocpp.plugin

g -shared -fPIC -DSYSTEM=OPUNIX stplugin.cpp hello.cpp -o hellocpp.plugin

在包含stplugin.h,stplugin.cpp和mylistw.cpp的目录中,通过输入以下内容创建mylistwcpp.plugin,

g -shared -fPIC -DSYSTEM=OPUNIX stplugin.cpp mylistw.cpp -o mylistwcpp.plugin

mycalcscpp.plugin

在包含stplugin.cpp,stplugin.h,mymatrix.cpp,mycalcsv.cpp,mycalcs.cpp,mymatrix.h和mycalcsv.h的目录中,通过输入以下内容创建mycalcscpp.plugin,

g -shared -fPIC -DSYSTEM=OPUNIX stplugin.cpp mymatrix.cpp mycalcsv.cpp mycalcs.cpp -o mycalcscpp.plugin

点击进入北京天演融智软件有限公司展台查看更多 来源:教育装备采购网 作者:科学软件网 责任编辑:张肖 我要投稿
校体购终极页

相关阅读

版权与免责声明:

① 凡本网注明"来源:教育装备采购网"的所有作品,版权均属于教育装备采购网,未经本网授权不得转载、摘编或利用其它方式使用。已获本网授权的作品,应在授权范围内使用,并注明"来源:教育装备采购网"。违者本网将追究相关法律责任。

② 本网凡注明"来源:XXX(非本网)"的作品,均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,且不承担此类作品侵权行为的直接责任及连带责任。如其他媒体、网站或个人从本网下载使用,必须保留本网注明的"稿件来源",并自负版权等法律责任。

③ 如涉及作品内容、版权等问题,请在作品发表之日起两周内与本网联系,否则视为放弃相关权利。

校体购产品