正文开始:
C#是一种托管(managed)的面向对象语言,而C++默认为非托管(native)。因此由C++生成的dll文件也应分为
托管C++dll与非托管C++dll两种。以下将分别说明。
1. 生成与使用托管C++dll
什么是托管C++?
托管是.NET的一个专门概念,它倡导一种新的编程理念,因此我们完全可以把“托管”视为“.NET”。由托管概念所引发
的C++应用程序包括托管代码、托管数据和托管类三个组成部分。
托管代码
.Net环境提供了许多核心的运行(RUNTIME)服务,比如异常处理和安全策略。为了能使用这些服务,必须要给运行环
境提供一些信息代码(元数据),这种代码就是托管代码。所有的C#、VB.NET、JScript.NET默认时都是托管的,但
Visual C++默认时不是托管的,必须在编译器中使用命令行选项(/CLR)才能产生托管代码。
托管数据
与托管代码密切相关的是托管数据。托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。默认情况下,
C#、Visual Basic 和 JScript.NET 数据是托管数据。不过,通过使用特殊的关键字,C# 数据可以被标记为非托管数
据。Visual C++数据在默认情况下是非托管数据,即使在使用 /CLR 开关时也不是托管的。
托管类
尽管Visual C++数据在默认情况下是非托管数据,但是在使用C++的托管扩展时,可以使用“__gc”关键字将类标记为
托管类。就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。另外,一个托管类也完全可以成为
.NET 框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的C++类可以
从Visual Basic类继承等。但同时也有一些限制,如托管类只能从一个基类继承等。
由上可以看到,所谓的托管C++与非托管C++主要的区别在于二者安全策略与内存管理策略的不同。托管的C++采用
与Java类似的内存管理,简单的例子就是托管中只有New而没有delete,对于习惯了非托管C++的人来说接受这一点
是一个由繁到简的过程,比较容易的过程。另外需要强调一点,在托管C++中只有一种数据类型,即类型,类型包括
值类型、引用类型与指针类型。我们常用的类可以是引用类型或者是值类型,而结构、枚举与基本类型都是值类型。
二者区别于数据存储位置不同,前者位于托管堆(heap)上,后者存储在堆栈(stack)上。为了与非托管C++区分,
在托管C++中使用gcnew关键字,并用^取代*,例如,有托管类TestManaged与非托管类TestNative,分别使用如下
实例化。
TestManaged ^t = gcnew Testmanaged()//托管类
TestNative * t = new TestNative()//非托管类
指针类型很少使用,只有在对非托管类进行封装时会用到。
封装非托管C++类
假设现有一非托管类avplayer,这是一个真实的类库,类头文件如下
//
// avplayer.h
// ~~~~~~~~~~
//
#ifndef __AVPLAYER_H__
#define __AVPLAYER_H__
#ifdef API_EXPORTS
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif
#pragma once
// 打开媒体类型.
#define MEDIA_TYPE_FILE 0//rtp
#define MEDIA_TYPE_BT 1
#define MEDIA_TYPE_HTTP 2//ts
#define MEDIA_TYPE_RTSP 3
#define MEDIA_TYPE_YK 4
// 渲染模式.
#define RENDER_DDRAW 0
#define RENDER_D3D 1
#define RENDER_OGL 2
#define RENDER_SOFT 3
#define RENDER_Y4M 4
class player_impl;
// avplayer封装类.
class EXPORT_API avplayer
{
public:
avplayer(void);
~avplayer(void);
public:
// 创建窗口, 也可以使用subclasswindow附加到一个指定的窗口.
HWND create_window(const char *player_name);
// 打开一个媒体文件
// movie 文件名.
// media_type 表示打开的媒体类型.
// render_type 表示播放渲染模式, 默认是ddraw渲染.
// 注意, 这个函数只打开文件, 但并不播放, 重新打开文件前, 必
// 须关闭之前的媒体文件, 否则可能产生内存泄漏! 另外, 在播放
// 前, avplayer必须拥有一个窗口.
BOOL open(const char *movie, int media_type, int render_type = RENDER_SOFT);
// 开始播放视频.
// fact 表示播放视频的起始位置, 按视频百分比计算, 默认从文件头开始播放.
// index 播放索引为index的文件, index表示在播放列表中的位置计数, 从0开始计
// 算, index主要用于播放多文件的bt文件, 单个文件播放可以使用直接默认为0而不
// 需要填写参数.
//BOOL play(double fact = 0.0f, int index = 0);
BOOL play();/*..................*/<pre name="code" class="cpp">private:
player_impl *m_impl;
};
#endif // __AVPLAYER_H__
</pre><pre>
现要实现对上述类的封装,主要涉及数据类型转换,关键点在注释处给出.
数据转换对照表:
// avplayer_cplus_managed.h
#include "avplayer.h"
#pragma comment(lib,"avcore.lib")
#pragma once
using namespace System;//托管类中应广泛使用命名空间
namespace avplayer_cplus_managed {//必须定义命名空间
public ref class avplayer_managed//声名为ref class类型(引用类型)
{
// TODO: 在此处添加此类的方法。
public:
avplayer_managed()
{
m_avplayer_c = new avplayer;//由于avplayer类为非托管类,所以只能使用指针类型。
};
~avplayer_managed()
{
delete m_avplayer_c;//new出的必须delete,否则内存泄漏
};
enum class MediaType//自定义枚举,也是值类型
{
RTP = 0,
TS = 2
};
enum class RenderType
{
DDRAW = 0,
D3D,
OGL,
SOFT,
Y4M
};
public:
// 创建窗口, 也可以使用subclasswindow附加到一个指定的窗口.
System::IntPtr create_window(System::String ^player_name);HWND类型对应IntPtr
// 打开一个媒体文件
// movie 文件名.
// media_type 表示打开的媒体类型.
// render_type 表示播放渲染模式, 默认是ddraw渲染.
// 注意, 这个函数只打开文件, 但并不播放, 重新打开文件前, 必
// 须关闭之前的媒体文件, 否则可能产生内存泄漏! 另外, 在播放
// 前, avplayer必须拥有一个窗口.
System::Boolean open(System::String ^ movie, MediaType media_type, RenderType render_type/* = RENDER_SOFT*/);//String为托管类,因此使用^代替*
// 开始播放视频.
// fact 表示播放视频的起始位置, 按视频百分比计算, 默认从文件头开始播放.
// index 播放索引为index的文件, index表示在播放列表中的位置计数, 从0开始计
// 算, index主要用于播放多文件的bt文件, 单个文件播放可以使用直接默认为0而不
// 需要填写参数.
//BOOL play(double fact = 0.0f, int index = 0);
System::Boolean play();使用Boolean代替BOOL
/*...................*/
private:
!avplayer_managed()
{
delete m_avplayer_c;
}
private:
avplayer* m_avplayer_c;
};
}
实现文件
// 这是主 DLL 文件。
//<span style="font-family: Arial, Helvetica, sans-serif;">avplayer_cplus_managed.cpp</span>
#include "stdafx.h"
#include "avplayer_cplus_managed.h"
System::IntPtr avplayer_cplus_managed::avplayer_managed::create_window(System::String ^player_name)
{
char *t = (char *)(void*)Marshal::StringToHGlobalAnsi(player_name);//将String转换为char*
System::IntPtr hwnd(m_avplayer_c->create_window(t));//HWND可直接转换为IntPtr
Marshal::FreeHGlobal(IntPtr(t));
return hwnd;
}
System::Boolean avplayer_cplus_managed::avplayer_managed::open(System::String ^ movie, MediaType media_type, RenderType render_type/* = RENDER_SOFT*/)
{
char *t = (char *)(void*)Marshal::StringToHGlobalAnsi(movie);
System::Boolean b = m_avplayer_c->open(t, (int)media_type, (int)render_type);
Marshal::FreeHGlobal(IntPtr(t));
return b;
}
System::Boolean avplayer_cplus_managed::avplayer_managed::play()
{
return m_avplayer_c->play();
}
上述工作做好后,avplayer_cplus_managed已经是托管类,将上述工程生成CLR DLL文件。在C#项目把DLL文件直接添加引用,就可调用avplayer_cplus_managed类。
C#项目中调用源码
using avplayer_cplus_managed;//使用avplayer命名空间
namespace avplayer_csharp
{
public partial class mediaPlayer : Form
{
private avplayer_managed m_avplayer;
private Boolean m_bPlaying;
private Form m_childForm;
public mediaPlayer()
{
m_avplayer = new avplayer_managed();//实例化avplayer
m_bPlaying = false;
InitializeComponent();
}
private void start_Click(object sender, EventArgs e)
{
bool b = m_avplayer.open("dvs6467_p2p.sdp",
avplayer_managed.MediaType.RTP,avplayer_managed.RenderType.SOFT);
if(b)
{
ThreadStart startPlayer = new ThreadStart(playThread); //线程起始设置
Thread playerThread = new Thread(startPlayer); //实例化要开启的新类
playerThread.Start();//开启线程
m_bPlaying = true;
}
}
//播放器线程
private void playThread()
{
if (m_avplayer.play())//成功则等待,不成功返回
m_avplayer.wait_for_completion();
else
return;
}
}
2 直接使用非托管C++DLL
这种方法缺点是只能使用DLL中export的函数。主要步骤:
[DllImport("Dll文件名"),entryPoint = "函数名",CharSet]
public static extern 函数声名
网上多为此类方法,可自行查找
以下转自http://blog.csdn.net/zhangzxy161723/article/details/4132853
C#语言使用方便,入门门槛较代,上手容易,并且语法与C,java有很类似的地方,IDE做的也好,通用性好,是MS下一代开发的主要力量.但是其开源代码较少,类库不是十分完美,在架构方面还有一些需要做的工作.C++写的程序占用内存较小,直接对内存或者文件操作,因此一些关键的步骤或者一些最内层的循环在一定程序上还需要依赖C++.
对于一直做c++开发的人员,使用c#来做界面将是一个很好的选择。
c#调用DLL用两种方式:直接导出函数;使用def文件导出函数。
1.下面介绍一个不采用def导出文件的简单例子
第一步,用C++做一个可以导出函数的dll(不采用def文件)
cxyMath.h
//在这里定义导出哪一些函数
class MyMathFuncs
{
public:
// Returns a + b
static __declspec(dllexport) double Add(double a, double b);
// Returns a - b
static __declspec(dllexport) double Subtract(double a, double b);
// Returns a * b
static __declspec(dllexport) double Multiply(double a, double b);
// Returns a / b
// Throws DivideByZeroException if b is 0
static __declspec(dllexport) double Divide(double a, double b);
};
cxyMath.cpp的实现就很简单了,代码附在上传的文件中,在这里就不贴代码了,编译成dll后,拷贝dll,lib文件到C#的工程中的debug的目录下(如果你写的是release版,请将dll,lib拷贝到relase文件夹下)
第二步:找出导出的函数名
写成如下形式,方便CS的调用
不采用def文件导出的函数名有些奇怪,但还是可以看出函数的层次,?函数名@类名@命名空间@@******,
找函数名可以使用ultraedit32,打开lib文件,就可看到了
另外,我们可以使用dllexp这个程序找出导出的函数(这个程序见附录)
[DllImport("cppdll.dll",EntryPoint="?Divide@MyMathFuncs@MathFuncs@@SANNN@Z",CharSet = CharSet.Auto)]
public static extern double Divide(double a,double b);
[DllImport("cppdll.dll",EntryPoint="?Multiply@MyMathFuncs@MathFuncs@@SANNN@Z",CharSet = CharSet.Auto)]
public static extern double Multiply(double a,double b);
第三步:调用
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show(Multiply(12,13).ToString());
}
2.采用def 文件导出函数
第一种方式比较简单,但是找一个dll函数的入口地址,还是比较麻烦的,并且,入口地址没有太大的意义,不直观,不好记忆
一般情况下,我们可以选择使用def文件导出函数
第一步,新建一个win32 application然后在应用程序的设置中选择动态dll,然后选择导出符号,这样,vs2003就为我们生成了一个非常完整的架子,但是美中不足的是生成的dll导出的函数也是和第一中情况一样
第二步,添加一个def文件,生成def文件的同时,vs2003自动为我们添加了这样一行,
LIBRARY win32dll
我们只要在他的下面加上我们要导出的函数就可以了.
GetAName @1
ShowMyName @2
PerfTest @3
这样经过编译我们使用dllexp查看,看到的就不再是一些没有意义的函数名了,而是我们在def中定义的文件函数名
第三步,拷贝lib,dll文件到CS工程中就可以了,
我们就不在这里一一叙述了