[MATLAB]XXX塔扩展公式DLL与MATLAB交互

11 篇文章 0 订阅
7 篇文章 0 订阅

任务说明:

某塔的PEL里提供了许多的函数,但是也会存在某些情况下需要自行扩展的情况,某塔提供了集中扩展的方式,其中一种是使用DLL公式(VC的DLL编程)进行扩展,方便整合原有的公式。

而matlab里面提供了许多的信号,序列等处理方法,可以很方便的进行一些策略,计算等等。

现在需要使用matlab的自定函数(M函数)对金字塔的函数进行扩展。

前期调查:

VC与MATLAB交互的几种方式,见前篇:http://blog.csdn.net/fonjames/article/details/51554385

首先Matlab引擎(Engine)的方法据说效率比较低下,不推荐,依次尝试了 C++ shared library --> com组件 --> Matlab coder 的方式。

某塔中要求的DLL方式,参照某塔提供的 “栗子”项目FmlDevelope  位于   ...\WeiSoft所在目录\fmldevelop.zip

自己生成的dll放于   ...\WeiSoft所在目录\FmlDLL    

然后在某塔的公式编辑器里  使用  “mydll@myfun"(arg1,arg2)   即可,注意双引号是必须存在的。

/*
///
xxx塔“C语言接口”扩展程序调用接口规范V1.0
1.扩展函数可用于实现系统公式函数不能实现的特殊算法.
2.扩展函数用windows 32位动态链接库实现,建议使用Microsoft Visual C++编程.
3.调用时在公式编辑器中书写"动态库名称@函数名称"(参数表)即可,例如下面函数可以写为"STOCKFUNC@MYMACLOSE"(5)
4.动态链接库名称和函数名称可以自己定义.
5.使用时必须将动态库文件放置在与*.stk文档相同目录下使用.  //原文错误,应该是fmldll目录
6.请注意所运行xx塔的版本,如果是x64位版本,请将DLL编译为64位版本环境下才能正常运行。

从xxx塔的2.34版后,接口支持逐K线模式调用,增加RUNMODE系统函数,用于告知调用的公式系统本接口的运行模式。
xxx塔的公式系统运行模式分序列模式和逐K线模式,序列模式模式整个公式系统解释时只会调用一次本接口,传递和
返回序列参数数据到接口,而逐K线模式会在解释时每个K线都调用本接口,传递和返回也都是单值数据。
传统的接口是设计在序列模式下运行的,虽然在逐K线模式下xxx塔也可以调用运行传统的接口,但是这是效率很低并且是不
稳定的,因此如果需要在逐K线模式下使用接口,请使用此种专用模式。
*//*//调用接口信息数据结构

typedef struct tagCALCINFO{ ... } CALCINFO; 
注1: 
	1.函数调用参数由m_pfParam1--m_pfParam4带入,若为NULL则表示该参数无效.
	2.当一个参数无效时,则其后的所有参数均无效.
		如:m_pfParam2为NULL,则m_pfParam3,m_pfParam4也为NULL.
	3.参数1可以是常数参数或序列数参数,其余参数只能为常数参数.
	4.若m_nParam1Start<0, 则参数1为常数参数,参数等于*m_pfParam1;
	5.若m_nParam1Start>=0,则参数1为序列数参数,m_pfParam1指向一个浮点型数组,
		数组大小为m_nNumData,数据有效范围为 m_nParam1Start 至 m_nNumData-1.
		在时间上m_pData[x] 与 m_pfParam1[x]是一致的
注2: 
	1.该扩展结构使调用参数在技术上可以是无限数目的,且每个参数都为数值序列。
	2.CALCPARAM结构用于带入参数信息和实际数据,m_pCalcParam数组大小为m_nNumParam,数据有效范围为 0 至 m_nNumParam-1.
	3.按参数的顺序,m_pCalcParam[0]为第一个参数的数据,m_pCalcParam[1]为第二个参数的数据...,为了保持兼容,原m_nParam1Start、m_pfParam1等5个属性依然有赋值。
	4.取用m_pCalcParam[i].m_pfParam,数组大小为m_nNumData,数据有效范围为m_pCalcParam[i].m_nParamStart 至 m_nNumData-1. 若m_pCalcParam[i].m_nParamStart<0, 则此数组中无有效数据。
	5.由于可以调用多个序列,许多序列的计算可以先在公式中进行,然后作为调用的参数即可。
*/
/* 函数输出

__declspec(dllexport) int xxxxxxxx(CALCINFO* pData);
1.函数名称需全部大写.
2.函数必须以上述形式声明,请用实际函数名称替代xxxxxxxx;
	对于C++程序还需包括在 extern "C" {   } 括号中.
3.函数计算结果用pData->m_pResultBuf带回.
4.函数返回-1表示错误或全部数据无效,对于序列模式返回第一个有效值位置,即:
	m_pResultBuf[返回值] -- m_pResultBuf[m_nNumData-1]间为有效值.逐K线模式返回值大于等于0均表示成功
*/
//DLL公式的运行模式,系统系统调用该DLL时,告知公式系统该DLL公式运行在何种模式下
//返回值:	  0本DLL运行序列模式 1本DLL运行逐周期模式
__declspec(dllexport) int WINAPI RUNMODE();
//示例函数,使用时用实际名称替换  
__declspec(dllexport) int WINAPI MYMAVAR(CALCINFO* pData)

