'A winamp plugin: Making of'

Posted on February 15, 2011 by phimuemue

This post shows the basics of writing a very simple plugin for my favourite music player.

There is no special knowledge necessary. A bit of C++, basic experience in Visual Studio, the Winamp SDK some time and patience and a bit of common sense are enough (in my opinion).

What is it all about?

First of all, let me introduce what the plugin is going to do.

I always wanted to have a information in my page footer that shows the track winamp is currently playing (or winamp was playing at last). So I thought of a plugin that gives this information to the server whenever the next track is going to played.

Winamp plugins are (commonly) written in C++. The IDE of my choice for this project was Visual Studio 2010 beta (I am not a Microsoft fan or something - i just had it at hand).

First steps - setting up the environment

The first steps I've done were guided by Beginner's Basic Plugin Guide. So the following lines are quite taken from this guide.

The initial code

In Winamp there are several plugin types. For our purpose (and a first plugin experiment), a general purpose plugin is sufficient. General purpose plugins (or at least the associated DLL-files) have to start with the letters gen, so i named all used files according to this convention.

The Beginner's Basic Plugin Guide offers template code for a plugin doing actually nothing. I used this construct in order to create the plugin. This basic structure consists of two files: a h- and a cpp-file.

I named these files gen_WinampSongSubmitter.h and gen_WinampSongSubmitter.cpp, respectively.

The template code of the header file gen_WinampSongSubmitter.h looks as follows:

#ifndef gen_WinampSongSubmitter_h
//---------------------------------------------------------------------------
#define gen_WinampSongSubmitter_h
#include <windows.h>

// plugin version (don't touch this)
#define GPPHDR_VER 0x10

// plugin name/title (change this to something you like)
#define PLUGIN_NAME "winampSongSubmitter"

// main structure with plugin information, version, name...
typedef struct {
  int version;                   // version of the plugin structure
  char *description;             // name/title of the plugin
  int (*init)();                 // function which will be executed on init event
  void (*config)();              // function which will be executed on config event
  void (*quit)();                // function which will be executed on quit event
  HWND hwndParent;               // hwnd of the Winamp client main window (stored by Winamp when dll is loaded)
  HINSTANCE hDllInstance;        // hinstance of this plugin DLL. (stored by Winamp when dll is loaded)
} winampGeneralPurposePlugin;

#endif
</windows.h>

I think the names used are quite intuitive and the comments suffice to make clear what the stuff means.

Additionally, the "blank" gen_WinampSongSubmitter.cpp consisted of the following lines:

#include "stdafx.h"
#include < windows.h >
#include "gen_WinampSongSubmitter.h"

// these are callback functions/events which will be called by Winamp
int  init(void);
void config(void);
void quit(void);

// this structure contains plugin information, version, name...
// GPPHDR_VER is the version of the winampGeneralPurposePlugin (GPP) structure
winampGeneralPurposePlugin plugin = {
  GPPHDR_VER,  // version of the plugin, defined in "gen_WinampSongSubmitter.h"
  PLUGIN_NAME, // name/title of the plugin, defined in "gen_WinampSongSubmitter.h"
  init,        // function name which will be executed on init event
  config,      // function name which will be executed on config event
  quit,        // function name which will be executed on quit event
  0,           // handle to Winamp main window, loaded by winamp when this dll is loaded
  0            // hinstance to this dll, loaded by winamp when this dll is loaded
};

// event functions follow

int init() {
  //A basic messagebox that tells you the 'init' event has been triggered.
  //If everything works you should see this message when you start Winamp once your plugin has been installed.
  //You can change this later to do whatever you want (including nothing)
  MessageBox(plugin.hwndParent, L"Init event triggered for gen_myplugin. Plugin installed successfully!", L"", MB_OK);
  return 0;
}

void config() {
  //A basic messagebox that tells you the 'config' event has been triggered.
  //You can change this later to do whatever you want (including nothing)
  MessageBox(plugin.hwndParent, L"Config event triggered for gen_myplugin.", L"", MB_OK);
}

