在非托管模块中实现你比较重要的算法,然后通过 CLR 的平台互操作,来使托管代码调用它,这样程序仍然能够正常工作,但对非托管的本地代码进行反编译,就很困难。

 

最直接的实现托管与非托管编程的方法就是使用C++/CLI

介绍

项目存档一直是企业的采用的做法,而是事实证明他们也是对的!对于一个程序员,这是几千men-days的工作量。为什么不开发一小段代码去重新利用那段代码,项目。
现在提供了一个渐渐的转向C#的新技术: 使用托管与非托管的混合编程。这是一个可行的方案在top-down issue(from UI to low-level layers)or bottom-up(from low-level to UI)案例。
本文目的就是通过两个简单的例子来说明怎么一起使用这两种技术:
* 在非托管中调用托管代码。
* 在托管中调用非托管代码。

非托管代码中调用托管函数


这个例子主要展示了在非托管代码(C++)中调用使用托管(C#)代码实现类,通过托管代码实现"mixed code"DLL 来导出API。

单一的非托管代码

以下是一个控制台程序

#include "stdafx.h"
#include <iostream> using namespace std; #ifdef _UNICODE
#define cout wcout
#define cint wcin
#endif int _tmain(int argc, TCHAR* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv); SYSTEMTIME st = {0};
const TCHAR* pszName = _T("John SMITH"); st.wYear = 1975;
st.wMonth = 8;
st.wDay = 15; CPerson person(pszName, &st); cout << pszName << _T(" born ")
<< person.get_BirthDateStr().c_str()
<< _T(" age is ") << person.get_Age()
<< _T(" years old today.")
<< endl;
cout << _T("Press ENTER to terminate...");
cin.get(); #ifdef _DEBUG
_CrtDumpMemoryLeaks();
#endif return 0;
}

这段代码没有什么特殊的,这只是个再普通不过的非托管代码。

单一的托管代码

这是个典型的使用C#实现的装配器

using System;

namespace AdR.Samples.NativeCallingCLR.ClrAssembly
{
public class Person
{
private string _name;
private DateTime _birthDate; public Person(string name, DateTime birthDate)
{
this._name = name;
this._birthDate = birthDate;
} public uint Age
{
get
{
DateTime now = DateTime.Now;
int age = now.Year - this._birthDate.Year; if ((this._birthDate.Month > now.Month) ||
((this._birthDate.Month == now.Month) &&
(this._birthDate.Day > now.Day)))
{
--age;
} return (uint)age;
}
} public string BirthDateStr
{
get
{
return this._birthDate.ToShortDateString();
}
} public DateTime BirthDate
{
get
{
return this._birthDate;
}
}
}
}

正如所见,这这是个单一的CLR

托管与非托管混合编程部分

这部分是最重要,也是最难的。VisualStudio环境提供了一些头文件来帮助开发者链接这些关键词。

#include <vcclr.h>

但是,并非就到这儿就结束了。我们还需要小心涉及的一些陷阱,尤其是是CLR(托管代码)和native(非托管代码)一些关键词之间数据的传递。
以下是个类的头文件输出一个托管的部分

#pragma once 

#ifdef NATIVEDLL_EXPORTS
#define NATIVEDLL_API __declspec(dllexport)
#else
#define NATIVEDLL_API __declspec(dllimport)
#endif #include <string> using namespace std; #ifdef _UNICODE
typedef wstring tstring;
#else
typedef string tstring;
#endif class NATIVEDLL_API CPerson
{
public:
// Initialization
CPerson(LPCTSTR pszName, const SYSTEMTIME* birthDate);
virtual ~CPerson(); // Accessors
unsigned int get_Age() const;
tstring get_BirthDateStr() const;
SYSTEMTIME get_BirthDate() const; private:
// Embedded wrapper of an instance of a CLR class
// Goal: completely hide CLR to pure unmanaged C/C++ code
void* m_pPersonClr;
};

强调一点,尽量在头文件里保证只有非托管代码,混合编程在cpp中去实现,数据的传递。比如: 应该尽量避免使用vcclr.h中的函数, 进行混合编程。这就是为什么定义一个void指针来包装CLR对象。
一个神奇的大门,就这样打开了。。。
正如我说的那样,神奇的事就从包含一个vcclr.h头文件开始。但是,需要使用CLR编码语言和使用一些复杂的类型(例如:strings, array, etc):