例子中的实现

 //计算均价,2个参数,参数1为待求均线的数据,参数2表示计算周期
//调用方法: "STOCKFUNC@MYMAVAR"(CLOSE-OPEN,5)
__declspec(dllexport) int WINAPI MYMAVAR(CALCINFO* pData)//对于逐K线模式,的处理方式的示范代码
{	if(pData->m_pfParam1 && pData->m_pfParam2)
	{	//对于逐K线模式,由于传递数据都是数值,因此需要一个数组用来保存传递过来的参数,用来计算.
		//本例只是简单演示这个用法,如果用户在公式多次调用了接口,则会出现重复使用该变量导致计算出错
		//因此要根据情况做几个这种全局静态变量用于保存各种数据。
		static std::vector<double> arMaData;		
		//第一个周期初始化数据
		if(pData->m_dwBarpos == 0)		{ arMaData.clear(); }
		double dbData = *pData->m_pfParam1;
		//防止使用仅刷最后K线的反复刷新问题
		if(arMaData.size() >= pData->m_nNumData)  arMaData[arMaData.size()-1] = dbData; //仅仅更新最后一个数据
		else			arMaData.push_back(dbData);
		DWORD dwCyc = (DWORD) *pData->m_pfParam2;		
		if(pData->m_dwBarpos < dwCyc-1)
		{	/*对于未到计算周期,返回无效数据*/	return -1;		}
		//先累加
		float fAdd = 0;
		for(DWORD i = pData->m_dwBarpos-(dwCyc-1); i <= pData->m_dwBarpos; i++)
		{			fAdd += arMaData[i];		}
		*pData->m_pResultBuf = fAdd / dwCyc;
	}
	return 1;*/
}

生成matlab的共享dll

使用mcc -W cpplib:YYYXXXXX  -T link:lib XXXXXX.m 或者 deploytool 将对应的m函数生成dll
再按照fmldevelop编写一个win32 dll, 最终结果却发现某塔在加载这个dll后就一直挂死,初步怀疑是加载函数dllmain中去再加载m函数的dll出了问题。
这个过程值得记录的就是对于mxarray与mwarray的转换。 参照 http://blog.csdn.net/fonjames/article/details/51622151
   mwArray dclose = mwArray(pData->m_nNumData,1, mxDOUBLE_CLASS, mxREAL);
   dclose.SetData(&arMaData1[0], arMaData1.size());
   mwArray dif1, dif2;
   mwArray nR = mwArray((int)dwRank);
   /* m函数自动生成,声明为: extern LIB_MatDif_CPP_API void MW_CALL_CONV MatDif(int nargout, mwArray& dif1
                                                                                            , mwArray& dif2
                                                                                      , const mwArray& close
                                                                                        , const mwArray& n);  */
   MatDif(2, dif1, dif2, dclose, nR); 
   *pData->m_pResultBuf = dif1(1,1);//mxDouble(dif1);



某塔在加载完封装的dll后,居然不会在当前目录查找m函数的dll,所以这个过程中还涉及了dll路径查找,简单的放到system32供其调用。在这条路径不通的情况,考虑使用com组件,这样第三层(某塔第一层,封装dll第二层) 就等于是向整个系统登记了位置,就没有很强的依赖了吧?

生成matlab的com组件

使用deploytool将m函数生成了com组件,并注册。使用http://blog.csdn.net/fonjames/article/details/51543846 的方法调用。
if(disp.CreateObject("MatDif1.LibMatDif") == false) {
			MessageBox(NULL,"加载com失败!","",MB_OK);
			return 0;
		}		
       VARIANT vclose, v3, vdif1, vdif2; 
	   VariantInit(&vclose);	   VariantInit(&vdif1); VariantInit(&vdif2);
	    v3.vt = VT_I2;
 	    v3.intVal = 3;
	    vclose.vt = VT_R8|VT_ARRAY;
		vclose.parray = SafeArrayCreateVector(VT_R8,0,arMaData1.size()-1);
		double * buf = NULL;
		SafeArrayAccessData(vclose.parray,(void**)&buf);
		memcpy(buf,&arMaData1[0],sizeof(double)*(arMaData1.size()-1));
		SafeArrayUnaccessData(vclose.parray);
		disp.InvokeMethod("MatDif",2, &vdif1,&vdif2,vclose,v3);
		pData->m_pResultBuf = new float;
		*pData->m_pResultBuf = vdif1.dblVal;