void quit() {
  //A basic messagebox that tells you the 'quit' event has been triggered.
  //If everything works you should see this message when you quit Winamp once your plugin has been installed.
  //You can change this later to do whatever you want (including nothing)
  MessageBox(0, L"Quit event triggered for gen_myplugin.", L"", MB_OK);
}

// This is an export function called by winamp which returns this plugin info.
// We wrap the code in 'extern "C"' to ensure the export isn't mangled if used in a CPP file.
extern "C" __declspec(dllexport) winampGeneralPurposePlugin * winampGetGeneralPurposePlugin() {
  return &plugin;
}

We notice that we have three "important" methods for our first plugin:

So far everything is quite intuitive i think. Now we can compile our code and run a first test - almost. We first have to add dependencies to our project so the linker knows where to find the libraries used.

Now everything should compile fine and generate a DLL file called gen_WinampSongSubmitter.dll. To see the plugin in action we copy this DLL into the Winamp plugin folder. As soon Winamp is started, it automatically detects the plugin and invokes the init-method. Then we should see a messagebox telling the init event was triggered. When Winamp is closed we should see a second messagebox.

If something went wrong up to this point, have a look at Beginner's Basic Plugin Guide.

Making the plugin useful

The rest of the work is completely done in the cpp-file. First of all, I removed all unnecessary comments and messageboxes in order to get an overview of what I had to do. More precisely, I cleaned the methods init, config and quit, so they were empty:

// [...] same as before

int init() {
  return 0;
}

void config() {

}

void quit() {

}

// [...] same as before

What's the goal?

I decided to write the "central" functionality first. The main goal of the plugin was to create a file on a FTP-server that contains the title of the currently played song.

Since I had no(t really any) idea how to get the title from winamp, I created a quite simple function that creates a file, writes the title (given as an argument) into it and uploads it via FTP.

void uploadSong(char* title){
	// Establish FTP connection
	HINTERNET hInternet = InternetOpen(L"gen_WinampSongSubmitter", INTERNET_OPEN_TYPE_DIRECT, L"", L"", INTERNET_FLAG_ASYNC);
	HINTERNET connection = InternetConnect(hInternet, L"ftp.myserver.com", 21, L"myusername", L"mypassword", INTERNET_SERVICE_FTP, 0, 0);

	// Create file containing information
	ofstream output ("songsubmitter.dat");
	output << title << endl;
	output.close();

	// upload file
	FtpPutFile(connection, L"songsubmitter.dat", L"songsubmitter.dat", FTP_TRANSFER_TYPE_BINARY, 0)

	// Close FTP-Connection
	InternetCloseHandle(connection);
	InternetCloseHandle(hInternet);
}

What this function does is quite simple: It connects to the given FTP-server, creates a file that contains the title and uploads the file. In order to make this function work, additional header files have to be included. Furthermore i told C++ to use the standard namespace, so i do not always have to use std::.

#include < WinInet.h >	// for FTP-connection
#include < iostream >	// for cout-debugging
#include < fstream >	// for writing a file

using namespace std;	// for avoiding std::endl, etc.

Interacting with winamp

Interacting with winamp is the most complicated part of this tutorial. For more detailed information, you could look at Basic Plugin Guide - Tutorial#2.

Most important thing to know when communicating with Winamp is a header file called wa_ipc.h that comes with the Winamp SDK (in the directory Winamp SDK/Winamp). It contains all constants available for plugin developers in order to tell Winamp what to do.

Whatever we want Winamp to do, is being done with the SendMessage function. Let's inspect a small example.

int version = SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GETVERSION);
int majVersion = WINAMP_VERSION_MAJOR(version);
int minVersion = WINAMP_VERSION_MINOR(version);

The above line reads the version of Winamp. To do so, it sends Winamp (plugin.hwndParent) a message. The constant WM_WA_IPC indicates that the message is for Winamp (and not for Windows). The third parameter of SendMessage is used to pass parameters. Since we do not need any parameters (often named wParam) for retrieving the version, it is set to 0. The last parameter (often called lParam) tells Winamp what to do. Look at wa_ipc.h for more such constants.

