Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

WinAmp DDE

Rate me:
Please Sign up or sign in to vote.
3.15/5 (9 votes)
28 May 200510 min read 56.7K   1.4K   14   10
Learn how to setup a DDE server, export function names through pragmas, create and use dialogs without MFC, override language operators, bypass a window's WndProc function, and last but not the least, create a general WinAmp plugin.

Contents

  1. The project behind
  2. Introduction
  3. From idea to reality through DDE.
  4. To Pragma or not to Pragma...
  5. The goto is not enough.
  6. Conclusion

1. The project behind

My main target was to use the information about songs currently played in WinAmp with other programs, to show other people the songs that I listen to, or simply to see the title of a song that I do not remember. It is not easy, because I use WinAmp in a "stealth" mode: it's minimized in the system tray, and hidden between all other icons (I love this WinXP feature). So basically, since I use hotkeys to run through songs, I had no way to easily display the songs name.

I already knew I needed some DDE. The fact is that I was lazy to code a plugin, so I just took one already done to be used with mIRC, which simply sends a message to it when a song is played. Given that, it is impossible to ask the plugin about information of any kind, rather it's the plugin that communicates actively. And that wasn't too much of a bothering, if it wasn't for streams. When a stream is played, the title can be updated, but this way the "active" DDE (which sends data only when a new song/stream is played) can't work. So, I threw away my laziness and started coding.

2. Introduction

I must admit it: I'm usually not a son of an "open source" philosophy. Not at all. Usually I take my sources ultra tight to my notebooks, but this is an exception. And who knows, maybe just a beginning of a whole series of exceptions. But let's cut it short.

Apart from the other things that made me release these sources (and my friend [SkiD] is certainly one of those reasons), there is a fact that I could find nowhere an easy overview on how to setup a DDE Server. I had to look through hundreds of pages, manual references, MSDN articles, to find the 10 lines of code that were needed. Amazing stuff nobody did ever write a similar article before (or maybe I'm just too stupid to find it).

Anyway, keeping aside the setup of the DDE Server there are several other nice things in this package, and although some may result to be totally useless to most, they can still be handy to some.

Let's get this party started...

3. From idea to reality through DDE

For the ones of you who don't know this, DDE is an acronym for Dynamic Data Exchange. It can be used to let two applications communicate with each other. As in all connection style protocols, there is a Server and a Client, and here we will analyze how to setup a Server. The main reason why there's no client setup here is because I never coded one. As simple as that. I have used some already VB coded DDE Client that I created a long while ago.

Off with the chitchats.

#include <ddeml.h>
BOOL APIENTRY DllMain(HANDLE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            [...]
            //
            //  Tries to register the DDE Server, and if it fails it
            //  tells the system to stop loading the DLL
            //
            if (DMLERR_NO_ERROR != DdeInitialize(&idInst, DdeCallback,
                                                 APPCLASS_STANDARD |
                                                 CBF_FAIL_ADVISES |
                                                 CBF_FAIL_EXECUTES |
                                                 CBF_FAIL_SELFCONNECTIONS |
                                                 CBF_SKIP_ALLNOTIFICATIONS,
                                                 0))
            {
Error:
                MessageBox(WA_hWnd," [...] ","Error",MB_ICONSTOP);
                break;
            }
            if (!(DDEServerName = DdeCreateStringHandle(idInst,
                                                        ServerName,
                                                        CP_WINANSI)))
              goto Error
            if (!(DDETopicEvaluate = DdeCreateStringHandle(idInst,
                                                           EvaluateName,
                                                           CP_WINANSI)))
              goto Error
            if (0 == DdeNameService(idInst, DDEServerName, 0, DNS_REGISTER))
              goto Error

            return true;
    }

    return false;
}

Above all, I would like to ask you to excuse me if the lines are too long for your monitor resolution: I use a 1400x1050 resolution, and as already stated before this is my first article, so... have mercy on me...

With the DdeInitialize we initialize a DDE which acts in a standard way (APPCLASS_STANDARD), accepts only XTYP_REQUEST and XTYP_POKE modes (CBF_FAIL_ADVISES | CBF_FAIL_EXECUTES | CBF_FAIL_SELFCONNECTIONS) and refuses any kind of notification (CBF_SKIP_ALLNOTIFICATIONS). And not to be forgotten, we pass the callback function address with this initialization step.

In order to use DDE APIs, we also need strings in a particular way, and to do so we call the DdeCreatStringHandle, which simply returns a handle to a HSZ, DDE formatted string. We need these strings to register the server name, and later, to check the topics and such.

Last of all, when the strings are ready, we register the name. The name is needed to let the other programs communicate with the server: just like a server listening on IP:Port on internet, the server usually listens to Name|Topic.