using namespace System;
using namespace Runtime::InteropServices;
using namespace AdR::Samples::NativeCallingCLR::ClrAssembly;

当然,需要申明一些使用的本地装配器。
首先,我们来看这个类的构造器:

CPerson::CPerson(LPCTSTR pszName, const SYSTEMTIME* birthDate)
{
DateTime^ dateTime = gcnew DateTime((int)birthDate->wYear,
(int)birthDate->wMonth,
(int)birthDate->wDay);
String^ str = gcnew String(pszName);
Person^ person = gcnew Person(str, *dateTime);
// Managed type conversion into unmanaged pointer is not
// allowed unless we use "gcroot<>" wrapper.
gcroot<Person^> *pp = new gcroot<Person^>(person);
this->m_pPersonClr = static_cast<void*>(pp);
}

在非托管代码里允许使用一个指针指向一个托管的类,但是我们并不想直接到处一个托管的API给用户。
所以, 我们使用了一个void指针来封装这个对象,一个新的问题又出现了:我们是不被允许直接用非托管指针指向托管类型的。这就是为什么我们会使用gcroot<>模板类。
需要注意怎么使用指针指向托管代码时需要加上^字符;这意味我们使用一个引用指针指向托管类。切记,类对象在.NET中被视为引用,当被用作函数成员时。
还需要注意一个在.NET中自动内存分配的关键词:gcnew. 这意味我们在一个垃圾收集器保护环境中分配空间,而不是在进程堆里。
有时候需要小心的是:进程堆和垃圾收集器保护环境完全不一样。我们将会看到一些封装任务还得做: 在类的析构函数:

CPerson::~CPerson()
{
if (this->m_pPersonClr)
{
// Get the CLR handle wrapper
gcroot<Person^> *pp = static_cast<gcroot<Person^>*>(this->m_pPersonClr);
// Delete the wrapper; this will release the underlying CLR instance
delete pp;
// Set to null
this->m_pPersonClr = 0;
}
}

我们使用标准的c++类型转化static_case. 删除对象会释放潜在封装的CLR对象,允许它进入垃圾回收机制。
提醒: 申明一个析构函数的原因是实现了IDisposeable 接口 和自己的Dispose()方法。
关键: 不要忘了调用Dispose()在CPerson实例中。否则,会导致内存泄露,正如在C++中不能释放(析构函数没有被调用)。
调用基本的CLR类成员十分容易,和上文类似。

unsigned int CPerson::get_Age() const
{
if (this->m_pPersonClr != 0)
{
// Get the CLR handle wrapper
gcroot<Person^> *pp = static_cast<gcroot<Person^>*>(this->m_pPersonClr);
// Get the attribute
return ((Person^)*pp)->Age;
} return 0;
}

但是,当我们必须要返回一个复杂类型时就麻烦一点,正如下面类成员:

tstring CPerson::get_BirthDateStr() const
{
tstring strAge;
if (this->m_pPersonClr != 0)
{
// Get the CLR handle wrapper
gcroot<Person^> *pp = static_cast<gcroot<Person^>*>(this->m_pPersonClr); // Convert to std::string
// Note:
// - Marshaling is mandatory
// - Do not forget to get the string pointer...
strAge = (const TCHAR*)Marshal::StringToHGlobalAuto(
((Person^)*pp)->BirthDateStr
).ToPointer();
} return strAge;
}

我们不能直接返回一个System::String 对象给非托管的string。 必须使用一下几步:
1. 得到 System::String 对象.
2. 使用 Marshal::StringToHGlobalAuto() 得到一个全局的句柄。我们在这里使用”auto”版本返回的是Unicode编码的string. 然后尽可能的转化为ANSI编码的string;
3. 最后,得到一个指针指向潜在包含对象的句柄。
以上3步就实现了替换!
阅读推荐的书关于C++/CLI, 你会看到其他的一些特别的关键词,如pin_ptr<> 和 interna_ptr<>允许你得到指针隐藏的对象, 阅读文档可以获取更多的细节。

大混合

这是个标准的例子展示了如何去创建一个本地的控制台程序使用MFC和CLR!

结论(非托管调用托管)