It is remarkable that Winamp fills the field plugin.hwndParent itself, so we do not have to find out the handle on our own.

The final part

Now that we have basic knowledge about how to interact with Winamp, I want to tell you how the final steps were made.

What do we still need?

Retrieving the title

After some search in wa_ipc.h I found out that the following line extracts the currently played title (as displayed in the playlist):

wchar_t title[256]
title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);

So we send Winamp a message containing the value of IPC_GET_PLAYING_TITLE. Then Winamp gives us the title as wchar_t. Since these datatype is not so handy and I do not use very special characters I decided to convert it into a usual char*.

char buff[256];
// Warning: The following line uses insecure wcstombs
wcstombs(buff, title, 256);

After that, title holds the title of the current track.

Letting Winamp notify the plugin

The most difficult part is to tell Winamp to notify our plugin. This is done at initialization (i.e. in init).

To do so, we need to subclass the Winamp main window. Subclassing in this context is not to be confused with inheritance and subclasses in object oriented design.

Subclassing in this context means the following: Winamp has one big routine handling all events (Mouse, Keyboard, etc.). Subclassing means that we write an own method that is executed before the actual big routine starts.

That means, we need an own routine that handles Winamp events and checks weather the event "New track starts" occurs. If so, it shall execute our uploadSong function. It is convenient that after this, our routine should call the original Winamp routine.

So we need some things to do:

In order to store the original Winamp routine, I introduced a new variable:

WNDPROC lpWndProcOld = NULL;

WNDPROC indicates that this is a windows procedure (respectively a pointer to some). This variable stores the original Winamp routine that handles all events.

The updated init function looks as follows:

int init() {
	lpWndProcOld = (WNDPROC)SetWindowLong(plugin.hwndParent, GWL_WNDPROC, (LONG)MainWndProc);
	return 0;
}

SetWindowLong sets a specified parameter (in this case GWL_WNDPROC) of a specified window (in this case plugin.hwndParent) to a specified value (in this case (LONG)MainWndProc). It is defined in WinUser.h. plugin.hwndParent indicates that a value of the Winamp window is changed. The constant GWL_WNDPROC indicates that the window procedure (the procedure handling all events) is set. The value set is (LONG)MainWndProc, wich is kind of pointer to our new routine. SetWindowLong returns the previous value of the set property. Thus, in this case it returns the original window routine. This routine is stored in lpWndProcOld. Now, we have told Winamp to execute our routine to handle events. But how does our routine actually look like? Consider the following code:

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message==WM_WA_IPC && lParam==IPC_PLAYING_FILE){
		wchar_t* title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);
		char buff[256];
		// Warning: The following line uses insecure wcstombs
		wcstombs(buff, title, 256);
		uploadSong(buff);
	}
 	return CallWindowProc(lpWndProcOld,hwnd,message,wParam,lParam);
}

Please dont mind the complicated return type of this function - it's just to look up somewhere.

This procedure has four arguments:

Then it checked wether the message is a message to winamp (message==WM_WA_IPC) and if the message is "Next track starts" (lParam==IPC_PLAYING_FILE).

Then the title is read and uploaded. At last, the original routine (stored in lpWndProcOld) is called via CallWindowProc. The arguments should be clear.

The complete code

Below you find the contents of the cpp-file:

// I assume no liability.

#include "stdafx.h"
#include < windows.h >
#include "gen_WinampSongSubmitter.h"

#include "wa_ipc.h"
#include < stdio.h >

#include < WinInet.h >
#include < iostream >
#include < fstream >

#include < stdlib.h >

using namespace std;

// these are callback functions/events which will be called by Winamp
int  init(void);
void config(void);
void quit(void);

// these are my own functions needed by the plugin
void uploadSong(char* title);

