使用 csharp 编写 winform 程序,不仅速度快,而且容易界面美化并找到其他类库的支持;而使用 opencv 编写图形图像处理程序,是目前比较流行,而且高效的一种方法。如果需要将两者结合,需要解决的问题就是使用 csharp 调用 vc 下编写的库文件。两个难点,一个是平台调用的内存控制问题,一个是参数传递问题。关注点在解决实际问题
在现实中,我发现问题比较大的是两点,一点是内存控制问题,一个是平台调用问题。
一、内存控制:(1-6种方法是我验证后失败的方法,关注问题解决者可直接看第7点)
1 、验证 opencv 下的 dll 程序是否能够对内存起到很好的控制。方法是将图片路径写在 dll 文件中,使用 csharp 调用 dll 文件,以此来验证是否是函数内部的溢出。
DllExport void imageprocesstest1()
{
vector inputmat;
vector points1;
vector points2;
vector locmat1;vector locmat2;
char cur_dir[50];
char strimg [50];
for ( int iimg = 1;iimg<=23;iimg++)
{
inputmat.clear();
points1.clear();
points2.clear();
locmat2.clear();
locmat1.clear();
sprintf(cur_dir, "D:\\t\\SteelNo%d\\Camera01" ,iimg);
Mat tmp;
// 读取数据
for ( int i =1;i<=10;i++) // 执行全部文件的遍历
{
if (i<10)
{
sprintf(strimg, "%s\\image00000%d.jpg" ,cur_dir,i);
}
else
{
sprintf(strimg, "%s\\image0000%d.jpg" ,cur_dir,i);
}
cv::Mat src= cv::imread(strimg,0);
if (!src.data)
return ;
inputmat.push_back(src);
}
InputAdjust(inputmat);
MulitMatch(inputmat,locmat1,locmat2,5);
Mat ma = MulitAlign(inputmat,locmat1,locmat2);
if (ma.rows == 2 || ma.rows ==3)
{
return ;
}
else
{
Mat mb;
mb =MulitBlend(inputmat,ma,locmat1,locmat2);
if (mb.rows == 1)
{
return ;
}
else
{
sprintf(strimg, "%s\\3.jpg" ,cur_dir);
imwrite(strimg,mb);
}
}
}
return ;
}
在这个函数中,我对 d 盘 t 文件目录下的 23 个文件夹进行遍历,调用 InputAdjust , MulitMatch , MulitBlend 这 3 个主要函数进行拼接操作。在运行状态下 (ctrl+F5) ,不溢出。
2 、采用动态加载 dll 文件的方式进行函数调用。(具体见 Zealic.Windows )
就是采用 loaddll 的方式动态地将 dll 调入程序中,并且采用 freedll 的方式动态地将 dll 文件注销掉。这个方法在运行状态下 (ctrl+F5) ,溢出,没有效果。
3 、采用轻量化代码生成的方法 (dynamicmethod) 进行 dll 文件的调用。
DynamicLibrary golib = new DynamicLibrary( "GOImage.dll" );
NativeMethodBuilder goBuilder = new NativeMethodBuilder();
goBuilder.ParameterLength = 3;
goBuilder.SetParameterInfo(0, typeof ( string ));
goBuilder.SetParameterInfo(1, typeof ( string ));
goBuilder.SetParameterInfo(2, typeof ( int ));
NativeMethod method = goBuilder.MakeMethod(golib, "imageprocess" );
method.Invoke(strtmp, strImagePath_Res, 1);
同样溢出。以上方法在 F5 模式下都能够运行正常。
4 、这个时候,我开始想是不是我的 dll 那个地方的资源没有释放掉。所以我特地生成了一个简单的函数。
DllExport void imageprocesstest( char *cur_dir, char *imagepath_res)
{
char DllBuff[50];
vector inputmat;
Mat src;
for ( int i = 1;i<=10;i++) // 执行全部文件的遍历
{
if (i<10)
{
sprintf(DllBuff, "%s\\image00000%d.jpg" ,cur_dir,i);
}
else
{
sprintf(DllBuff, "%s\\image0000%d.jpg" ,cur_dir,i);
}
src= cv::imread(DllBuff,CV_LOAD_IMAGE_COLOR);
if (!src.data)
{
inputmat.clear();
return ;
}
for ( int j=0;j<10;j++) // 采用这种方式
{
inputmat.push_back(src); // 将读取的结果压入 inutmat
}
}
vector (inputmat).swap(inputmat);
return ;
}
它的调用采用
[ DllImport ( "GOImage.dll" ,
EntryPoint = "imageprocesstest" ,
CharSet = CharSet .Ansi, CallingConvention = CallingConvention .Cdecl)]
public static extern int imageprocesstest( string ImagePath, string ImagePath_Res);
这个函数不执行任何函数,唯一的作用就是讲图片读进去,然后清空。其中
vector (inputmat).swap(inputmat);
叫做 vector 的 swap 方法,是 effiective c++ 上面推荐的一种确实能够清空 vector 的方法。而 mat 对象时 opencv 的内建对象,自己带注销代码,而且其注销无法被显示调用。
这个时候,依然是 ctrl+F5 会溢出。
我认为,现在的情况是,在重复调用一个 dll 文件的情况下(而且这个 dll 文件需要若干秒的执行时间),会发生内存无法情况的情况。因为非托管方式下,垃圾回收器无法管理非托管代码内存。但是我已经显示地将内存注销了,只是不能被垃圾回收器回收。
5 、所以我强制进行垃圾回收
Gc.colect();
依然没有效果。所以这个方向的问题我卡在了这里。
6、这个时候,我想到了以前通过 com 调用 matlab 函数的时候也出现过这样的问题,但是当时是个小项目,就采用了一种方法对付了一下。现在也想看一下这种方法是否可行。
我们要达到的效果,就是输入一个目录,下面是以某种方式保存的图片,然后 进行拼接,得到对应的拼接图片。是否可以采用这种形式
Csharp 调用图像处理的 dll 文件,同时向 param.log 文件中以固定格式写入需要处理的图片的参数。 Dll 文件收到后,按照 param.log 中指定的参数进行处理,同时将结果写入 result.log ,并且在完成后,返回一个值, csharp 在收到这个值后,到指定的地方读取 param.log ,并显示出来。
如果采用这种方法,就只需要调用几次 dll 文件,那么内存溢出的问题就不会影响到系统的稳定。但是这个问题仍然没有被解决。
7、最终,在研究appdomin后,我发现,可以讲pinvoke 的调用写在program.cs下面。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
namespace WinFormTester
{
static class Program
{
[ STAThread ]
static void Main()
{
Application .EnableVisualStyles();
Application .SetCompatibleTextRenderingDefault( false );
Application .Run( new Form1 ());
}
[ DllImport ( "F:\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" ,
EntryPoint = "stringbuilder" ,
CharSet = CharSet .Auto, CallingConvention = CallingConvention .Cdecl)]
public static extern Boolean ParamIsString( string str, ref StringBuilder ip);
}
}
这种方法下再进行调用,解决内存问题。其原有我认为是。。
二、 参数传递问题。传递int等系统默认变量很简单方便,回写的方法,最后能够传递image变量
1、int 写入,如果要写出,加*
2、算是解决吧 bool值
DllExport int stringbuilder( char *str, int ** str_out)
public static extern Boolean ParamIsString( string str, ref int ip);
bool值的解决方法,装换为int类型后强制转换,0为假,不是0为真;
private void button1_Click( object sender, EventArgs e)
{
int k = Convert .ToInt32(textBox1.Text);
Boolean b = Program .ParamIsString(textBox1.Text,k);
textBox2.Text = b.ToString();
}
就返回值这一块,其实就是一个类型转换,不仅是可行的,而且可以灵活运用起来
3、string 写入,char*在里面,没有问题
To summarize, System.String is preferable when working with immutable strings, especially for input (In) arguments. On the other hand, System.Text.StringBuilder is preferable with changeable strings especially output (Out) arguments.
我相信,marsh和stringbuilder的方法一定可以解决这个问题,但是目前我还没有研究清楚。还是那句老话“一鸟在手胜过千鸟在林”我这里有一种可用的解决方法。
#include "stdafx.h"
#define DllExport extern "C" __declspec ( dllexport )
DllExport bool stringbuilder( char *str, char * str_out)
{
char CharBuf[256];
// memcpy(CharBuf,str,sizeof(char)*256);
// memcpy(str_out,CharBuf,sizeof(char)*256);
strcpy(CharBuf,str);
strcat(CharBuf, "ddd" );
strcpy(str_out,CharBuf);
return 1;
}
这里采用了stringcpy,直接的内存操作,需要在外部结合起来进行边界检查
byte [] srcBytes = System.Text. Encoding .ASCII.GetBytes(textBox1.Text);
byte [] resBytes = new byte [256];
bool b = Program .ParamIsString( ref srcBytes[0], ref resBytes[0]);
string res = (System.Text. Encoding .ASCII.GetString(resBytes, 0, resBytes.Length)).TrimEnd();
textBox2.Text = res;
以及
[ DllImport ( "F:\\04.研D究?项?目?\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" ,
EntryPoint = "stringbuilder" ,
CharSet = CharSet .Auto, CallingConvention = CallingConvention .Cdecl)]
public static extern bool ParamIsString( ref byte src, ref byte res);
有很多需要注意的点 .没有办法传递中文,但是这个方法具有通用性,体现了这种的问题如何来解决。变长等
4、结构体,关键是内外要用一样的数据结构,然后能够修改,这样就很强。
OK,这样可以解决问题:
DllExport bool structbuilder(ImageInfo & imginfo, int width, int height, int type)
{
imginfo.width = width;
imginfo.height = height;
imginfo.type = type;
return 1;
}
[ DllImport ( "F:\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" ,
EntryPoint = "structbuilder" ,
CharSet = CharSet .Auto, CallingConvention = CallingConvention .Cdecl)]
public static extern bool ParamIsStruct( ref ImageInfo src, int width, int height, int type);
[ StructLayout ( LayoutKind .Sequential)]
public struct ImageInfo
{
public int width;
public int height;
public int type;
};
并且,非常重要的一点是,这里可以对这些类进行重新的封装,这样能够把csharp的优点和c++原生代码的优点结合起来。结果就可以变得比较稳定。这是一个新的总结性的问题。
下面是传递图像。如果int是第一类,代表的是系统原生的类型,struct是一类,代表你能够研究清楚内存放置的结构,而string又是一类,代表的是内存的直接操作。好的opencv的图像,要用上面的所有知识。