这里面又有新的内容,COM的基础类型是 VARIANT, VARIANT与matlab的矩阵(向量)数据的交互又涉及到了safearray.
参考文章:http://blog.csdn.net/zhangkunhn/article/details/9220859 和 http://blog.csdn.net/csfreebird/article/details/234547
我的例子里是使用了相对简便的方法,其中获取vector的array指针是,基于vector的数据是连续存放的原理。
  //SAFEARRAY的Win32定义:
  typedef struct tagSAFEARRAY   {  //这个结构的成员(cDims,cLocks等)是通过API函数来设置和管理的。
    unsigned short cDims;          //数组的维数
    unsigned short fFeatures;     //用来描述数组如何分配和如何被释放的标志
    unsigned long cbElements;    //数组元素的大小
    unsigned long cLocks;         //一个计数器,用来跟踪该数组被锁定的次数
    void * pvData;                 //指向数据缓冲的指针    /真正的数据存放在pvData成员中
    SAFEARRAYBOUND rgsabound[ 1 ]; //描述数组每维的数组结构,该数组的大小是可变的   //SAFEARRAYBOUND结构定义该数组结构的细节。
   } SAFEARRAY; 
     /* rgsabound是一个有趣的成员,它的结构不太直观。它是数据范围的数组。//该数组的大小依safe array维数的不同而有所区别。
      rgsabound成员是一个SAFEARRAYBOUND结构的数组--每个元素代表SAFEARRAY的一个维。 */
    typedef struct tagSAFEARRAYBOUND   {
    unsigned long cElements;
    unsigned long lLbound;
   } SAFEARRAYBOUND;
      /*维数被定义在cDims成员中。例如,一个\'C\'类数组的维数可以是[3][4][5]-一个三维的数组。
      如果我们使用一个SAFEARRAY来表示这个结构,我们定义一个有三个元素的rgsabound数组--一个代表一维。
     cDims = 3;  SAFEARRAYBOUND rgsabound[3];
     rgsabound[0]元素定义第一维。在这个例子中ILBOUND元素为0,是数组的下界。cElements成员的值等于三。
      数组的第二维 ([4])可以被rgsabound结构的第二个元素定义。下界也可以是0,元素的个数是4,第三维也是这样。
      要注意,由于这是一个"C"数组,因此由0 开始, */
  /*对于其它语言,例如Visual Basic,或者使用一个不同的开始。该数组的详细情况如下所示:
         元素               cElements  ILbound
 rgsabound[0]               3          0
 rgsabound[1]               4          0
 rgsabound[2]               5          0  */



在dllmain里初始化com环境,加载m函数的com文件,会导致程序挂死,将com文件的加载封装成一个方法,单例调用。
初步可以实现在金字塔里传入相应的close数据,计算出结果并返回。
但是在切换页面(F5) 即分时图与k线图时,会导致某塔又被挂死…… (应该是com的加载卸载问题……需要继续研究)

matlab的coder方式

三层的调用效率上回有折损,同时也会有很多交互的问题,能不能直接两层呢?偶尔发现了matlab提供coder方式。(2011a版本后)
折腾了下新版本2014,(编译器还要vs2008以上),将m函数生成了源代码。
需要选择参数的类型,同时选择生成c++代码,选择编译器(vs2008以上)最终生成了m函数文件 和 rtXXX的辅助文件。
在导入所有的生成的文件进项目,将每个cpp文件都include "stdAfx.h"文件进行预编译。
大致看了下数据结构 emxArray_real_T,初始化输入数据,处理输出数据,生成dll,放入金字塔,调用,成功!
<pre name="code" class="cpp">if(pData->m_dwBarpos < dwRank || pData ->m_dwBarpos < pData->m_nNumData -1)
{ //对于未到计算周期,返回无效数据
			return -1;
}
double *dif1 = new double;
double *dif2 = new double;
emxArray_real_T * close = emxCreateWrapper_real_T((double*)&arMaData[0],1,arMaData.size());
MatDif(close,3, dif1, dif2);
pData->m_pResultBuf = new float;
*pData->m_pResultBuf = *dif1;
emxDestroyArray_real_T(close);


 
 

第一次更新于  2016.06.16 凌晨。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值