// this structure contains plugin information, version, name...
// GPPHDR_VER is the version of the winampGeneralPurposePlugin (GPP) structure
winampGeneralPurposePlugin plugin = {
	GPPHDR_VER,  // version of the plugin, defined in "gen_myplugin.h"
	PLUGIN_NAME, // name/title of the plugin, defined in "gen_myplugin.h"
	init,        // function name which will be executed on init event
	config,      // function name which will be executed on config event
	quit,        // function name which will be executed on quit event
	0,           // handle to Winamp main window, loaded by winamp when this dll is loaded
	0            // hinstance to this dll, loaded by winamp when this dll is loaded
};

// Subclassing stuff (hopefully)
WNDPROC lpWndProcOld = NULL;

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message==WM_WA_IPC && lParam==IPC_PLAYING_FILE){
		wchar_t* title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);
		char buff[256];
		// Warning: The following line uses insecure wcstombs
		wcstombs(buff, title, 256);
		uploadSong(buff);
	}
 	return CallWindowProc(lpWndProcOld,hwnd,message,wParam,lParam);
}

// event functions follow
int init() {
	lpWndProcOld = (WNDPROC)SetWindowLong(plugin.hwndParent, GWL_WNDPROC, (LONG)MainWndProc);
	return 0;
}

void config() {
}

void quit() {
}

// This is an export function called by winamp which returns this plugin info.
// We wrap the code in 'extern "C"' to ensure the export isn't mangled if used in a CPP file.
extern "C" __declspec(dllexport) winampGeneralPurposePlugin * winampGetGeneralPurposePlugin() {
	return &plugin;
}

// This function does the uploading stuff
void uploadSong(char* title){
	// Establish FTP connection
	HINTERNET hInternet = InternetOpen(L"gen_WinampSongSubmitter", INTERNET_OPEN_TYPE_DIRECT, L"", L"", INTERNET_FLAG_ASYNC);
	HINTERNET connection = InternetConnect(hInternet, L"myftpserver", 21, L"myusername", L"mypassword", INTERNET_SERVICE_FTP, 0, 0);

	// Create file containing information
	ofstream output ("songsubmitter.dat");
	output << title << endl;
	output.close();

	// upload file
	FtpPutFile(connection, L"songsubmitter.dat", L"songsubmitter.dat", FTP_TRANSFER_TYPE_BINARY, 0)

	// Close FTP-Connection
	InternetCloseHandle(connection);
	InternetCloseHandle(hInternet);
	return;
}

Conclusion

This is a very basic project, of course. But it shows some helpful stuff for developing Winamp plugins. More sophisticated techniques may contain GUI, other plugin types, etc.

Making the plugin useful

The rest of the work is completely done in the cpp-file. First of all, I removed all unnecessary comments and messageboxes in order to get an overview of what I had to do. More precisely, I cleaned the methods init, config and quit, so they were empty:

// [...] same as before

int init() {
  return 0;
}

void config() {

}

void quit() {

}

// [...] same as before

What's the goal?

I decided to write the "central" functionality first. The main goal of the plugin was to create a file on a FTP-server that contains the title of the currently played song.

Since I had no(t really any) idea how to get the title from winamp, I created a quite simple function that creates a file, writes the title (given as an argument) into it and uploads it via FTP.

void uploadSong(char* title){
	// Establish FTP connection
	HINTERNET hInternet = InternetOpen(L"gen_WinampSongSubmitter", INTERNET_OPEN_TYPE_DIRECT, L"", L"", INTERNET_FLAG_ASYNC);
	HINTERNET connection = InternetConnect(hInternet, L"ftp.myserver.com", 21, L"myusername", L"mypassword", INTERNET_SERVICE_FTP, 0, 0);

	// Create file containing information
	ofstream output ("songsubmitter.dat");
	output << title << endl;
	output.close();

	// upload file
	FtpPutFile(connection, L"songsubmitter.dat", L"songsubmitter.dat", FTP_TRANSFER_TYPE_BINARY, 0)

	// Close FTP-Connection
	InternetCloseHandle(connection);
	InternetCloseHandle(hInternet);
}