In this case, I placed the code in the DllMain function in answer to a DLL_PROCESS_ATTACH reason. This allows us to avoid the DLL to be loaded if we are not able to start a DDE Server, so basically if the plugin would be useless. This procedure can be used in any DLL: if DllMain returns false, then the system stops its loading.

The uninitialization is very simple:

void quit()
{
//
//  Unregisters DDE Server and save configuration
//
    DdeNameService(idInst, DDEServerName, 0, DNS_UNREGISTER);
    DdeFreeStringHandle(idInst, DDETopicEvaluate);
    DdeFreeStringHandle(idInst, DDEServerName);
    DdeUninitialize(idInst);
    [...]
}

The code talks by itself.

Now we have got the initialization and the uninitialization, but what's missing is the Callback function:

HDDEDATA CALLBACK DdeCallback(UINT uType,
                              UINT uFmt,
                              HCONV hconv,
                              HSZ hszTopic,
                              HSZ hszItem,
                              HDDEDATA hdata,
                              DWORD dwData1,
                              DWORD dwData2)
{
    HDDEDATA ret = 0;

    switch (uType)
    {
//
//      Allow DDE connections
//
        case XTYP_CONNECT:
            ret = (HDDEDATA)true;
            break;

//
//      Answer the DDE Requests. If the Topic is correct, obviously :P
//
        case XTYP_REQUEST:
//
//          We use the DdeCmpStringHandles instead of the strcmp
//          for two reasons: 1st we already have the strings in HSZ
//          format, 2nd this way it's case insensitive as the DDE
//          protocol is supposed to be.
//
            if (DdeCmpStringHandles(hszTopic, DDETopicEvaluate) == 0)
            {
                  [...]

                  ret = DdeCreateDataHandle(idInst,
                                            (BYTE*)"\1Unknown Error...",
                                            18, 0, hszItem, CF_TEXT, 0);
            }
            break;
//
//      No pokes this time...
//
        case XTYP_POKE:
//          Return FACK if poke message is processed
//          ret = DDE_FACK;

            ret = DDE_FNOTPROCESSED;
            break;
    }

    return ret;
}

Since I believe I never talked about what the difference between a XTYP_POKE and a XTYP_REQUEST could/should be, I will do that now. Usually (at least for what I saw until now), REQUESTs are used to get information from the server. In fact, the return value of the DdeCallback should either be a handle to DdeData (HDDEDATA) created through DdeCreateDataHandle, or NULL. The POKEs instead are used to "throw" commands, or at least tell the server to do a certain thing. If the server actually does that, it returns DDE_FACK, otherwise it has to return DDE_FNOTPROCESSED. The thing I did not mention here is about the XTYP_REQUEST: there is a particular condition in which you have to return a certain value, and that should be done when the calculation of the result requires some time. But I'd leave that case to you and MSDN.

The XTYP_POKE case is pretty explicative, so I'll avoid commenting.

Every time you receive a call, the first thing you have to do is to check whether the topic is valid or not. In this example we have EVALUATE only, so the job is easy. A call to DdeCmpStringHandles is enough to see if the topic is valid, and if it is we can simply create a result data and return. It is done by calling DdeCreateDataHandle and returning its result.

I guess this is all for the DDE.

4. To Pragma or not to Pragma...

... this is the question...

As I have stated in the Readme contained in the source package, I am using the latest Intel C++ compiler with the VS IDE. Given that, I do not know if the problems that I have with the inline name export technique is due to that: I just know I do have a problem. What's this problem you might ask? Let's take a look at it:

__declspec( dllexport ) winampGeneralPurposePlugin*
                        winampGetGeneralPurposePlugin()
{
    return &plugin;
}

Easy, isn't it? It would be if it works fine. The function is actually exported in the DLL, but the problem is that while we need the name to be exactly winampGetGeneralPurposePlugin, the compiler decides to use the object-based function name, and the result is this:

?winampGetGeneralPurposePlugin@@YAPAUwinampGeneralPurposePlugin@@XZ

That does not really seem like winampGetGeneralPurposePlugin to me, does it? It is known that, we have the possibility to use pragmas to do the job. The new version of that would be:

winampGeneralPurposePlugin *winampGetGeneralPurposePlugin()
{
    return &plugin;
}
#pragma comment(linker,
        "/EXPORT:winampGetGeneralPurposePlugin=?winampGet[...]@@XZ")

I have cut the function name to make the line fully visible with lower resolutions, but you would need the full name, of course...

The export works like this: "/EXPORT:ExportName:FunctionName". This allows us to export any function with any name. There are two things to underline: the first is that the /EXPORT: command is not as easy as it is used here, but as always I leave that to you to learn it with MSDN if you are interested (in this case, take a look here); the other is that there are other methods to export names, as well as the always recommended usage of the .DEF file, but that is not in my style. If you want to learn about the .DEF... well... go to MSDN again. This link maybe of help to you.

5. The goto is not enough