非托管中调用托管是一件复杂的事,这个例子很基本,普通。在例子中,你可以看到一些很复杂的考虑。希望你可以在今后混合编程中,碰到更多的其他的一些场景,获取到更多经验。

托管中调用非托管


这个例子展示了怎样在CLR(C#)中调用非托管的C++类库,通过起中间媒介的”mixed code”DLL,导出一个API来使用非托管代码。

非托管的C++DLL

DLL导出:
1. A C++ 类
2. A C-风格的函数
3. A C-风格的变量
这一段介绍对象的申明,尽管他们很简单,以至于没有必要注释。
C++ 类

class NATIVEDLL_API CPerson {
public:
// Initialization
CPerson(LPCTSTR pszName, SYSTEMTIME birthDate);
// Accessors
unsigned int get_Age(); private:
TCHAR m_sName[64];
SYSTEMTIME m_birthDate; CPerson();
};

get_Age()函数简单得计算从出生到现在的一个时间段。
导出 C 函数

int fnNativeDLL(void);

导出C变量

int nNativeDLL;

.NET 端

这里不详细的介绍这个经典的案例。

笔记1:
.NET类不能直接从非托管的C++类中继承。写一个托管C++的类嵌入到c++实体对象内部。

笔记2:
申明一个成员CPerson_person2; 会导致生成C4368编译错误(不能定义’member’ 作为一个托管类型的成员: 不支持混合类型)
这就是为什么在内部使用(在C#被视为’unsafe’)
技术文档上是这么说的:
你不能直接嵌入一个非托管的数据成员到CLR中。但是,你可以申明一个本地化类型的指针,在构造函数,析构函数, 释放托管的类里控制它的生命周期(看在Visual c++ 里有关于析构函数和终结器更多的信息)。
这就是嵌入的对象:

CPerson* _pPerson;

而不是:

CPerson person;

构造器中特殊的信息
公共的构造器有一个System::String string(托管类型)和一个SYSTEMTIME 结构体(Win32 API 类型,但是只是数值:很明显是个数据集)
这个非托管的c++ CPerson 构造函数使用了LPCTSTR string 类型的指针, 这个托管的string不能直接转化非托管的对象。
这是构造器的源代码:

SYSTEMTIME st = { (WORD)birthDate.Year,
(WORD)birthDate.Month,
(WORD)birthDate.DayOfWeek,
(WORD)birthDate.Day,
(WORD)birthDate.Hour,
(WORD)birthDate.Minute,
(WORD)birthDate.Second,
(WORD)birthDate.Millisecond }; // Pin 'name' memory before calling unmanaged code
pin_ptr<const TCHAR> psz = PtrToStringChars(name); // Allocate the unmanaged object
_pPerson = new CPerson(psz, st);

注意这里使用pin_ptr关键词来保护string可以在CRL中使用。
这个是一可以保护对象指向个内部的指针。当传递一个托管类的地址给一个非托管的的函数是很有必要的,因为地址不是在非托管代码调用时异常的改变。

总结(托管中调用非托管)

如果我们觉得在托管中导入一个非托管的比非托管中导入一个托管更为常见,写一个”intermediate assembly”是相当不容易的。
你应该确定是不是需要全部移植代码,那样是不合理的。考虑重新设计这个应用。重写托管代码可能比移植更划算。而且,最终的应用架构也是很清晰明了。

C# 托管和非托管混合编程的更多相关文章

  1. C#的托管与非托管大难点

    托管代码与非托管代码 众所周知,我们正常编程所用的高级语言,是无法被计算机识别的.需要先将高级语言翻译为机器语言,才能被机器理解和运行.在标准C/C++中,编译过程是这样的:源代码首先经过预处理器,对 ...

  2. C#的三大难点之二:托管与非托管

    相关文章: C#的三大难点之前传:什么时候应该使用C#?​C#的三大难点之一:byte与char,string与StringBuilderC#的三大难点之二:托管与非托管C#的三大难点之三:消息与事件 ...

  3. [.net 面向对象程序设计进阶] (8) 托管与非托管

    本节导读:虽然在.NET编程过程中,绝大多数内存垃圾回收由CLR(公共语言运行时)自动回收,但也有很多需要我们编码回收.掌握托管与非托管的基本知识,可以有效避免某些情况下导致的程序异常. 1.什么是托 ...

  4. C# using 三种使用方式 C#中托管与非托管 C#托管资源和非托管资源区别

    1.using指令.using + 命名空间名字,这样可以在程序中直接用命令空间中的类型,而不必指定类型的详细命名空间,类似于Java的import,这个功能也是最常用的,几乎每个cs的程序都会用到. ...

  5. 利用C#Marshal类实现托管和非托管的相互转换

    Marshal 类 命名空间:System.Runtime.InteropServices 提供了一个方法集,这些方法用于分配非托管内存.复制非托管内存块.将托管类型转换为非托管类型,此外还提供了在与 ...

  6. [转]C# 之DLL调用(托管与非托管)

    每种编程语言调用DLL的方法都不尽相同,在此只对用C#调用DLL的方法进行介绍.首先,您需要了解什么是托管,什么是非托管.一般可以认为:非托管代码主要是基于win 32平台开发的DLL,activeX ...

  7. 有关 Azure IaaS VM 磁盘以及托管和非托管高级磁盘的常见问题解答

    本文将对有关 Azure 托管磁盘和 Azure 高级存储的一些常见问题进行解答. 托管磁盘 什么是 Azure 托管磁盘? 托管磁盘是一种通过处理存储帐户管理来简化 Azure IaaS VM 的磁 ...

  8. NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

    在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时 ...

  9. Oracle Data Provider for .NET的使用(托管与非托管(一))

    目录 简单的概述 简单的使用 非托管系统要求 托管驱动系统要求 其它的注意事项 ODP.NET版本说明 安装ODP.NET 安装非托管驱动 非托管驱动绿色配置 简单的概述 ODP.NET的含义是 Or ...

随机推荐

  1. MTP in Android详解

    MTP in Android详解 最近好长一段时间没有做笔记了,今天主要学习一下MTP相关的知识. MTP的全称是Media Transfer Protocol(媒体传输协议),它是微软公司提出的一套 ...

  2. UE4 材质切换(带动画效果)

    先看效果图:小木块掉到地板上(小木块本身会消失掉),地板就开始了动效材质切换.引擎版本用的是4.11.2 方法步骤: 首先在UE4内容浏览器中新建一个材质. 第一步要实现一个扫光的效果,如下图. 实现 ...

  3. Swift—泛型(上)

    1.泛型 泛型是一种非常灵活的语法,允许程序在函数.枚举.结构体.类中定义类型形参,这种类型形参实际代表的类型是动态改变的——程序可以等到真正使用这些函数.枚举.结构体.类时才为这些类型形参传入实际的 ...

  4. tableView优化性能

    在iOS应用中,UITableView应该是使用率最高的视图之一了.iPod.时钟.日历.备忘录.Mail.天气.照片.电话.短信. Safari.App Store.iTunes.Game Cent ...

  5. Express4.x常用API(一):res

    最近在学习NodeJS,用到了express,看着官网上的API手册,打算把其中比较常用到的API根据自己理解翻译一下,方便自己学习使用. 该篇打算用来记录下express中res. 由于水平有限,希 ...

  6. php判断请求类型 ajax、get还是post类型

    1.通过PHP获取预定义变量中的XMLHttpRequest判读. 首先你必须使用jquery或Js发送ajax请求,通过jquery发送的$.ajax, $.get or $.post方法请求网页内 ...

  7. Android 的 DatePicker、TimePicker或NumberPicker

    布局文件加上这个就可以,去除日期选择器.时间选择器或数值选择器的可编辑状态. android:descendantFocusability="blocksDescendants" ...

  8. Opencv-Python 学习

    加载一个灰度图,显示图片,按下’s’键保存后退出,或者按下 ESC 键退出不保存. import numpy as np import cv2 img = cv2.imread('linux.png' ...

  9. SQL 行转列和列转行2

    DECLARE @T TABLE (columnName varchar(100) NOT NULL PRIMARY KEY); INSERT INTO @T SELECT columnName fr ...

  10. 64位系统里的IIS运行32位ODP.NET的方法

    在64位Win7里的IIS里部署使用了ODP.NET的网站,Oracle的版本是11.20.3.20.直接部署会提示错误:在64位环境里使用了32位的程序.自己折腾了两天,最后才从别人的博客里找到解决 ...