What this function does is quite simple: It connects to the given FTP-server, creates a file that contains the title and uploads the file. In order to make this function work, additional header files have to be included. Furthermore i told C++ to use the standard namespace, so i do not always have to use std::.

#include < WinInet.h >	// for FTP-connection
#include < iostream >	// for cout-debugging
#include < fstream >	// for writing a file

using namespace std;	// for avoiding std::endl, etc.

Interacting with winamp

Interacting with winamp is the most complicated part of this tutorial. For more detailed information, you could look at Basic Plugin Guide - Tutorial#2.

Most important thing to know when communicating with Winamp is a header file called wa_ipc.h that comes with the Winamp SDK (in the directory Winamp SDK/Winamp). It contains all constants available for plugin developers in order to tell Winamp what to do.

Whatever we want Winamp to do, is being done with the SendMessage function. Let's inspect a small example.

int version = SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GETVERSION);
int majVersion = WINAMP_VERSION_MAJOR(version);
int minVersion = WINAMP_VERSION_MINOR(version);

The above line reads the version of Winamp. To do so, it sends Winamp (plugin.hwndParent) a message. The constant WM_WA_IPC indicates that the message is for Winamp (and not for Windows). The third parameter of SendMessage is used to pass parameters. Since we do not need any parameters (often named wParam) for retrieving the version, it is set to 0. The last parameter (often called lParam) tells Winamp what to do. Look at wa_ipc.h for more such constants.

It is remarkable that Winamp fills the field plugin.hwndParent itself, so we do not have to find out the handle on our own.

The final part

Now that we have basic knowledge about how to interact with Winamp, I want to tell you how the final steps were made.

What do we still need?

Retrieving the title

After some search in wa_ipc.h I found out that the following line extracts the currently played title (as displayed in the playlist):

wchar_t title[256]
title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);

So we send Winamp a message containing the value of IPC_GET_PLAYING_TITLE. Then Winamp gives us the title as wchar_t. Since these datatype is not so handy and I do not use very special characters I decided to convert it into a usual char*.

char buff[256];
// Warning: The following line uses insecure wcstombs
wcstombs(buff, title, 256);

After that, title holds the title of the current track.

Letting Winamp notify the plugin

The most difficult part is to tell Winamp to notify our plugin. This is done at initialization (i.e. in init).

To do so, we need to subclass the Winamp main window. Subclassing in this context is not to be confused with inheritance and subclasses in object oriented design.

Subclassing in this context means the following: Winamp has one big routine handling all events (Mouse, Keyboard, etc.). Subclassing means that we write an own method that is executed before the actual big routine starts.

That means, we need an own routine that handles Winamp events and checks weather the event "New track starts" occurs. If so, it shall execute our uploadSong function. It is convenient that after this, our routine should call the original Winamp routine.

So we need some things to do:

In order to store the original Winamp routine, I introduced a new variable:

WNDPROC lpWndProcOld = NULL;

WNDPROC indicates that this is a windows procedure (respectively a pointer to some). This variable stores the original Winamp routine that handles all events.

The updated init function looks as follows:

int init() {
	lpWndProcOld = (WNDPROC)SetWindowLong(plugin.hwndParent, GWL_WNDPROC, (LONG)MainWndProc);
	return 0;
}

SetWindowLong sets a specified parameter (in this case GWL_WNDPROC) of a specified window (in this case plugin.hwndParent) to a specified value (in this case (LONG)MainWndProc). It is defined in WinUser.h. plugin.hwndParent indicates that a value of the Winamp window is changed. The constant GWL_WNDPROC indicates that the window procedure (the procedure handling all events) is set. The value set is (LONG)MainWndProc, wich is kind of pointer to our new routine. SetWindowLong returns the previous value of the set property. Thus, in this case it returns the original window routine. This routine is stored in lpWndProcOld. Now, we have told Winamp to execute our routine to handle events. But how does our routine actually look like? Consider the following code:

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message==WM_WA_IPC && lParam==IPC_PLAYING_FILE){
		wchar_t* title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);
		char buff[256];
		// Warning: The following line uses insecure wcstombs
		wcstombs(buff, title, 256);
		uploadSong(buff);
	}
 	return CallWindowProc(lpWndProcOld,hwnd,message,wParam,lParam);
}

