我想:在回答“你用什么播放器听 MP3”这个问题时,90%的人都会回答 Winamp!那么你一定用过 Winamp 的插件功能吧,正是多样化的插件使这个“老”播放器不断地焕发青春。不管新推出什么音频格式(MP4,VQF,RM...),只要插件一装就能播放。还有形形色色的可视插件,比如 Giess 等等,将音乐的节奏感表现的可谓淋漓尽致!
既然插件是用 程序 编写的,那么我们何不来一试身手,动手做它一个出来?!用过 Winamp 的人都知道,Winamp 插件是放在 Pulgin 文件 夹中一个个的 DLL(动态链接库) 文件 ,所以编写 Winamp 插件其实就是编写 Windows 的动态链接库。当然写的时候是要遵循一定的规范的(相关文档可以从 www.winamp.com 下载),在这方面,Winamp 作者 Justin Frankel 写的一个可视插件的例子可以作为我们很好的参考。下面我们就以这个例子(当然也是一个编写规范)为参考,认识一下 Winamp 可视插件的编写方法。
(下面的 程序 可从 Winamp 官方网站下载, 文件 名为 vis_minisdk.zip )
首先让我们看一下可视插件使用的数据结构(在 文件 Vis.h 中)
// 注意:
// 任何呆在前台的插件窗口都应该将按键传送给其父(WinAMP 的)窗口,以确保
// 用户仍旧可以控制 WinAMP(除非用户按了 ESC 键或者插件所指定的键)。
// 在存储配置时,配置数据应当统一存放在 <dll directory>/plugin.ini 中。
// 请将这个插件例程看作一个框架。
typedef struct winampVisModule {
char *description; // 模块描述(出现在插件选择列表框下面得下拉列表框中)
HWND hwndParent; // 父窗口------------- (由主调应用填充)
HINSTANCE hDllInstance; // 此 DLL 的实例句柄 - (由主调应用填充)
int sRate; // 采样速率 ---------- (由主调应用填充)
int nCh; // 声道数 ------------ (由主调应用填充)
int latencyMs; // 从调用 RenderFrame 到真正绘制的潜伏时间(毫秒)
// (主调应用在获取数据的时候会查看这个值)
int delayMs; // 每两次调用之间的间隔时间(毫秒)
// 数据依照各自的 Nch(声道数) 条目被填充
int spectrumNch;
int waveformNch;
unsigned char spectrumData[2][576]; // 频谱数据
unsigned char waveformData[2][576]; // 波形数据
void (*Config)(struct winampVisModule *this_mod); // 模块配置函数
int (*Init)(struct winampVisModule *this_mod); // 初始化函数(创建窗口等等)。成功返回0
int (*Render)(struct winampVisModule *this_mod); // “表演”函数。成功返回0,如返回1表示插件应该终止
void (*Quit)(struct winampVisModule *this_mod); // 模块退出函数。完成之后调用
void *userData; // 用户数据 (可选)
} winampVisModule;
typedef struct {
int version; // VID_HDRVER (当前模块的版本)
char *description; // 插件的描述(出现在选择插件对话框的插件列表框中)
winampVisModule* (*getModule)(int); // 用来获取模块结构
} winampVisHeader;
// 定义导出标识
typedef winampVisHeader* (*winampVisGetHeaderType)();
// 当前模块的版本 (0x101 == 1.01)
#define VIS_HDRVER 0x101
上面列出的是一个编写可视插件必须包含的头 文件 ,里面列出了可视插件用到的数据结构。在探讨具体插件 程序 之前,有一些概念必须搞清:一个可视插件中可以包含若干个模块(每一模块都是一种演示效果,可以在插件选择对话框中选择用哪个模块来演示),这些模块通过某种方法(后面将会看到)被 Winamp 获取,从而得到“表演”的机会。简而言之,Winamp 利用所有插件 DLL 中导出的一个统一名称的函数获得了一个插件头数据结构,然后通过此数据结构中的一个函数再去获取各个模块的信息(这个过程与 COM 的 QueryInterface() 用法有些神似,看来好的设计思想是相通的),进而利用多线程(通过 DLL View 观察得知)实现可视插件的展示。下面就是可视插件的源 程序 :
// Winamp 测试用可视插件 v1.0
// 版权所有 (C) 1997-1998, Justin Frankel/Nullsoft
// 基于此框架可自由的编写任何可视插件...
#include <windows.h>
#include "vis.h"
char szAppName[] = "SimpleVis"; // 窗口类名
// 有关配置的声明
int config_x=50, config_y=50; // 窗口在屏幕上的横纵坐标
void config_read(struct winampVisModule *this_mod); // 读配置
void config_write(struct winampVisModule *this_mod); // 写配置
void config_getinifn(struct winampVisModule *this_mod, char *ini_file); // 生成一个 .ini 文件 名
// 在需要的时候返回一个 winampVisModule,用在下面的 hdr 中。WinAMP 可由此得知插件中的模块数。
winampVisModule *getModule(int which);
// "成员"函数
void config(struct winampVisModule *this_mod); // 模块配置函数
int init(struct winampVisModule *this_mod); // 模块初始化函数
int render1(struct winampVisModule *this_mod); // 模块1 的“表演”函数
int render2(struct winampVisModule *this_mod); // 模块2 的“表演”函数
int render3(struct winampVisModule *this_mod); // 模块3 的“表演”函数
void quit(struct winampVisModule *this_mod); // 模块结束的清理函数
// 插件窗口的窗口处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
HWND hMainWnd; // 主窗口句柄
// 双缓冲数据
HDC memDC; // 内存DC
HBITMAP memBM, // 内存位图 (for memDC)
oldBM; // old bitmap (from memDC)
// 模块头部。包括模块版本,插件描述(出现在选择插件对话框的插件列表框中)和模块接口函数的地址
winampVisHeader hdr = { VIS_HDRVER, "Nullsoft Test Visualization Library v1.0", getModule };
// 第一模块 (示波器)
winampVisModule mod1 =
{
"Oscilliscope",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
0, // spectrumNch
2, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render1,
quit
};
// 第二模块 (光谱分析)
winampVisModule mod2 =
{
"Spectrum Analyser",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
2, // spectrumNch
0, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render2,
quit
};
// 第三模块 (VU meter)
winampVisModule mod3 =
{
"VU Meter",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
0, // spectrumNch
2, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render3,
quit
};
// 这是插件 DLL 中仅有的一个导出函数,用来返回插件头结构体指针,从而进一步得
// 知插件中各个模块的信息。
// 如果你正在编译 C++ 程序 ,extern "C" { 就是必要的,所以我们使用了 #ifdef。
#ifdef __cplusplus
extern "C" {
#endif
__declspec( dllexport ) winampVisHeader *winampVisGetHeader()
{
return &hdr;
}
#ifdef __cplusplus
}
#endif
// 在得到插件头结构体指针后用来获取模块信息。
// 如果一个不存在的模块被请求就返回 NULL,否则依据 'which' 来返回各可用模块(结构体指针)中的一个。
winampVisModule *getModule(int which)
{
switch (which)
{
case 0: return &mod1;
case 1: return &mod2;
case 2: return &mod3;
default:return NULL;
}
}
// 模块配置函数。通过 this_mod 可得知要配置哪个模块。
// 允许你的所有模块共用一个配置函数。
// (当然你不一非定要使用这个名字,你可以建立 config1(), config2(), 等等...)
void config(struct winampVisModule *this_mod)
{
MessageBox(this_mod->hwndParent,"This module is Copyright (c) 1997-1998, Justin Frankel/Nullsoft/n"
"-- This is just a demonstration module, it really isn't/n"
" supposed to be enjoyable --","Configuration",MB_OK);
}
// 模块初始化函数。注册窗口类,创建窗口等等。
// 这是所有模块都要做的工作,但是你也可以建立 init1() 和 init2()...
// 成功返回 0,失败返回1。
int init(struct winampVisModule *this_mod)
{
int width = (this_mod == &mod3)?256:288; // mod1 和 mod2 的宽高相等
int height = (this_mod == &mod3)?32:256; // 但是 mod3 的与另外两个不同
config_read(this_mod); // 读取配置信息
{ // 注册窗口类
WNDCLASS wc;
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = WndProc; // 窗口处理过程
wc.hInstance = this_mod->hDllInstance; // DLL 的实例句柄
wc.lpszClassName = szAppName; // 窗口类名
if (!RegisterClass(&wc))
{
MessageBox(this_mod->hwndParent,"Error registering window class","blah",MB_OK);
return 1;
}
}
hMainWnd = CreateWindowEx(
WS_EX_TOOLWINDOW|WS_EX_APPWINDOW, // 这些扩展风格建立一个好看的小窗口外框,
// 但是窗口在任务栏上有一个按钮
szAppName, // 窗口类名
this_mod->description, // 窗口标题使用模块描述
WS_VISIBLE|WS_SYSMENU, // 使窗口可见并且有一个关闭按钮
config_x,config_y, // 窗口的屏幕位置 (从配置中读取)
width,height, // 窗口的宽高 (需要在后面调整客户区尺寸)
this_mod->hwndParent, // 父窗口句柄 (WinAMP 主窗口)
NULL, // 无菜单
this_mod->hDllInstance, // DLL 的实例句柄
0); // 无窗口创建数据
if (!hMainWnd)
{
MessageBox(this_mod->hwndParent,"Error creating window","blah",MB_OK);
return 1;
}
SetWindowLong(hMainWnd,GWL_USERDATA,(LONG)this_mod); // 将窗口用户数据设为模块结构体指针
{ // 调整窗口尺寸以使得客户区等于宽乘高
RECT r;
GetClientRect(hMainWnd,&r);
SetWindowPos(hMainWnd,0,0,0,width*2-r.right,height*2-r.bottom,SWP_NOMOVE|SWP_NOZORDER);
}
// 创建双缓冲
memDC = CreateCompatibleDC(NULL);
memBM = CreateCompatibleBitmap(memDC,width,height);
oldBM = SelectObject(memDC,memBM);
// 显示窗口
ShowWindow(hMainWnd,SW_SHOWNORMAL);
return 0;
}
// 示波器模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render1(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,288,256);
// 绘制示波器
for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图
{
MoveToEx(memDC,0,(y*256)>>(this_mod->nCh-1),NULL);
for (x = 0; x < 288; x ++)
{
LineTo(memDC,x,(y*256 + this_mod->waveformData[y][x]^128)>>(this_mod->nCh-1));
}
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// 频谱分析模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render2(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,288,256);
// 绘制分析仪
for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图
{
for (x = 0; x < 288; x ++)
{
MoveToEx(memDC,x,(y*256+256)>>(this_mod->nCh-1),NULL);
LineTo(memDC,x,(y*256 + 256 - this_mod->spectrumData[y][x])>>(this_mod->nCh-1));
}
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// VU 表模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render3(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,256,32);
// 绘制 VU 表
for (y = 0; y < 2; y ++)
{
int last=this_mod->waveformData[y][0];
int total=0;
for (x = 1; x < 576; x ++)
{
total += abs(last - this_mod->waveformData[y][x]);
last = this_mod->waveformData[y][x];
}
total /= 288;
if (total > 127)
total = 127;
if (y)
Rectangle(memDC,128,0,128+total,32);
else
Rectangle(memDC,128-total,0,128,32);
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,256,32,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// 模块清除函数 (对应于 init())。销毁窗口,取消窗口类的注册。
void quit(struct winampVisModule *this_mod)
{
config_write(this_mod); // 写入配置
SelectObject(memDC,oldBM); // 删除双缓冲
DeleteObject(memDC);
DeleteObject(memBM);
DestroyWindow(hMainWnd); // 销毁窗口
UnregisterClass(szAppName,this_mod->hDllInstance); // 取消窗口类的注册
}
// 插件窗口的窗口处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE: return 0;
case WM_ERASEBKGND: return 0;
case WM_PAINT:
{ // 以双缓冲的信息更新窗口显示
PAINTSTRUCT ps;
RECT r;
HDC hdc = BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&r);
BitBlt(hdc,0,0,r.right,r.bottom,memDC,0,0,SRCCOPY);
EndPaint(hwnd,&ps);
}
return 0;
case WM_DESTROY: PostQuitMessage(0); return 0;
case WM_KEYDOWN: // 将键盘消息传递给 WinAMP 主窗口 (使其被处理)
case WM_KEYUP:
{ // 从窗口的用户数据中得到 this_mod
winampVisModule *this_mod = (winampVisModule *) GetWindowLong(hwnd,GWL_USERDATA);
PostMessage(this_mod->hwndParent,message,wParam,lParam);
}
return 0;
case WM_MOVE:
{ // 从配置中得到 config_x 和 config_y
RECT r;
GetWindowRect(hMainWnd,&r);
config_x = r.left;
config_y = r.top;
}
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
// 生成一个 <dll directory>/plugin.ini 形式的 .ini 文件 名
void config_getinifn(struct winampVisModule *this_mod, char *ini_file)
{
char *p;
GetModuleFileName(this_mod->hDllInstance,ini_file,MAX_PATH);
p=ini_file+strlen(ini_file);
while (p >= ini_file && *p != '//')
p--;
if (++p >= ini_file)
*p = 0;
strcat(ini_file,"plugin.ini");
}
void config_read(struct winampVisModule *this_mod)
{
char ini_file[MAX_PATH];
config_getinifn(this_mod,ini_file);
config_x = GetPrivateProfileInt(this_mod->description,"Screen_x",config_x,ini_file);
config_y = GetPrivateProfileInt(this_mod->description,"Screen_y",config_y,ini_file);
}
void config_write(struct winampVisModule *this_mod)
{
char string[32];
char ini_file[MAX_PATH];
config_getinifn(this_mod,ini_file);
wsprintf(string,"%d",config_x);
WritePrivateProfileString(this_mod->description,"Screen_x",string,ini_file);
wsprintf(string,"%d",config_y);
WritePrivateProfileString(this_mod->description,"Screen_y",string,ini_file);
}
正如我们前面所说,插件 程序 与主 程序 之间一定要有相互合作的规范才能正常的运作,比如约定好的函数名等等。从上面的 程序 中可以看出,Winamp 用约定好的函数名调用插件 DLL 中唯一的一个导出函数 winampVisGetHeader() 来获取一个指向插件头结构体 winampVisHeader 的指针,而后者包含的一个指针函数(*getModule)(int) 就可以根据给定参数向 Winamp 暴露模块的接口。^_^这样一来,插件的全部细节就都出现在 Winamp 面前了。因为 getModule 函数对没有实现的模块返回 NULL,所以 Winamp 就可以通过返回值确定可视插件中模块的数目了。其实上面的 程序 就是一个可视插件的编写框架,当明确了其中的规范之后就可以把精力放在“表演”函数的编写和具体实现上了。
既然插件是用 程序 编写的,那么我们何不来一试身手,动手做它一个出来?!用过 Winamp 的人都知道,Winamp 插件是放在 Pulgin 文件 夹中一个个的 DLL(动态链接库) 文件 ,所以编写 Winamp 插件其实就是编写 Windows 的动态链接库。当然写的时候是要遵循一定的规范的(相关文档可以从 www.winamp.com 下载),在这方面,Winamp 作者 Justin Frankel 写的一个可视插件的例子可以作为我们很好的参考。下面我们就以这个例子(当然也是一个编写规范)为参考,认识一下 Winamp 可视插件的编写方法。
(下面的 程序 可从 Winamp 官方网站下载, 文件 名为 vis_minisdk.zip )
首先让我们看一下可视插件使用的数据结构(在 文件 Vis.h 中)
// 注意:
// 任何呆在前台的插件窗口都应该将按键传送给其父(WinAMP 的)窗口,以确保
// 用户仍旧可以控制 WinAMP(除非用户按了 ESC 键或者插件所指定的键)。
// 在存储配置时,配置数据应当统一存放在 <dll directory>/plugin.ini 中。
// 请将这个插件例程看作一个框架。
typedef struct winampVisModule {
char *description; // 模块描述(出现在插件选择列表框下面得下拉列表框中)
HWND hwndParent; // 父窗口------------- (由主调应用填充)
HINSTANCE hDllInstance; // 此 DLL 的实例句柄 - (由主调应用填充)
int sRate; // 采样速率 ---------- (由主调应用填充)
int nCh; // 声道数 ------------ (由主调应用填充)
int latencyMs; // 从调用 RenderFrame 到真正绘制的潜伏时间(毫秒)
// (主调应用在获取数据的时候会查看这个值)
int delayMs; // 每两次调用之间的间隔时间(毫秒)
// 数据依照各自的 Nch(声道数) 条目被填充
int spectrumNch;
int waveformNch;
unsigned char spectrumData[2][576]; // 频谱数据
unsigned char waveformData[2][576]; // 波形数据
void (*Config)(struct winampVisModule *this_mod); // 模块配置函数
int (*Init)(struct winampVisModule *this_mod); // 初始化函数(创建窗口等等)。成功返回0
int (*Render)(struct winampVisModule *this_mod); // “表演”函数。成功返回0,如返回1表示插件应该终止
void (*Quit)(struct winampVisModule *this_mod); // 模块退出函数。完成之后调用
void *userData; // 用户数据 (可选)
} winampVisModule;
typedef struct {
int version; // VID_HDRVER (当前模块的版本)
char *description; // 插件的描述(出现在选择插件对话框的插件列表框中)
winampVisModule* (*getModule)(int); // 用来获取模块结构
} winampVisHeader;
// 定义导出标识
typedef winampVisHeader* (*winampVisGetHeaderType)();
// 当前模块的版本 (0x101 == 1.01)
#define VIS_HDRVER 0x101
上面列出的是一个编写可视插件必须包含的头 文件 ,里面列出了可视插件用到的数据结构。在探讨具体插件 程序 之前,有一些概念必须搞清:一个可视插件中可以包含若干个模块(每一模块都是一种演示效果,可以在插件选择对话框中选择用哪个模块来演示),这些模块通过某种方法(后面将会看到)被 Winamp 获取,从而得到“表演”的机会。简而言之,Winamp 利用所有插件 DLL 中导出的一个统一名称的函数获得了一个插件头数据结构,然后通过此数据结构中的一个函数再去获取各个模块的信息(这个过程与 COM 的 QueryInterface() 用法有些神似,看来好的设计思想是相通的),进而利用多线程(通过 DLL View 观察得知)实现可视插件的展示。下面就是可视插件的源 程序 :
// Winamp 测试用可视插件 v1.0
// 版权所有 (C) 1997-1998, Justin Frankel/Nullsoft
// 基于此框架可自由的编写任何可视插件...
#include <windows.h>
#include "vis.h"
char szAppName[] = "SimpleVis"; // 窗口类名
// 有关配置的声明
int config_x=50, config_y=50; // 窗口在屏幕上的横纵坐标
void config_read(struct winampVisModule *this_mod); // 读配置
void config_write(struct winampVisModule *this_mod); // 写配置
void config_getinifn(struct winampVisModule *this_mod, char *ini_file); // 生成一个 .ini 文件 名
// 在需要的时候返回一个 winampVisModule,用在下面的 hdr 中。WinAMP 可由此得知插件中的模块数。
winampVisModule *getModule(int which);
// "成员"函数
void config(struct winampVisModule *this_mod); // 模块配置函数
int init(struct winampVisModule *this_mod); // 模块初始化函数
int render1(struct winampVisModule *this_mod); // 模块1 的“表演”函数
int render2(struct winampVisModule *this_mod); // 模块2 的“表演”函数
int render3(struct winampVisModule *this_mod); // 模块3 的“表演”函数
void quit(struct winampVisModule *this_mod); // 模块结束的清理函数
// 插件窗口的窗口处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
HWND hMainWnd; // 主窗口句柄
// 双缓冲数据
HDC memDC; // 内存DC
HBITMAP memBM, // 内存位图 (for memDC)
oldBM; // old bitmap (from memDC)
// 模块头部。包括模块版本,插件描述(出现在选择插件对话框的插件列表框中)和模块接口函数的地址
winampVisHeader hdr = { VIS_HDRVER, "Nullsoft Test Visualization Library v1.0", getModule };
// 第一模块 (示波器)
winampVisModule mod1 =
{
"Oscilliscope",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
0, // spectrumNch
2, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render1,
quit
};
// 第二模块 (光谱分析)
winampVisModule mod2 =
{
"Spectrum Analyser",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
2, // spectrumNch
0, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render2,
quit
};
// 第三模块 (VU meter)
winampVisModule mod3 =
{
"VU Meter",
NULL, // hwndParent
NULL, // hDllInstance
0, // sRate
0, // nCh
25, // latencyMS
25, // delayMS
0, // spectrumNch
2, // waveformNch
{ 0, }, // spectrumData
{ 0, }, // waveformData
config,
init,
render3,
quit
};
// 这是插件 DLL 中仅有的一个导出函数,用来返回插件头结构体指针,从而进一步得
// 知插件中各个模块的信息。
// 如果你正在编译 C++ 程序 ,extern "C" { 就是必要的,所以我们使用了 #ifdef。
#ifdef __cplusplus
extern "C" {
#endif
__declspec( dllexport ) winampVisHeader *winampVisGetHeader()
{
return &hdr;
}
#ifdef __cplusplus
}
#endif
// 在得到插件头结构体指针后用来获取模块信息。
// 如果一个不存在的模块被请求就返回 NULL,否则依据 'which' 来返回各可用模块(结构体指针)中的一个。
winampVisModule *getModule(int which)
{
switch (which)
{
case 0: return &mod1;
case 1: return &mod2;
case 2: return &mod3;
default:return NULL;
}
}
// 模块配置函数。通过 this_mod 可得知要配置哪个模块。
// 允许你的所有模块共用一个配置函数。
// (当然你不一非定要使用这个名字,你可以建立 config1(), config2(), 等等...)
void config(struct winampVisModule *this_mod)
{
MessageBox(this_mod->hwndParent,"This module is Copyright (c) 1997-1998, Justin Frankel/Nullsoft/n"
"-- This is just a demonstration module, it really isn't/n"
" supposed to be enjoyable --","Configuration",MB_OK);
}
// 模块初始化函数。注册窗口类,创建窗口等等。
// 这是所有模块都要做的工作,但是你也可以建立 init1() 和 init2()...
// 成功返回 0,失败返回1。
int init(struct winampVisModule *this_mod)
{
int width = (this_mod == &mod3)?256:288; // mod1 和 mod2 的宽高相等
int height = (this_mod == &mod3)?32:256; // 但是 mod3 的与另外两个不同
config_read(this_mod); // 读取配置信息
{ // 注册窗口类
WNDCLASS wc;
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = WndProc; // 窗口处理过程
wc.hInstance = this_mod->hDllInstance; // DLL 的实例句柄
wc.lpszClassName = szAppName; // 窗口类名
if (!RegisterClass(&wc))
{
MessageBox(this_mod->hwndParent,"Error registering window class","blah",MB_OK);
return 1;
}
}
hMainWnd = CreateWindowEx(
WS_EX_TOOLWINDOW|WS_EX_APPWINDOW, // 这些扩展风格建立一个好看的小窗口外框,
// 但是窗口在任务栏上有一个按钮
szAppName, // 窗口类名
this_mod->description, // 窗口标题使用模块描述
WS_VISIBLE|WS_SYSMENU, // 使窗口可见并且有一个关闭按钮
config_x,config_y, // 窗口的屏幕位置 (从配置中读取)
width,height, // 窗口的宽高 (需要在后面调整客户区尺寸)
this_mod->hwndParent, // 父窗口句柄 (WinAMP 主窗口)
NULL, // 无菜单
this_mod->hDllInstance, // DLL 的实例句柄
0); // 无窗口创建数据
if (!hMainWnd)
{
MessageBox(this_mod->hwndParent,"Error creating window","blah",MB_OK);
return 1;
}
SetWindowLong(hMainWnd,GWL_USERDATA,(LONG)this_mod); // 将窗口用户数据设为模块结构体指针
{ // 调整窗口尺寸以使得客户区等于宽乘高
RECT r;
GetClientRect(hMainWnd,&r);
SetWindowPos(hMainWnd,0,0,0,width*2-r.right,height*2-r.bottom,SWP_NOMOVE|SWP_NOZORDER);
}
// 创建双缓冲
memDC = CreateCompatibleDC(NULL);
memBM = CreateCompatibleBitmap(memDC,width,height);
oldBM = SelectObject(memDC,memBM);
// 显示窗口
ShowWindow(hMainWnd,SW_SHOWNORMAL);
return 0;
}
// 示波器模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render1(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,288,256);
// 绘制示波器
for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图
{
MoveToEx(memDC,0,(y*256)>>(this_mod->nCh-1),NULL);
for (x = 0; x < 288; x ++)
{
LineTo(memDC,x,(y*256 + this_mod->waveformData[y][x]^128)>>(this_mod->nCh-1));
}
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// 频谱分析模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render2(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,288,256);
// 绘制分析仪
for (y = 0; y < this_mod->nCh; y ++) //有几个声道就花几条波形图
{
for (x = 0; x < 288; x ++)
{
MoveToEx(memDC,x,(y*256+256)>>(this_mod->nCh-1),NULL);
LineTo(memDC,x,(y*256 + 256 - this_mod->spectrumData[y][x])>>(this_mod->nCh-1));
}
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,288,256,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// VU 表模块的“表演”函数。成功返回0,如果插件应该被终止则返回1。
int render3(struct winampVisModule *this_mod)
{
int x, y;
// 清除背景
Rectangle(memDC,0,0,256,32);
// 绘制 VU 表
for (y = 0; y < 2; y ++)
{
int last=this_mod->waveformData[y][0];
int total=0;
for (x = 1; x < 576; x ++)
{
total += abs(last - this_mod->waveformData[y][x]);
last = this_mod->waveformData[y][x];
}
total /= 288;
if (total > 127)
total = 127;
if (y)
Rectangle(memDC,128,0,128+total,32);
else
Rectangle(memDC,128-total,0,128,32);
}
{ // 将双缓冲复制到窗口中
HDC hdc = GetDC(hMainWnd);
BitBlt(hdc,0,0,256,32,memDC,0,0,SRCCOPY);
ReleaseDC(hMainWnd,hdc);
}
return 0;
}
// 模块清除函数 (对应于 init())。销毁窗口,取消窗口类的注册。
void quit(struct winampVisModule *this_mod)
{
config_write(this_mod); // 写入配置
SelectObject(memDC,oldBM); // 删除双缓冲
DeleteObject(memDC);
DeleteObject(memBM);
DestroyWindow(hMainWnd); // 销毁窗口
UnregisterClass(szAppName,this_mod->hDllInstance); // 取消窗口类的注册
}
// 插件窗口的窗口处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE: return 0;
case WM_ERASEBKGND: return 0;
case WM_PAINT:
{ // 以双缓冲的信息更新窗口显示
PAINTSTRUCT ps;
RECT r;
HDC hdc = BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&r);
BitBlt(hdc,0,0,r.right,r.bottom,memDC,0,0,SRCCOPY);
EndPaint(hwnd,&ps);
}
return 0;
case WM_DESTROY: PostQuitMessage(0); return 0;
case WM_KEYDOWN: // 将键盘消息传递给 WinAMP 主窗口 (使其被处理)
case WM_KEYUP:
{ // 从窗口的用户数据中得到 this_mod
winampVisModule *this_mod = (winampVisModule *) GetWindowLong(hwnd,GWL_USERDATA);
PostMessage(this_mod->hwndParent,message,wParam,lParam);
}
return 0;
case WM_MOVE:
{ // 从配置中得到 config_x 和 config_y
RECT r;
GetWindowRect(hMainWnd,&r);
config_x = r.left;
config_y = r.top;
}
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
// 生成一个 <dll directory>/plugin.ini 形式的 .ini 文件 名
void config_getinifn(struct winampVisModule *this_mod, char *ini_file)
{
char *p;
GetModuleFileName(this_mod->hDllInstance,ini_file,MAX_PATH);
p=ini_file+strlen(ini_file);
while (p >= ini_file && *p != '//')
p--;
if (++p >= ini_file)
*p = 0;
strcat(ini_file,"plugin.ini");
}
void config_read(struct winampVisModule *this_mod)
{
char ini_file[MAX_PATH];
config_getinifn(this_mod,ini_file);
config_x = GetPrivateProfileInt(this_mod->description,"Screen_x",config_x,ini_file);
config_y = GetPrivateProfileInt(this_mod->description,"Screen_y",config_y,ini_file);
}
void config_write(struct winampVisModule *this_mod)
{
char string[32];
char ini_file[MAX_PATH];
config_getinifn(this_mod,ini_file);
wsprintf(string,"%d",config_x);
WritePrivateProfileString(this_mod->description,"Screen_x",string,ini_file);
wsprintf(string,"%d",config_y);
WritePrivateProfileString(this_mod->description,"Screen_y",string,ini_file);
}
正如我们前面所说,插件 程序 与主 程序 之间一定要有相互合作的规范才能正常的运作,比如约定好的函数名等等。从上面的 程序 中可以看出,Winamp 用约定好的函数名调用插件 DLL 中唯一的一个导出函数 winampVisGetHeader() 来获取一个指向插件头结构体 winampVisHeader 的指针,而后者包含的一个指针函数(*getModule)(int) 就可以根据给定参数向 Winamp 暴露模块的接口。^_^这样一来,插件的全部细节就都出现在 Winamp 面前了。因为 getModule 函数对没有实现的模块返回 NULL,所以 Winamp 就可以通过返回值确定可视插件中模块的数目了。其实上面的 程序 就是一个可视插件的编写框架,当明确了其中的规范之后就可以把精力放在“表演”函数的编写和具体实现上了。