matlab 与VS混合编程的几个问题

matlab 与VS混合编程的几个问题

根据前一段的时间matlab与VS程序数据互传的程序调试,总结一下编译调试过程中可通遇到的几个问题及处理方法。

1.环境与资源配置

本人用的是matlab2016b 与VS2017 Community版,采用matlab语言与VC++编译器完成联合编译。关于VS中有关matlab头文件与库文件,包含路径网上多的是,不再多说。Matlab下VS编译器的使用首先要做以上工作:

1.1 mex 配置

在matlab中执行以下命令,会得到本机VS配置参数 ,图1中有好多“否”不重要,重要的是图2这些是“是”,保证你的VS2017可以使用。图2最后一行,显示VS2017配置成功。

图1 mex查询
图1 mex查询
在这里插入图片描述

图2

1.2 mex配置失败处理

Mex配置失败,一般是matlab2016b少了msvr2017.xml与msvcpp2017.xml两上,这是matlab2016b的两个补丁文件,位置如图3所示。
在这里插入图片描述

图3
给出其下载链接的网站如下:
http://www.mathworks.com/help/matlab/matlab_external/upgrading-mex-files-to-use-64-bit-api.html。
在这里插入图片描述

图4 下载文件包
下载完成如图4(下载时需个人注册一下,免费的),解压并将两个文件拷贝至如图3位置,这个工作根据自己电脑的安装情况决定,如不能完成,建议别做VS与matlab的联合编译工作。(如不能下载,请发邮件(附后),我只有matlab2016与VS2017相关的,其它配置请在http://www.mathworks.com网站上自己找。)然后可以执行第一步了,可mex执行还不成功,请另请高明。

2.Matlab调用Vs编写的dll

2.1主要matlab函数:

1)	loadlibrary
2)	libisloaded
3)  calllib
4)    unloadlibrary

具体参数传输请参考matlab帮助,如图5。(这个谁不会用?)
在这里插入图片描述

图 5

2.2 DLL编译注意事项

1)matlab调用需要头文件***.h与动态库文件***.dll;如果不会在VS上产生一个dll工程,请去认真学习VC++。生成***.dll自己做好先在VS上直接做一个exe调用调试一下,保证DLL执行无误。注意如你的matlab是64位的,dll也要用64位编译,如图6所示。
在这里插入图片描述

图6
2)matlab对用VS产生的动态库只支持标准C编写的动态库,所以动态库中有关C++元素就不要写入了,如class new delete等,struct一定要用typedef格式。写成:

Typedef struct{
Int aa;
}bb;
而不是:
struct bb
{
Int aa;
};

头文件***.h和源文件***.c(注意是.C不是.CPP存盘时自己改名)中的函数,变量定义建议用extern “C” 定义,做法如下:

#ifdef __cplusplus //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern “C” {
#endif

//这里写函数与变量定义 (***.h中)
// 这里写函数体 保证临时定义及语法符合C规范 (***.c中)

#ifdef __cplusplus
}
#endif

2.3 建议dll接口函数的写法

Matlab为弱变量类型定义语言,其数据类型没有什么double、float、int、char等具体变量定义方法,这些类型只能根据输出要求,进行强制转换,内存里基本上以double类型的,这与C语言的严格类型匹配格格不入,在dll函数参数传递方面,也许有比较简单的办法,但没有调试经验,不建议大家浪费时间。对于以下类型函数:
double myfunction(double x1,double x2 , double *x3 ,double *x4);
可以在dll中定义,在matlab上调用,注意以上形参中可以有指针,(matlab 有指针的,不过不要想多了,指针初始化能烦死人,如果指针定义的数组只有几个元素,可以逐个初始化,如果你的指针指向一个图像数据,建议放弃吧!)有了指针,多个返回值便可实现(你明白的),注意你的函数返回值一定不能是一个你定义的结构体,matlab不支持,所以一定不要试(我的时间全浪费在这些坑里了)。给出matlab 的指针函数
libpointer
自己去调吧,这么邪恶的东西,作为一个业余使用C++的人,我憎恨它。
所以简单的多参数返回大家都认真体会一下上面给的函数形式,所以你在做dll程序时一定不要想多了,能double的就double吧,远离内存优化吧!

