首页 > 其他分享 >CLR基本原理和如何运用于GOCW

CLR基本原理和如何运用于GOCW

时间:2022-12-25 12:01:09浏览次数:41  
标签:img int 基本原理 代码 托管 C++ GOCW CLR

   GOCW的重点和难点就在于Csharp调用OpenCV,其中的桥梁就是CLR,当然我们也有其他方法,但是CLR是一个比较新的、比较可靠的、关键是能用的桥梁。这里关于CLR的基本原理知识、如何用于GOCW项目的相关内容加以整理思考,以图深入:



一、什么是CLR;



1、什么是CLR

CLR(Common Language Runtime)是“公共语言运行时”的缩写,简单来说它是和Java虚拟机一样的一个运行时环境。它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

通用语言运行时是.NET 框架应用程序的执行引挚。它提供了许多服务,其中包括:代码管理(装入和执行)、类型安全性验证、元数据(高级类型信息)访问、为管理对象管理内存、管理代码,COM对象和预生成的DLLs(非管理代码和数据)的交互操作性、对开发人员服务的支持等等。

我们GOCW项目中为了能够使用Csharp调用OpenCV,采用了托管C++;

 

2、什么是托管C++?

       托管是.NET的一个专门概念,它是融于通用语言运行时(CLR)中的一种新的编程理念,使用托管C++意味着,我们的代码可以被CLR所管理,并能开发出具有最新特性如垃圾自动收集、程序间相互访问等的.NET框架应用程序。

  由托管概念所引发的C++应用程序包括托管代码、托管数据和托管类三个组成部分。  

  (1) 托管代码:. Net环境提供了许多核心的运行(RUNTIME)服务,比如异常处理和安全策略。为了能使用这些服务,必须要给运行环境提供一些信息代码(元数据),这种代码就是托管代码。所有的C#、VB.NET、JScript.NET默认时都是托管的,但Visual C++默认时不是托管的,必须在编译器中使用命令行选项(/CLR)才能产生托管代码。

  (2) 托管数据:与托管代码密切相关的是托管数据。托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。默认情况下,C#、Visual Basic 和 JScript.NET 数据是托管数据。不过,通过使用特殊的关键字,C# 数据可以被标记为非托管数据。Visual C++数据在默认情况下是非托管数据,即使在使用 /CLR 开关时也不是托管的。

  (3) 托管类: 尽管Visual C++数据在默认情况下是非托管数据,但是在使用C++的托管扩展时,可以使用"__gc"关键字将类标记为托管类。就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。另外,一个托管类也完全可以成为 .NET 框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的C++类可以从Visual Basic类继承等。但同时也有一些限制,如托管类只能从一个基类继承等。需要说明的是,在托管C++应用程序中既可使用托管类也可以使用非托管类。这里的非托管类不是指标准C++类,而是使用托管C++语言中的__nogc关键字的类。

3、托管C++与标准C++的主要区别

    尽管托管C++是从标准C++建立而来的,但它与标准C++有着本质上的区别,这主要体现在以下几个方面:
  (1) 广泛采用"名称空间"(namespace)
  名称空间是类型的一种逻辑命名方案,.NET使用该命名方案用于将类型按相关功能的逻辑类别进行分组,利用名称空间可以使开发人员更容易在代码中浏览和引用类型。当然,我们也可将名称空间理解成是一个"类库名"。

       (2) 基本数据类型的变化

  我们知道,标准C++语言的数据类型是非常丰富的。而托管C++的数据类型更加丰富,不仅包含了标准C++中的数据类型,而且新增了__int64 (64位整型)、Decimal(96位十进制数)、String*(字符串类型)和Object*(对象类型)等类型,表1-1列出它们各自数据类型。

      (3) 新增三个托管C++类型:__gc class、__value class和__gc interface

  一个__gc类或结构意味着该类或结构的生命周期是由.NET开发平台自动管理及垃圾自动收集,用户不必自已去调用delete来删除。定义一个__gc类或结构和标准C++基本相似,所不同的是在class或struct前加上__gc。

 



 



二、CLR为什么能用于Csharp和C++互相调用



 



      基本的思路是将C++代码封装成为托管代码,而CSharp代码本来就可以翻译成CLR语句。在这种情况下,C++实现的效果,能够直接被CSharp调用,从而达到联合的目的。



      其中的难点,其实并不是引用,而是参数的传递:如何将“图像”这种本质上较为巨大的数据在两种系统里面传递,所以必然就需要有内存的操作;此外CLR语言的编码方法和普通CSharp差距较大,也是需要注意。



      一般来说:



      有C#及C++背景的人使用C++/CLI的必备知识:



  1、C++/CLI里的new等于C++里的new, gcnew等于C#里的new



  2、原生指针用*表示,托管引用使用^表示



  如: Stream^ stream = gcnew Stream();



  3、array<unsigned char>^ 等于 System.Byte[]



  4、pin_ptr关键字能把托管引用转换为原生指针:



  如: pin_ptr<BYTE> pBytes = & byteArray[0];



  然后pBytes就可以当作原生的BYTE* 使用了. 



  等代码执行完pBytes的有效范围,byteArray就会恢复可被GC处理的状态



 