Please dont mind the complicated return type of this function - it's just to look up somewhere.

This procedure has four arguments:

Then it checked wether the message is a message to winamp (message==WM_WA_IPC) and if the message is "Next track starts" (lParam==IPC_PLAYING_FILE).

Then the title is read and uploaded. At last, the original routine (stored in lpWndProcOld) is called via CallWindowProc. The arguments should be clear.

The complete code

Below you find the contents of the cpp-file:

// I assume no liability.

#include "stdafx.h"
#include < windows.h >
#include "gen_WinampSongSubmitter.h"

#include "wa_ipc.h"
#include < stdio.h >

#include < WinInet.h >
#include < iostream >
#include < fstream >

#include < stdlib.h >

using namespace std;

// these are callback functions/events which will be called by Winamp
int  init(void);
void config(void);
void quit(void);

// these are my own functions needed by the plugin
void uploadSong(char* title);

// this structure contains plugin information, version, name...
// GPPHDR_VER is the version of the winampGeneralPurposePlugin (GPP) structure
winampGeneralPurposePlugin plugin = {
	GPPHDR_VER,  // version of the plugin, defined in "gen_myplugin.h"
	PLUGIN_NAME, // name/title of the plugin, defined in "gen_myplugin.h"
	init,        // function name which will be executed on init event
	config,      // function name which will be executed on config event
	quit,        // function name which will be executed on quit event
	0,           // handle to Winamp main window, loaded by winamp when this dll is loaded
	0            // hinstance to this dll, loaded by winamp when this dll is loaded
};

// Subclassing stuff (hopefully)
WNDPROC lpWndProcOld = NULL;

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message==WM_WA_IPC && lParam==IPC_PLAYING_FILE){
		wchar_t* title = (wchar_t*) SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_PLAYING_TITLE);
		char buff[256];
		// Warning: The following line uses insecure wcstombs
		wcstombs(buff, title, 256);
		uploadSong(buff);
	}
 	return CallWindowProc(lpWndProcOld,hwnd,message,wParam,lParam);
}

// event functions follow
int init() {
	lpWndProcOld = (WNDPROC)SetWindowLong(plugin.hwndParent, GWL_WNDPROC, (LONG)MainWndProc);
	return 0;
}

void config() {
}

void quit() {
}

// This is an export function called by winamp which returns this plugin info.
// We wrap the code in 'extern "C"' to ensure the export isn't mangled if used in a CPP file.
extern "C" __declspec(dllexport) winampGeneralPurposePlugin * winampGetGeneralPurposePlugin() {
	return &plugin;
}

// This function does the uploading stuff
void uploadSong(char* title){
	// Establish FTP connection
	HINTERNET hInternet = InternetOpen(L"gen_WinampSongSubmitter", INTERNET_OPEN_TYPE_DIRECT, L"", L"", INTERNET_FLAG_ASYNC);
	HINTERNET connection = InternetConnect(hInternet, L"myftpserver", 21, L"myusername", L"mypassword", INTERNET_SERVICE_FTP, 0, 0);

	// Create file containing information
	ofstream output ("songsubmitter.dat");
	output << title << endl;
	output.close();

	// upload file
	FtpPutFile(connection, L"songsubmitter.dat", L"songsubmitter.dat", FTP_TRANSFER_TYPE_BINARY, 0)

	// Close FTP-Connection
	InternetCloseHandle(connection);
	InternetCloseHandle(hInternet);
	return;
}

Conclusion

This is a very basic project, of course. But it shows some helpful stuff for developing Winamp plugins. More sophisticated techniques may contain GUI, other plugin types, etc.