简而言之,Winamp 利用所有插件 DLL 中导出的一个统一名称的函数获得了一个插件头数据结构,然后通过此数据结构中的一个函数再去获取各个模块的信息(这个过程与 COM 的 QueryInterface() 用法有些神似,看来好的设计思想是相通的),进而利用多线程(通过 DLL View 观察得知)实现可视插件的展示。下面就是可视插件的源程序:
// Winamp 测试用可视插件 v1.0 // 版权所有 (C) 1997-1998, Justin Frankel/Nullsoft // 基于此框架可自由的编写任何可视插件...
#include
#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 ++) file://有几个声道就花几条波形图 { 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 ++) file://有几个声道就花几条波形图 { 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); }
// 生成一个 /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); onfig_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 就可以通过返回值确定可视插件中模块的数目了。其实上面的程序就是一个可视插件的编写框架,当明确了其中的规范之后就可以把精力放在“表演”函数的编写和具体实现上了。
说明:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
2/2 首页 上一页 1 2 |