三、通过CLR传递Mat和Bitmap:



 



      这里应该算是核心代码的解析,完整代码可以自己看,主要讲接口



Csharp(BitMap)->Mat->Csharp(BitMap) ,几乎全部的内容都在CLR形式的C++代码中,其它地方只是实现接口。



将输入cli::array<unsigned char>转换为cv::Mat//

pin_ptr
<System
:
:Byte
> p1
=
&pCBuf1[
0];


unsigned
char
* pby1
= p1;

cv
:
:Mat img_data1(pCBuf1
-
>Length,
1,CV_8U,pby1);

cv
:
:Mat img_object
= cv
:
:imdecode(img_data1,IMREAD_UNCHANGED);


if (
!img_object.data)


return nullptr;



这里注意,内存操作其实是在imdecode中实现的,我们相信OpenCV已经做了比较好封装。




System
:
:Drawing
:
:Bitmap
^ MatToBitmap(
const cv
:
:Mat
& img)

{


if (img.type()
!= CV_8UC3)

{


throw gcnew NotSupportedException(
"Only images of type CV_8UC3 are supported for conversion to Bitmap");

}


//create the bitmap and get the pointer to the data

PixelFormat fmt(PixelFormat
:
:Format24bppRgb);

Bitmap
^bmpimg
= gcnew Bitmap(img.cols, img.rows, fmt);

BitmapData
^data
= bmpimg
-
>LockBits(System
:
:Drawing
:
:Rectangle(
0,
0, img.cols, img.rows), ImageLockMode
:
:WriteOnly, fmt);

Byte
*dstData
=
reinterpret_cast
<Byte
*
>(data
-
>Scan0.ToPointer());


unsigned
char
*srcData
= img.data;


for (
int row
=
0; row
< data
-
>Height;
++row)

{

memcpy(
reinterpret_cast
<
void
*
>(
&dstData[row
*data
-
>Stride]),
reinterpret_cast
<
void
*
>(
&srcData[row
*img.step]), img.cols
*img.channels());

}

bmpimg
-
>UnlockBits(data);


return bmpimg;

}

bmp是有LocKBits操作的,就是在这里将Bitmap处理的结果固定在内存中的。



Bitmap类使用LockBits和UnLockBits方法来将位图的数据矩阵保存在内存中、直接对它进行操作,最后用修改后的数据代替位图中的原始数据。 LockBits返回以各BitmapData的类用已描述数据在已锁定的矩阵中的位置和分布。



      BitmapData类包括以下几个重要的属性:



  • Scan0:数据矩阵在内存中的地址。
  • Stride:数据矩阵中的行宽,以byte为单位。可能会扩展几个Byte,后面会介绍。
  • PixelFormat:像素格式,这对矩阵中字节的定位很重要。
  • Width:位图的宽度。
  • Height:位图的高度。

  具体关系见下图:

CLR基本原理和如何运用于GOCW_CLR



 



   如上图所示,stride属性表示位图数据矩阵的行宽,以byte为单位。出于效率考虑,矩阵的行宽并非刚好是每行像素数的整数倍,系统往往会将其封装成4的整数倍。举例来说,对于一幅24位深17像素宽的图像,其stride属性为52;每行的数据量为17*3=51,系统将其自动封装一个字节,所以它的stride为52byte(或13*4byte)。对于一幅17像素宽的4位索引图,其stride为12,其中9byte(准确地说是8.5个byte)用来记录数据信息,每行再自动添加3(3.5)个byte保证其为4的整数倍。



   具体数据的分布因其pixelformat而异。24位深的图像每隔3个byte包含一组RGB信息;32位深的图像每隔4个byte包含一组RGBA信息。那些每个字节包含多个像素的pixelformat,比如4位索引图像或1位索引图像,必须经过仔细处理,从而保证同一字节中的相邻byte不会混淆。



指针的准确定位



  • 32位RGB:假设X、Y为位图中像素的坐标,则其在内存中的地址为scan0+Y*stride+X*4。此时指针指向蓝色,其后分别是绿色、红色,alpha分量。
  • 24位RGB:scan0+Y*stride+X*3。此时指针指向蓝色,其后分别是绿色和红色。
  • 8位索引:scan0+Y*stride+X。当前指针指向图像的调色盘。
  • 4位索引:scan0+Y*stride+(X/2)。当前指针所指的字节包括两个像素,通过高位和低位索引16色调色盘,其中高位表示左边的像素,低位表示右边的像素。
  • 1位索引:scan0+Y*stride+X/8。当前指针所指的字节中的每一位都表示一个像素的索引颜色,调色盘为两色,最左边的像素为8,最右边的像素为0 。(TODO EMGUCV ISSUE)



四、CLR传递int和string



 



这样的图像处理程序,肯定不是只传递图像就够了的:在处理图像的过程中,可能遇到错误和异常,最好是以errorCode也就是一个int的样式反馈回来;处理的结果可能是一段比较长的数据,这个最好是string类型反馈回来。经过一些实验,目前得到以下的解决方案。



 