If the world is not enough, go figure if a goto is so. I'll make it short and describe what is my problem like.

int main()
{
    bool error = true;

    if (error)
      goto Errore

    int ret = 1;
    return ret;

Errore:
    return 0;
}

Now... although this snippet is pretty useless, it describes the problem. While compiling this, you are most likely to have an error, because the compiler sees that the goto might skip the initialization of the variable named ret. Even though this is correct, and the problem can be easily resolved by writing the "int ret;" right after the "error" declaration, it's also true that in several cases (included a lot of my sources) the "politically correct" code might be a little bit harder to read and understand.

It might be a matter of taste, I perfectly know that, and I'm also aware of the fact that I have strange tastes. But anyway in my code I like to do what I want to, and not what the compiler tells me to. The problem can be fixed in the following way:

#define goto __asm jmp

With this, the compiler now sees the goto as an assembly jump and not a C command, therefore it's not an object of compiler checks. With this trick you can easily avoid the troubles in the above snippet. For all of you who don't know, jmp is the assembly operator equivalent to C, basic and Pascal goto. Nothing too hard to understand here.

This is as powerful as dangerous: when it comes to code mistakes, this can seriously make you waste a lot of time trying to hunt down the bug. But if you can manage to use it wisely, it can save your day. It just happened once to me when I had troubles with an error like that.

Some of you might say that for small things like that, even a try-throw-catch block could save the day. Little that you know, is that the try blocks costs, and do really cost much in terms of CPU, when it comes to calls inside the block itself. I will not discuss about that here, but I, as a damascene coder, always look for the quickest, smallest and definitely the best way to solve a technical problem like this one. And so far, I have never seen anything better. Although I do not want anything else.

By the way, in the way described above you could change every single language operator. I do not recommend it, or at least I do not see any reason to do that, but it is possible. And after all, all I've been trying to do with this code is to show you different possible solutions to common problems. Or to what I consider common problems.

6. Conclusions

All in all, I hope that with this article you have learnt something new, or at least useful in some way. There are also other things inside the package that I have not explained here, but I believe that the commented sources and the readmes in the source package are more than enough.

So, I guess that this is really the end of my first article for Code Project, and all in all I must say that I'm glad I, for the first time, have contributed with my knowledge to a community that at more than a couple times has helped me a bit.

These are my 5 cents... if you like this article, and found it useful or interesting, let me know, as well as if you find any problems with my code or just anything else. I'm considering to start release of part of my sources, and I believe that the greatest part of the game will be played by the feedbacks I will have with this.

Well... so long...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Italy Italy

When he was 2 years old some friends of his mother made him play with arcade machines, and was almost clear what his path would have been. When he was 10 years old he's been given a 80286 as birthday present, and from that day on it's been a crescendo of programming techniques. Started with batch ms-dos file, that in a short while got interfaced with .com files created with debug.exe embedded ms-dos file... and then gw-basic, turbo pascal, quick basic, assembly, vb, vc++, delphi, java, plus a bunch of scripting languages, php included.
His other main passion is music and art in general, and has been awarded several times for his graphics and songs. He founded and still leads a demogroup named 3D, which stands for DBC Demo Division, and from time to time he takes the chance to attend demoparties, and eventually, submit artworks to some of the competitions offered by the different parties.
He's currently workin as a sales agent. Nothing to do with computers, but still he has to gain some money to buy some food...


Comments and Discussions

 
QuestionDDE with Winword Files Pin
AjaySaic20-Mar-08 0:26
AjaySaic20-Mar-08 0:26 
QuestionDDE calls fails for Word 2007. Pin
patelronak26-Jul-07 0:05
patelronak26-Jul-07 0:05 
Questionhi, poke? Pin
danschi22-Jan-06 6:01
danschi22-Jan-06 6:01 
AnswerRe: hi, poke? Pin
Skizo22-Jan-06 10:42
Skizo22-Jan-06 10:42 
Generalclient source Pin
danschi6-Jan-06 4:38
danschi6-Jan-06 4:38 
GeneralRe: client source Pin
Skizo6-Jan-06 9:26
Skizo6-Jan-06 9:26 
GeneralA problem with the plugin Pin
Victoria Kagansky6-Nov-05 11:32
Victoria Kagansky6-Nov-05 11:32 
GeneralRe: A problem with the plugin Pin
Skizo20-Dec-05 14:37
Skizo20-Dec-05 14:37 
GeneralSolution to pragma problem Pin
skeil2-Jun-05 20:34
skeil2-Jun-05 20:34 
GeneralRe: Solution to pragma problem Pin
Skizo3-Jun-05 9:42
Skizo3-Jun-05 9:42 
Thank you. I was not quite aware of this solution, and needs kind of less effort than using the pragmas Smile | :)

Aside the fact that it's my first article, this is also my first dll. Usually I code single .exes.

--
You can never go down
while whistling Koji Kondo

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.