这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
1、VLC代码封装
1.1 首先需要配置可使用 VLC 正常播放的 QT(C++)工程,配置过程可参考我之前的一篇文章QT + VS2015 ,获取VLC每一帧并渲染到Qwidget。
1.2 首先假设我们已经对 VLC 的 api 进行了简单的基础封装,如下:
#pragma once
#include <memory>
#include <basetsd.h>
typedef SSIZE_T ssize_t;
#include "vlc/vlc.h"
#include <mutex>
struct libvlc_media_track_info_t;
struct libvlc_media_t;
struct libvlc_instance_t;
struct libvlc_media_player_t;
struct libvlc_event_t;
class context;
enum MediaState {
NothingSpecial = 0,
Opening = 1,
Buffering = 2,
Playing = 3,
Paused = 4,
Stopped = 5,
Ended = 6,
Error = 7
};
class TestVlcVideo
{
public:
TestVlcVideo();
void init( std::function<void(int)> eventCallback);
void setHwnd(const int64_t iHwnd) ;
bool loadMedia(const char* &url) ;
int play() ;
void pause() ;
void stop() ;
void setRatio(const char* &ratio) ;
int getVolume() ;
int setVolume(const int volume) ;
int getMediaState() ;
libvlc_instance_t * getVlcInstance();
libvlc_media_player_t * getVlcMediaPlayer();
private:
static void vlcEvents(const libvlc_event_t *ev, void *param);
static libvlc_instance_t *m_instance;
libvlc_media_player_t *m_mediaPlayer = nullptr;
int64_t m_durationMS;
std::function<void(int)> m_eventCallback;
MediaState m_currentMediaState;
};
上面 static 声明的 m_instance 是为了优化效率,不必每次播放视频的时候都新建。
这是第二步工作。
1.3 接下来第三步就需要封装真正的 DLL 了,向C#暴露的也是这个类里面的方法。
#pragma once
typedef int(*CallBackMediaState)(int);
#ifdef DLLVLC_EXPORTS // 用来导出函数
#define DLLVLC_API __declspec(dllexport)
#else // 用来标识为导入函数,对于引用该头文件的外部模块来说dllimport这个标记对编译优化有作用
#define DLLVLC_API __declspec(dllimport)
#endif
#include "Testvlcvideo.h"
namespace TestVLCDLL {
extern "C" {
/*
* @brief VLC Instance和Player实例初始化
* @param CallBackMediaState callback 回调函数为媒体播放状态
* @return 每次vlcInit会返回一个VLC的Player ID,此ID唯一,后面的接口都需要此ID找到对应的Player
*/
DLLVLC_API int vlcInit(CallBackMediaState callback);
/*
* @brief VLC 媒体加载接口
* @param int index PlayerID
* @param const char *path 媒体路径
*/
DLLVLC_API bool vlcLoad(int index, const char *path);
/*
* @brief 设置句柄,如不设置则为默认窗口播放
* @param const int64_t iHwnd windows窗口句柄
*/
DLLVLC_API bool vlcSetHwnd(int index,const int64_t iHwnd);
DLLVLC_API bool play(int index);
DLLVLC_API bool pause(int index);
DLLVLC_API bool stop(int index);
/*
* @brief 设置播放窗口比例
* @param 形如 16:9 4:3 等字符串
*/
DLLVLC_API bool setRatio(int index,const char* ratio);
/*
* @brief 设置媒体播放音量
*/
DLLVLC_API bool setVolume(int index, int volume);
/*
* @brief 获取媒体总时长
*/
DLLVLC_API int64_t getMediaLength(int index);
/*
* @brief 获取当前播放状态
*/
DLLVLC_API int getMediaState(int index);
/*
* @brief 销毁VLC Player
*/
DLLVLC_API bool vlcDisponse(int index);
}
}
首先在最开始定义了 CallBackMediaState 回调函数,对应C++ 层使用函数指针和std::function 都可以。然后使用 DLLVLC_EXPORTS 指示本类为导出类,然后再使用 DLLVLC_API 宏定义导出函数,这些方法都是 dll 暴露给外部调用的方法。
1.4
// DLLVLC.cpp : 定义 DLL 应用程序的导出函数。
#define DLLVLC_EXPORTS
#include "DLLVLC.h"
#include "Testvlcvideo.h"
#include <iostream>
#include <map>
#include <mutex>
#include <atomic>
std::map<int, TestVlcVideo*> g_mapVLC;
std::atomic_int g_iIndex = 0;
std::mutex g_mt;
DLLVLC_API int TestVLCDLL::vlcInit(CallBackMediaState callback)
{
//如果是初次调用,则初始化instance,否则复用instance
std::lock_guard<std::mutex> l(g_mt);
++g_iIndex;
TestVlcVideo *vlcVideo = new TestVlcVideo;
g_mapVLC.emplace(g_iIndex, vlcVideo);
g_mapVLC.at(g_iIndex)->init(callback);
return g_iIndex;
}
DLLVLC_API bool TestVLCDLL::play(int index)
{
std::lock_guard<std::mutex> l(g_mt);
TestVlcVideo *vlcVideo = g_mapVLC.at(index);
if (nullptr == vlcVideo)
{
return false;
}
vlcVideo->play();
return true;
}
.......
因为我们采用的是导出接口方法,而不是导出类(导出类比较麻烦,自己测试未能成功),因此在制作 dll 库时,使用静态 map 保存相关实例,使用对应的 init方法和 dispose 方法借助 id 参数创建和销毁对象。
1.5 下来再看下我们第一段代码的 cpp 文件,就是 vlc 简单封装的具体实现:
#include "Testvlcvideo.h"
#include <iostream>
libvlc_instance_t *TestVlcVideo::m_instance = nullptr;
TestVlcVideo::TestVlcVideo()
: m_mediaPlayer(nullptr)
, m_durationMS(0)
, m_eventCallback(nullptr)
{
}
void TestVlcVideo::init(std::function<void(int)> eventCallback)
{
getVlcInstance();
{
getVlcMediaPlayer();
libvlc_event_manager_t *em = libvlc_media_player_event_manager(m_mediaPlayer);
{
libvlc_event_attach(em, libvlc_MediaPlayerPlaying, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerPaused, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerStopped, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerNothingSpecial, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerOpening, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerBuffering, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerEndReached, vlcEvents, this);
libvlc_event_attach(em, libvlc_MediaPlayerPositionChanged, vlcEvents, this);
}
m_eventCallback = std::move(eventCallback);
}
}
void TestVlcVideo::setHwnd(const int64_t iHwnd)
{
libvlc_media_player_set_hwnd(m_mediaPlayer, (void *)iHwnd);
}
bool TestVlcVideo::loadMedia(const char* &url)
{
libvlc_media_t *m_media = nullptr;
std::string url_ = url;
if (url_.find("://") == std::string::npos)
{
m_media = libvlc_media_new_path(getVlcInstance (), url);
}
else
{
m_media = libvlc_media_new_location(getVlcInstance(), url);
}
if (nullptr == m_media)
{
m_currentMediaState = MediaState::Error;
return false;
}
libvlc_media_player_set_media(getVlcMediaPlayer (), m_media);
libvlc_media_parse(m_media);
m_durationMS = libvlc_media_get_duration(m_media);
libvlc_media_release(m_media);
return true;
}
libvlc_instance_t * TestVlcVideo::getVlcInstance()
{
if (nullptr == m_instance)
{
m_instance = libvlc_new(0, NULL);
}
return m_instance;
}
libvlc_media_player_t * TestVlcVideo::getVlcMediaPlayer()
{
if (nullptr == m_mediaPlayer)
{
m_mediaPlayer = libvlc_media_player_new(m_instance);
}
return m_mediaPlayer;
}
int TestVlcVideo::play()
{
return libvlc_media_player_play(m_mediaPlayer);
}
void TestVlcVideo::pause()
{
if(libvlc_media_player_is_playing(m_mediaPlayer))
{
libvlc_media_player_set_pause(m_mediaPlayer, 1);
}
else
{
libvlc_media_player_set_pause(m_mediaPlayer, 0);
}
}
到这儿,一般情况下我们还需要配置 def 文件,以避免导出的函数名被增加额外的信息,而不是简短的“play”等。但是可以看到我们在所有的导出函数前增加了“extern C ”标识。意思是这些函数按照 C 标准进行编译,由于C++ 的函数重载,再加上各个编译器的不同,导致编译而出的函数名被(mangled name),且各不相同,但是C不支持重载,因此采用统一的编译规定,同时也可以保证此函数被 C 正确调用,所以我们就无需写 def 文件也可以保证函数名不被破坏。
上面的代码后面我会上传demo。
2、C# 调用
上面简要说完了 C++ 端关于 DLL 的封装,再总结一下大概就是这4点:
-
至少需要两个文件,一个是自己对具体实现的封装类,一个是导出方法文件,本文中我们没有使用类,而是直接导出函数。
-
回调函数像这样 typedef int(*CallBackMediaState)(int); 去定义。
-
导出文件添加宏 dllexport
#ifdef DLLVLC_EXPORTS // 用来导出函数
#define DLLVLC_API __declspec(dllexport)
#else // 用来标识为导入函数,对于引用该头文件的外部模块来说dllimport这个标记对编译优化有作用
#define DLLVLC_API __declspec(dllimport)
#endif
- 导出函数添加 extern "C" DLLVLC_API 声明。
2.1 C# 回调函数声明与定义
[DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcInit",
SetLastError = true,
CharSet = CharSet.Ansi,
ExactSpelling = false,
CallingConvention = CallingConvention.Cdecl)]
public extern static int vlcInit(DllcallBack pfun);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int DllcallBack(int MediaState);
C# 的回调函数即为委托,需要提前定义委托 DllcallBack ,然后我们假定它是被 C++ 认可的,作为参数传入 vlcInit。在下面我们需要写此委托函数具体的实现:
public static int CsharpCall(int MediaState)
{
Console.WriteLine(MediaState);
return MediaState;
}
使用的时候:
static int index;
static void Main(string[] args)
{
DllcallBack mycall;
mycall = new DllcallBack(Program.CsharpCall);
index = vlcInit(mycall);
......
}
经过验证,此种方式的回调函数能被 C++ 承认,对应于C++的 std::function。
2.2 C# 导出普通函数调用
[DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcLoad",
CallingConvention = CallingConvention.Cdecl)]
public extern static bool vlcLoad(int index, string path);
[DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcSetHwnd",
CallingConvention = CallingConvention.Cdecl)]
public extern static bool vlcSetHwnd(int index, int iHwnd);
上面是 C# 关于普通导出函数的加载方法,在 main 函数中直接进行调用即可。
static int index;
static void Main(string[] args)
{
DllcallBack mycall;
mycall = new DllcallBack(Program.CsharpCall);
index = vlcInit(mycall);
Console.WriteLine(vlcLoad(index, @"D:\1.mp4"));
Console.WriteLine(getMediaLength(index));
play(index);
setRatio(index,"16:9");
其实 C# 端的调用还是比较简单的,上面的方式是采用静态加载的方式,需要将C++ 的 dll 放到 C# 工程 bin 目录下,而动态加载的方式笔者未进行尝试。
整个过程就完成了,使用 C++ 封装的方式可以使用一些只支持 C++,只有 C++ API 的强大库,也可以防止反编译,还可以使代码更好的分层等。
下面附上demo链接,有需要的小伙伴可下载运行(VS2015)。