2.4 大量参数返回的matlab接口

大量的参数返回,返回给matlab一下数据指针,完全是对牛谈情,不仅仅是不懂,根本是找错了对象。所以matlab还留下了补丁,它允许C编译器生成一个它能调用的文件,就象matlab自己的函数一样调用。C语言要调用一个DLL的执行数据,显然是可行的(这里不多说了)。但注意matlab函数形式,它是一只孤鸟,没有全局变量的什么事,函数间传点数全需要形参传递,对于执行次数不多的函数可以这么干,对于多次执行的函数,明显效率极低,尤其对于每次都要loadlibrary的事,这简直不是人能想到的设计,将来程序做完,你不哭,电脑也要哭了!但C语言也能用别的招式,比如它可以读内存,通过内存共享,实现不同进程间的数据传递。
实现如下:
1)在发送进程(也就是dll文件中)中初始创建一块内存空间;函数原型如下:(#include <windows.h> 这个微软接口还不错)

	  HANDLE  hMapImageFile = CreateFileMapping(
			INVALID_HANDLE_VALUE,
			NULL,
			PAGE_READWRITE,//权限,有枚举定义,自己去查
			0,
			filesize ,  //内存大小,字节数,自己根据需求分配
			("ImageDataShareMemory") //共享内存名,自己起名,注意在另一个进程中必是这个名。
		);
//hMapImageFile 为本进程中共享内存的名柄。判断创建成功与否

2)打开共享内存

hMapImageFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, false1, "ImageDataShareMemory");
此函数返回句柄与创建句柄为同一指针,注意共享名与创建一致,false1为自定义bool型变量宏:
#define false1 0

不要惊奇,C语言没有false关键字。
3)内存映射首地址获取

LPVOID	lpMapAddress1 = MapViewOfFile(
			hMapImageFile, //注意此句柄为上面创建与打开句柄。
			FILE_MAP_ALL_ACCESS,
			0,
			0,
			0);
	if (buf != NULL) // buf为要写内存的char型指针
		{
			free(buf);
			buf = NULL;
		}
		buf = ( char* )malloc(filesize); // 分配缓存区大小,注意此filesize与创建的函数中的filesize一致。

//下面将要发送的数据写入buf,注意所有的数据都可以以字节形式写入,不会的话去看基础C的操作。我测试时如下写入:

memset(buf, 1, filesize);

可以说一下,你可以直接压入buf一个结构体,接收时将指针让结构体强制类型转换一下,直接读出,甚至在buf里你可以定义各种标识位。
下面将缓存数据写入映射内存:
lstrcpy((char*)lpMapAddress1, buf);
简单吧!
4)内存映射释放

UnmapViewOfFile(lpMapAddress); //解除内存映射,注意一定要在创建的主进程中解除,而且解除时,一定保证读取工作已完成,而且确认程序运行已完成,现在内存余量大,不要解除,再建立,如是实时数据传输,会内存操作报错的。相当于不停拆房子再建房子。lpMapAddress这个指针不能删除;程序结束后不再使用就好。

下一句

CloseHandle(hMapImageFile); //这个函数释放句柄,同时释放内存,

5)内存文件从进程读取
注意这些内存读取代码单独写在一个.C文件里,文件名就是matlab你将来要调用的函数名。

HANDLE  lhShareMemory = 
OpenFileMapping(FILE_MAP_READ,
 0, 
"ImageDataShareMemory"); //注意这个名内存名,与主进程创建的内存名一致。此函数在从进程中获取内存句柄。注意不能在从进程中再创建同名内存映射,打开就好。
char *lpcBuffer = (char*)MapViewOfFile(
lhShareMemory,  //从进程中获得的内存映射句柄。
FILE_MAP_READ, 
0, 
0, 
filesize); //filesize输入0,也能工作,没有来得及调试,大家可以试试。
类型转换,我测试时使用如下语句:
uint16_t * fp = (uint16_t *)lpcBuffer;
uint16_t 在(#include<stdio.h>)定义。此类型可以定义成任何数据结构,比如你自定义的struct。
读取出来,示例代码处理如下:
long length = filesize /sizeof(uint16_t);
注意fp指针还指在共享内存,下次共享内存值改变fp下的值也就变了。所以应立即将数转移到另一块存储空间。
 long * buf = (long *)malloc(length);
 char * fpp = (char *)buf;
 memcpy(fpp, fp, filesize);

至此buf里就存着你要读取的数据。
注意一下,句柄lhShareMemory 与指针lpcBuffer不用在从进程这里关闭与free。否则会报错,你只需将buf 指针free掉。
6)建立matlab 程序编译的C++代码
第5部写了数据接收的关键代码,但是那些代码还是要填在如下函数中。头文件包含如下,你还可以填加:

#include "mex.h"  //必须有
#include "matrix.h" //你要将数据传回matlab,这个也必备,因为matlab的数据大多是矩阵,要是传一个数也不会这么麻烦了。
#include <Windows.h> //内存共享的函数文件头
#include<stdio.h> //你要做字节缓存数据从映射中拷出,字符处理函数的文件头,否则你用循环去读,速度会很慢。
入口函数:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])

这个函数网上有许多变种,这个mexFunction函数名不会变,你的***.c文件里可能有许多函数,但都必须在这个函数里执行,否则无效。
nlhs 是matlab调用时输出量个数,这个得理解一下,matlab不会直接调用mexFunction的,一会下示例认真看一下;
*plhs[] 是matlab调用时输出量以矩阵形式描述,这些矩阵可以是不一样的,看这个(mxArray *plhs[])定义,是好多个mxArray。
nt nrhs, const mxArray *prhs[] 分别输入量个数与输入量,还没调试,估计与输出差不多,大家有空去试试。
贴一段代码上来看看吧:

#include "mex.h"
#include <Windows.h>
#include "matrix.h"
#include<stdio.h>
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#define m_width  640
#define m_Height 512
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
	double *y;
	for (int i = 0; i < nlhs; i++)
	{
		plhs[i] = mxCreateDoubleMatrix(m_width, m_Height, mxREAL); // plhs[i]每一个都是矩阵,mxCreateDoubleMatrix 将数据都弄成了double了,反正到matlab了,类型都没用了。可以看出每个plhs[i]都有自己灵活的大小。mxREAL这个参数不用试了,估计你用复数的条件很少。
     }
	HANDLE  lhShareMemory = OpenFileMapping(FILE_MAP_READ, 0, "ImageDataShareMemory");
//打开映射文件,都懒得判断是否成功,也没加异常处理
	char *lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, m_width * m_Height * 5 * 2); //将内存段数据映射给指针,也没加异常处理

	uint16_t * fp = (uint16_t *)lpcBuffer; //强制类型转换
	for (int k = 0; k < nlhs; k++)
	{
		y = mxGetPr(plhs[k]);   // mxGetPr得到矩阵的指针,估计在matrix.h里有定义。
		for (int j = 0; j < m_Height; j++)
		{
			for (int i = 0; i < m_width; i++)
				y[i + j * m_width] = fp[i + j * m_width + k * m_width * m_Height]; //把这些16位图像像素值就赋给double型数组。
		}
	}
}

注意以上代码存成Readfuck.c ,在哪写都一样,记事本也行,VS的空文件里最好,但它不在VS里直接编译。
7) mex 编译
虽然不高兴,但文件还时改成了ReadImageData.c,拷到matlab当前能找到执行的路径下。
在这里插入图片描述

图 7
如图7所示它编译成功了。
8)mex 编译文件调用
在这里插入图片描述

图8
图8 哪一句是从动态库里的读内存映射语句?
[p1,p2,p3,p4,p5] = ReadImageData(5);
这句执行了,看出了吧,matlab的函数名就是文件名,5是指输出5项,这个值会传给mexFunction函数的nlhs变量,以上调试五个数组返回正确。至于其它的输入形参匹配你们去调吧,我用不到。从以上调用大家看清了dll多数据传回的过程了吧。图上那个GetTestData函数就是动态库写映射文件的。

3. VC++进程间内存映射读取与互斥量编程初探

不同进程间内存映射读取时必须进行同步性设置,否则内存处理会出错。尤其对实时采集与处理的缓存系统尤为重要,本人也是初步调试这方面的程序,把工作过程记录如下。

3.1主进程工作:

1)初始化函数

	#include <windows.h>
