
Contents
- The project behind
- Introduction
- From idea to reality through DDE.
- To Pragma or not to Pragma...
- The goto is not enough.
- Conclusion
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.
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...
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:
[...]
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()
{
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)
{
case XTYP_CONNECT:
ret = (HDDEDATA)true;
break;
case XTYP_REQUEST:
if (DdeCmpStringHandles(hszTopic, DDETopicEvaluate) == 0)
{
[...]
ret = DdeCreateDataHandle(idInst,
(BYTE*)"\1Unknown Error...",
18, 0, hszItem, CF_TEXT, 0);
}
break;
case XTYP_POKE:
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.
... 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.
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.
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...