a、传递int:



由于int是一个系统默认结构,也就是无论c++还是csharp还是clr中,都有这样的基本的结构,所以基本上不需要转化,一方面,我们可以直接将int作为参数传递进入函数,也可以作为返回值进行反馈。但是一般来说,我们肯定希望是能够传递参数地址,这样可以得到”修改参数“目的:



这样操作,clr中这样定义



//2.引用传递int
         int GOClrClass : :allTest( int a, int b,  int * c);



并实现



int  GOClrClass
:
:allTest(
int a,
int b,
int
* c)

{


*c
=a
+b;


return
*c;

}


那么在Csharp调用过程中,最重要的一步,就是需要给这个int c一个固定的内存地址,所以需要这样操作


unsafe

{


int
* value
= stackalloc
int[
1];

value[
0]
=
0;


int iret
= client.allTest(
2,
3, value);

}


需要注意, 由于有个int*类型参数,在C#里指针属于不安全代码,因此使用unsafe关键字将涉及到指针的代码包括起来,在工程属性里设置允许使用不安全代码。定义int指针需要使用stackalloc关键字,创建一个int数组,对数组赋值后,将指针传递给类函数。



其实,这里就是直接强制地将int的地址传递过去,因为它简单嘛。



 



b、传递string :



 



string类型复杂许多,因为它不仅仅是char[]的集合,肯定还包括许多其它的东西。复杂度关系,这里我没有实现”引用传值“,但是能够得到一种解决方法。



基本上来说,c#中的std::string 和clr中的system::String^之间存在直接转换关系,估计在clr翻译的过程中就是直接翻译的,所以可以这样来做:



 



clr处程序编写



 


System
:
:String
^ GOClrClass
:
:allTestStr(System
:
:String
^ inputStr)

{

System
:
:String
^ retstr
=
"fsdfsdf";


return retstr;

}


 



c#处程序编写



 



    string   s  =  client . allTestStr ( "abcdefg" );



 



能够直接将string传递到clr中(比如直接传递图片地址),并且返回string结果。应该还是有一定作用的。



 



感谢阅读至此,希望有所帮助。



 



 











标签:img,int,基本原理,代码,托管,C++,GOCW,CLR
From: https://blog.51cto.com/jsxyhelu2017/5968080

相关文章

  • 《LEARNING HOMOPHILIC INCENTIVES IN SEQUENTIAL SOCIAL DILEMMAS》 2022-ICLR
    在序贯社会困境中学习同质性激励总结:由于在类似harvest这种困境中,带有惩罚功能的智能体惩罚他人会付出代价,由此可能引发第二层级的搭便车行为,即都不想进行惩罚动作付出小......
  • MobTech秒验(一键登录)的基本原理
    MobTech秒验是一款帮助开发者实现一键验证功能的产品,从根源上降低企业验证成本,有效提高拉新转化率,降低因验证带来的流失率,3秒完成手机号验证(一键登录)。因其各种优势被用户......
  • 时钟系统,Linux系统中芯片时钟的基本原理
    第一:时钟系统分析   每款芯片都有自己的主频,有的时候需要把主频设置到合适的值,才能发挥它的最大性能。     可以看出,开发板的系统时钟来源于两个部分:32.7......
  • AOL开发框架:EBS功能安全性基本原理
    为了方便管理,分类维护,EBS在"用户"和"Forms"之间加了几个层次。考察如下过程:1、 "用户"如sysadmin登录,系统验证其用户名/密码2、 如果OK,系统列出其拥有的所有角色,在EBS中......
  • Git 基本原理与使用指南
    1、创建新仓库:创建新文件夹,打开,然后执行​​​gitinit​​以创建新的git仓库。2、检出仓库:执行如下命令以创建一个本地仓库的克隆版本:​​​gitclone/path/to/reposi......
  • MySQL HA(High Availability) 数据库高可用工具Orchestrator 基本原理
    目录1.实例探测(instancesdiscover/poll)2.故障分析(failureanalysis)3.故障恢复(failurerecovery)4.总结Orchestrator是一款开源的MySQL数据库高可用HA工具。它稳定可靠,......
  • Docker安装和基本原理及镜像管理
    一、Docker概述1.1IT架构的演进:裸金属→虚拟机→容器→函数化、代码化   云计算涌现出很多改变传统IT架构和运维方式的新技术,比如虚拟机、容器、微服务......
  • HybridCLR热更新方案
    Hybrid指的是混合开发,CLR指的是公共语言运行库(CommonLanguageRuntime)->托管代码执行核心中的引擎。前身叫做huatuogit示例项目地址为https://github.com/focus-creat......
  • ​Docker网络实现的基本原理
    Docker的网络实现基本原理是利用了Linux的网络命令空间和虚拟网络设备,因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将......
  • Docker网络实现的基本原理
    Docker的网络实现基本原理是利用了Linux的网络命令空间和虚拟网络设备,因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包......