HANDLE h_MutexHadle = CreateMutex(NULL, false, "Mytest");//创建互斥量。自己加异常处理。“Mytest”这个名起好,不同进程间识别这个标识。与内存映射的名作用差不多。
	ReleaseMutex(h_MutexHadle); //创建后立即释放

  HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,PAGE_READWRITE,0,filesize + 1,("ShareFile")); //创建内存映射,前面说过了。

	LPVOID lpMapAddress = MapViewOfFile(hMapFile,
FILE_MAP_ALL_ACCESS,0,0,0); //取出内存映射指针,此指针若频繁使用,可及早取出。
2)数据发送

DWORD ret = WaitForSingleObject(h_MutexHadle, INFINITE);//查看句柄h_MutexHadle指向的互斥量是否被其它线程或进程占用,如被占用,本线程停下来等待。INFINITE指要等到天荒地老,此情不渝。可以设置等待时间,输入一个数,单位ms。如(ret = WAIT_OBJECT_0)时,说明互斥量没有被别的线程或进程占用,本线程可用。WaitForSingleObject函数不再等待,本线程占领此互斥量,别个线程与进程要想使用必须等本线程爽完。示例代码如下:

    DWORD ret = WaitForSingleObject(h_MutexHadle, INFINITE);
		if (ret == WAIT_OBJECT_0) //占有互斥量,进入爽态
		{	memset(buf, 56, filesize - 1);
			buf[filesize] = 1; //最后一位做标识,标志读写状态,以免对同一数据多次读写
            char tem = ((char*)lpMapAddress)[filesize];//读出标识
if (tem == 0) //上一次数已被读
            {
			lstrcpy((char*)lpMapAddress, buf); //写入新数
			((char*)lpMapAddress)[filesize] = 1; //确认最后一位是1,上边buf已写入1,此句可删。
             }
			     ReleaseMutex(h_MutexHadle); //爽完让坑
		}
    以上代码正常工作在特定的线程循环里。

3)资源释放函数

 Delete [] buf;
UnmapViewOfFile(lpMapAddress); //释放内存映射
CloseHandle(hMapFile);
	 CloseHandle(h_MutexHadle);

3.2 从进程工作

1)初始化

HANDLE h_MutexHadle = OpenMutex(MUTEX_ALL_ACCESS, false1, "Mytest");
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, false1, "ShareFile");
 LPVOID lpMapAddress = MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,0,0);
2)数据读写
    DWORD ret = WaitForSingleObject(h_MutexHadle, INFINITE);
		if (ret == WAIT_OBJECT_0) //占有互斥量,进入爽态
		{
            char tem = ((char*)lpMapAddress)[filesize];//读出标识
           if (tem == 1) //上一次数已被写
            {
			lstrcpy(buf,(char*)lpMapAddress); //写入新数
			((char*)lpMapAddress)[filesize] = 0; //确认最后一位是0,表示已读。
             }
			 ReleaseMutex(h_MutexHadle); //爽完让坑
		}

3)资源释放函数

Delete [] buf; //其它资源在主线程里释放

以上工作过程中,其工作流程必须是主进程先启动,数据写入完成初始化完成,然后从进程再启动,反过来则从线程初始化函数不会获得主线程的互斥量与共享内存参数,实际运行时可加上进程启动检测代码让从进程自动判断主进程时否已在运行,关机时则应过来进行。

4.总结

本调试过程是同事在matlab上做了好多图像处理地程序,让我给他做一个实时的采集与分析程序,我不太想将他matlab程序重写成C语言进行联调,也不太想将他matlab程序做成dll,我去调试,所以做了一个这样的数据缓存发送的调试过程,希望能让同事自主实现采集与处理,并能很好的利用matlab图形图像显示接口。
第一次做C动态库,也是第一次调同步与内存映射,还要做matlab接口dll调用与数据传输。花了完整三天的时间,大部分都在找matlab的相关资料,感觉对matlab越来越讨厌了。
现在将过程总结一下,以后少走弯路,也希望大家少走弯路,其实浪费生命进别人坑里一趟,真的很没有意义。
(大家若有疑问,仅限此文,不准备做太多测试了,
留邮件:sxwlux@163.com)

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值