MSN Messenger 7 Current Playing Song
#22
Posted 15 February 2005 - 06:48 AM
(especially in silverspeed's final code)
shaneh, on Feb 14 2005, 09:43 AM, said:
shaneh, on Feb 14 2005, 09:43 AM, said:
If you know how to handle these, they work as easy as WCHAR[]s or any other form of data storage... When you want the pointer to the actual data contained in the WCHAR[] string you use StrPtr(), when you want the pointer to the BSTR "string" (which is actually contains the pointer to the WCHAR[] string) you use VarPtr(). VarPtr() can also be used on any other element (thus, strings are actually a special case, hence the use of StrPtr()).
shaneh, on Feb 14 2005, 09:59 AM, said:
You need an array of 100 wide characters, not a pointer to a string. The pointers are taking up just 4 bytes, you need 200 (100 wide characters = 200 bytes) per title/artist/album.
shaneh, on Feb 14 2005, 09:59 AM, said:
Now for the VB code...
shaneh, on Feb 14 2005, 03:29 PM, said:
Type msnstruct intMsnCommand As Integer szTitle (0 To 200) As Byte szArtist (0 To 200) As Byte szTitle (0 To 200) As Byte szWmCommand (0 To 80) As Byte End Type
By using byte-arrays this is what will happen:
You have an ascii string S = "Hello"
1) VB stores it internally as a BSTR (which is in the end already stored as a WCHAR = "H.e.l.l.o.")
2) you grab the string from memory
3) VB converts it back to ascii ("Hello") to give you the string
4) you do some manipulation so you can store it as a WCHAR[] (again back to "H.e.l.l.o.") into another variable being the byte array.
5) VB stores this byte array as bytes (ofcourse), thus "H.e.l.l.o."
6) in the end you must give a pointer to this array
Now, doesn't this seem like a very long complicated and useless way (for this problem case)?
But there is another thing which isn't correct though (even if you do use the byte array method): szTitle (0 To 200) As Byte
This makes an byte array of 201 elements. The whole structure will be bigger then 684 bytes (0x02AC)! And hence I see some strange and unneeded calculations in silverspeeds final code to compensate this (putting #0 in front of 2 strings, starting to count from 1 instead of 0, ...) And above all, it will result in wrongly aligned text anyways (try to fill all the available space or try to put some accented characters like "é" in and you'll see):
'change title sTitle = Chr(0) & sTitle For i = 0 To Len(sTitle) - 1 msnpush.Title(i * 2) = Asc(Mid(sTitle, i + 1, 1)) Next 'change artist sArtist = Chr(0) & sArtist For i = 1 To Len(sArtist) - 1 msnpush.Artist((i * 2) - 1) = Asc(Mid(sArtist, i + 1, 1)) Next 'change album For i = 0 To Len(sAlbum) - 1 msnpush.Album(i * 2) = Asc(Mid(sAlbum, i + 1, 1)) Next
If you declared your intMsnCommand and byte arrays correctly (200 bytes, instead of 201) in the first place, all that manipulation (which is wrong anyways) isn't needed:
Type msnstruct intMsnCommand As Long szTitle (0 To 199) As Byte szArtist (0 To 199) As Byte szTitle (0 To 199) As Byte szWmCommand (0 To 79) As Byte End Type 'change title For i = 0 To Len(sTitle) - 1 msnpush.Title(i * 2) = Asc(Mid(sTitle, i + 1, 1)) Nextor
'change title For i = 1 To Len(sTitle) msnpush.Title(i * 2 - 2) = Asc(Mid(sTitle, i, 1)) NextAnd this for all three the converting routines.
But, you don't need to start the base of the array at 0, 1 or whatever; you can choose what base it has. So if choosen carefully you even can reduce the amount of calculations (the -1's, -2's, +1's, etc):
Type msnstruct intMsnCommand As Long szTitle (2 To 201) As Byte szArtist (2 To 201) As Byte szTitle (2 To 201) As Byte szWmCommand (2 To 81) As Byte End Type 'change title For i = 1 To Len(sTitle) msnpush.Title(i * 2) = Asc(Mid(sTitle, i, 1)) Next
Now you see that the code is already getting smaller (and faster) and more logical to follow and read.
But there is 1 (big) thing left to say. I started by saying that the use of byte array's is not needed because VB stores his strings as WCHAR[]s anyway. And also, with that method of converting an ascii string to a forced unicode string, you loose the ability to actually use unicode characters in your songtitles, artists, etc... (try to add characters like é into the strings with your code, then try it with mine)
So to cut the already too long post, here is the whole corrected code:
Option Explicit
Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const WM_COPYDATA = &H4A
Private Type MSNMSGSTRUCT ' Internal structure of MSNMSGSTRUCT will be:
MsnCommand As Long ' ####T.i.t.l.e.A.r.t.i.s.t.A.l.b.u.m.W.m.C.o.m.m.a.n.d.
Title As String * 100 ' \4b/\200bytes/\-200bytes-/\200bytes/\----80 bytes----/
Artist As String * 100
Album As String * 100
WmCommand As String * 40
End Type
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Private Sub Form_Load()
'Change the MSN song info
ChangeSong "This is the title", "This is the artist", "This is the album"
End Sub
Private Sub Form_Unload(Cancel As Integer)
'Song stops playing
StopSong
End Sub
Private Sub ChangeSong(sTitle As String, sArtist As String, sAlbum As String)
Dim msndata As COPYDATASTRUCT
Dim msnpush As MSNMSGSTRUCT
Dim msnui As Long
'Set msncommand, title, artist and album
'Notice that we don't do any string manipulation as VB internaly
'converts strings to unicode anyways. Also we can get away with
'this because we don't provide the MSNMSGSTRUCT structure
'directly to the API call, but rather the pointer to it, hence VB does
'not convert the strings back to ANSI, as it normaly would do when
'you call an API with string parameters.
msnpush.MsnCommand = 1
msnpush.Title = sTitle & vbNullChar 'Zero terminate the string
msnpush.Artist = sArtist & vbNullChar 'Zero terminate the string
msnpush.Album = sAlbum & vbNullChar 'Zero terminate the string
msnpush.WmCommand = vbNullChar 'Zero terminate the null string
'Find window & send message
msnui = FindWindow("MsnMsgrUIManager", vbNullString)
msndata.dwData = &H547
msndata.cbData = &H2AC
msndata.lpData = VarPtr(msnpush)
SendMessage msnui, WM_COPYDATA, Me.hWnd, msndata
End Sub
Private Sub StopSong()
Dim msndata As COPYDATASTRUCT
Dim msnpush As MSNMSGSTRUCT
Dim msnui As Long
''Set msncommand
''Not realy needed though, it will be initialized to 0 upon creation anyways.
''And if MsnCommand is 0, the rest of the data will be ignored also, no matter
''what it contains (hence all this is commented out).
'msnpush.MsnCommand = 0
'msnpush.Title = vbNullChar 'Zero terminate the null string
'msnpush.Artist = vbNullChar 'Zero terminate the null string
'msnpush.Album = vbNullChar 'Zero terminate the null string
'msnpush.WmCommand = vbNullChar 'Zero terminate the null string
'Find window & send message
msnui = FindWindow("MsnMsgrUIManager", vbNullString)
msndata.dwData = &H547
msndata.cbData = &H2AC
msndata.lpData = VarPtr(msnpush)
SendMessage msnui, WM_COPYDATA, Me.hWnd, msndata
End SubEdit: fixed space-bug. Also, for a better and more optimalized code, converted to a module and to take polygamy in account, see the new listing in next post.ok final note for those who are confused or to be more confused :P:
a BSTR is actualy a pointer to a pointer to a UCS-2 unicode string.
a WCHAR* is a pointer to a wide character string.
(note that "unicode" strings and "wide character" strings aren't always the same; a wide character can be longer then 2 bytes (see UTF-8). But in 99% of all cases they are the same. To be strict, the term "wide character" can mean both unicode and UTF-8. In UCS-2 unicode the length of a wide character is always 2 bytes (or 4 bytes in UCS-4 unicode). In UTF-8 the length can be anything from 1 to 6 bytes)
S = "Hello"
In the internal VB memory it looks like this:
####H.e.l.l.o. (4 bytes for the length, followed by the UCS-2 unicode string)
If you want the BSTR pointer:
VarPtr(S)
If you want a pointer to the unicode string itself (the memory offset of "H"):
StrPtr(S)
The 4 bytes which contain the length of the unicode string can be found like so:
StrPtr(S) - 4
If you want to store S in memory as ascii for whatever reason (instead of the default unicode):
A = StrConv(S, vbFromUnicode)
In the memory it looks like this:
####Hello (4 bytes for the length, followed by the string)
Note that when you print A, VB will actually handle it as an unicode string and thus will not print "Hello", but "???".
This post has been edited by CookieRevised: 17 February 2005 - 11:31 AM
#23
Posted 15 February 2005 - 08:19 AM
Oh, and with regards to the BSTR, AFAIK they are ref counted, so there would be a reference count in there somewhere (the 'garbage' I was talking about - quite different to a straight WCHAR[]). Hence the 'pointer to a pointer' etc, which is why you cant just use a 'String'. A string 'array' in VB on the other hand I guess is essentially just a WCHAR[].
A WCHAR is not a pointer to a wide character string, a WCHAR* is. A WCHAR is just a two byte variable effectively. A WCHAR[] is an array of those.
This post has been edited by shaneh: 15 February 2005 - 08:26 AM
#26
Posted 15 February 2005 - 11:56 AM
shaneh, on Feb 15 2005, 09:19 AM, said:
In structures, such as used in the program here, those "length bytes" aren't present since the strings are fixed length. The whole structure is just like it should be, a sequence of data without any "garbage" in front, in between or at the end.
shaneh, on Feb 15 2005, 09:19 AM, said:
shaneh, on Feb 15 2005, 09:19 AM, said:
shaneh, on Feb 15 2005, 09:19 AM, said:
I've edited my previous post to correct this, thanks...
Daniel, on Feb 15 2005, 10:42 AM, said:
Are WCHAR[]s zero-terminated???
If so then the total structure size can't be 684 (0x02AC) bytes.
4 bytes for the integer
200 bytes for the wide string + 1 nullchar
200 bytes for the wide string + 1 nullchar
200 bytes for the wide string + 1 nullchar
80 bytes for the wide string + 1 nullchar
-------------------------------------------
=688 bytes
Or what is the most significant byte of a WCHAR?
The first or the last? VB uses normal little endian unicode, "A" is stored as "0x41 0x00".
solved: see Huuf's (very important) remark that VB doesn't fill fixed length strings with null characters but rather with spaces.
final correction :D: VB does fill newly created strings with null characters!! However, when you assign a text to a fixed length string, VB will fill the remaining (unused) characters with spaces.
solved: was a bad leftover which I didn't noticed before from the first C to VB ported source earlier in this thread (integer data type in VB is only 2 bytes, in C it is 4 bytes)
This post has been edited by CookieRevised: 17 February 2005 - 11:43 AM
#27
Posted 15 February 2005 - 12:15 PM
EDIT: I think the nullchar whould be apart of the 200, not added on.
#28
Posted 15 February 2005 - 12:43 PM
Daniel, on Feb 15 2005, 01:15 PM, said:
EDIT: I think the nullchar whould be apart of the 200, not added on.
final correction :D: VB does fill newly created strings with null characters!! However, when you assign a text to a fixed length string, VB will always fill the remaining (unused) characters with spaces. And that's the thruth and nothing but the thruth, so help me God :D
Edit: so stupidly wrong from me to say this, see remark above... sorry :angel:
But ermmm... (/me slaps himself) an integer in C is 4 bytes, right? In VB it is only 2 bytes :rolleyes: stupid me... I totaly overlooked that fact in silverspeeds code. In VB intMsnCommand should be declared as Long, not as Integer. That of course explains the 1 character (2 bytes) of...
I fixed my listing...
This post has been edited by CookieRevised: 17 February 2005 - 11:38 AM
#29
Posted 15 February 2005 - 01:15 PM
The Integer size of 2 bytes certainly would explain the behaviour you are getting. In C they are 4 bytes when using most (all?) 32 bit compilers.
Good to see the VB code is working now, or at least I assume it is :)
A slight improvement on the code is to do the following:
while (msnui = FindWindowEx(NULL, msnui, "MsnMsgrUIManager", NULL))
{
do msnpush initialisation
sendmessage(msnui, WM_COPYDATA,.. etc)
}This is incase there are other windows registered with this class on the system. This may be the case if the user is running multiple clients (polygamy patches) or MS decides to have other windows with the same class etc.
The WMP plugin does exactly this.
This post has been edited by shaneh: 15 February 2005 - 01:21 PM
#30
Posted 15 February 2005 - 01:34 PM
shaneh, on Feb 15 2005, 02:15 PM, said:
Edit: The procedure StopSong worked because MsnCommand was initialized to 0. And that's the important part of the function though, when MsnCommand is 0, the data is ignored anyways.
shaneh, on Feb 15 2005, 02:15 PM, said:
This post has been edited by CookieRevised: 17 February 2005 - 11:40 AM
#32
Posted 16 February 2005 - 09:38 PM
Though there are again a bunch of remarks and comments to say about this. (and I cba to PM them also, so I posted the entire module again) Sorry, please don't take my corrections wrongly though. :)
-----EDIT----------NEW----------EDIT----------NEW----------EDIT-----
The attached zip contains:
SETSONG_COMMENTS.BAS
see also the listing below
* proper type declarations for the API's
* no more global variables
* not depending on any formhandle
* very short code
SETSONG_AS_ANY.BAS
an alternative to SETSONG.BAS
* not so proper type declarations for the API's, thus:
* shows difference between: AS LONG vs. AS ANY
* shows difference between: ByRef vs. ByVal
* as an example, contains only 1 procedure to handle both setting and clearing the song
SETSONG_OLD_SOURCE.BAS
As a reference, the source that was previously posted in this post
* not so proper type declarations for the API's
* contains a global variable
* depending on the formhandle
* because of the above, the code was longer
SETSONG_FINAL.BAS
which I never intended to do, but here it is anyways
* proper type declarations for the API's
* no more global variables
* not depending on any formhandle
* not using the API callback function anymore
* very short code
* (but please read/look at the other modules first to understand the comments)
And now, the code for SETSONG_COMMENTS.BAS (thus heavly commented though :P)
For background info (eg: about what a string is in VB) and to see the first codechanges see my earlier post(s) in this thread.
The calls to use in your project based upon the module below:
Call ChangeSong("Title", "Artist", "Album")
Call StopSong()
Note: do not compile any modules with "Assume no aliasing" as aliasing is used in every example module.
Quote
'# INTRO NOTES:
'#
'# First of all, sorry for the heavy comments :D But as this source
'# actually depicts many different and interesting issues about VB and
'# API programming I felt like explaining some (internal) things, so
'# people would understand why something is done in a certain way and
'# because I see many bad things happening in VB sources. Hopefully a
'# few will learn something new from it.
'#
'# Now for the comment system that I used. All extra/explaining comments
'# begin with '#. The normal code-comments just begin with '. So if you
'# want to delete these extra long comments, you will find them easly
'# and you'll be left with the comments that you'll find in normal code.
Option Explicit
Private Declare Function EnumWindows Lib "user32.dll" _
(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" _
(ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
'# API Declaration of SendMessage changed to reflect the true internal
'# types. Defining parameters as "AS ANY" in VB is sometimes very handy,
'# but if you don't realy know what AS ANY does or why sometimes it is
'# there and sometimes not, it can be very confusing (and your code can
'# have some very hard to catch bugs).
'#
'# Internaly the SendMessage API expects only pointers to be passed. In
'# VB this is reflected as defining the parameters AS LONG; a pointer is
'# 4 bytes. This means that you can't pass the actual value of a variable
'# to it (or directly a string or number for that matter), but you must
'# pass the pointer to that variable.
'#
'# To get a pointer from a variable you can use undocumented functions
'# like VarPtr(), VarObj(), VarStr(), etc... Oldtime Basic programmers
'# will remember these from the good old Basica/GWBasic/Q(uick)Basic
'# days. But for some reason MS doesn't want you to know about them ;)
'# (Also see http://support.micro...kb;en-us;199824)
'#
'# Now to overcome all this (the fact that you can only assign pointers
'# and the fact that the functions like VarPtr() are hidden from the
'# user) they invented the AS ANY declaration. (that's my theory though :D)
'#
'# This will make that VB itself will catch the pointer from a passed
'# variable and passes the pointer from that variable instead.
'#
'# The difference between ByVal and ByRef is easy once you get the hang
'# of it. With ByVal you pass the variable's contents. If you use that
'# in a procedure or function, it will be possible to change the variable
'# in that procedure or function without changing the original variable.
'#
'# sString = "Hello"
'# Call MySub(sString)
'# MsgBox sString 'will result in "Hello"
'#
'# Sub MySub(ByVal MyString)
'# MyString = MyString & " World"
'# End Sub
'#
'# When you use ByRef, then a reference (the pointer) to the variable
'# will be passes. This reference will be used to handle the variable
'# inside the procedure or function. This means that if you use it in
'# a procedure or function the contents of the original variable can
'# and will change.
'#
'# sString = "Hello"
'# Call MySub(sString)
'# MsgBox sString 'will result in "Hello Hello"
'#
'# Sub MySub(ByRef MyString)
'# MyString = MyString & " World"
'# End Sub
'#
'# If you omit ByRef or ByVal in front of a variable, then VB will use
'# ByRef as default!
'#
'# So, all this (ByRef/ByVal and AS ANY/AS <...> is the reason why
'# sometimes you see declaration of API's written as:
'# APIName Lib "mylib.dll" (ByVal lParam As Long)
'# and sometimes as
'# APIName Lib "mylib.dll" (lParam As Any)
'# or
'# APIName Lib "mylib.dll" (ByRef lParam As Any)
'#
'# And that can be very confusing if you don't know what it actually
'# means or does. Once you get the hang of it, the use of ByRef/ByVal
'# and AS ANY can be very usefull and rewarding.
'#
'# And this explains why I did what I did in the source (I hope ;P)
Private Const WM_COPYDATA = &H4A
Private Type MSNMSGSTRUCT ' Internal memory structure of MSNMSGSTRUCT will be:
MsnCommand As Long ' ####T.i.t.l.e.A.r.t.i.s.t.A.l.b.u.m.W.m.C.o.m.m.a.n.d.
Title As String * 100 ' \4b/\200bytes/\-200bytes-/\200bytes/\----80 bytes----/
Artist As String * 100
Album As String * 100
WmCommand As String * 40
End Type
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
'# Notice the lack of the parameter "frmOwner As Form" which was
'# present in the previous source. It is not needed anymore.
Public Sub ChangeSong(sTitle As String, sArtist As String, sAlbum As String)
'Create our structures
'# Notice that we don't do this anymore in the global memory space
'# but rather in this procedure itself. This is because there is no
'# use anymore for a global msndata variable and wasting memoryspace
'# if we directly pass the variable to our subprocedure EnumWindows.
Dim msnpush As MSNMSGSTRUCT
Dim msndata As COPYDATASTRUCT
'Set msncommand, title, artist, album and WmCommand
'# Notice that we don't do any string manipulation as VB internaly
'# converts strings to unicode anyways. Also we can get away with
'# this because we don't provide the MSNMSGSTRUCT structure and its
'# strings directly to the API call, but rather the pointer to it via
'# the COPYDATASTRUCT structure. Hence VB does not convert the strings
'# back to ANSI, as it normaly would do when you call an API with
'# parameters which contain strings.
msnpush.MsnCommand = 1
msnpush.Title = sTitle & vbNullChar 'Zero terminate the string
msnpush.Artist = sArtist & vbNullChar 'Zero terminate the string
msnpush.Album = sAlbum & vbNullChar 'Zero terminate the string
msnpush.WmCommand = vbNullChar 'Zero terminate the null string
'Set the pointers and size to msnpush
msndata.dwData = &H547
msndata.cbData = &H2AC
msndata.lpData = VarPtr(msnpush)
'Loop trough all top-level windows and pass the pointer to the
'msndata structure with it.
'# Thus notice that we don't pass the handle anymore of the form as
'# it was done in the previous source. It was of no use and was
'# ignored by Messenger anyways. This makes that we now have again a
'# free available parameter to use. Thus we will use it to pass the
'# pointer of msndata. This makes that msndata doesn't need to be
'# declared globaly anymore and doesn't take extra memoryspace in
'# our project. The less global variables, the better.
Call EnumWindows(AddressOf EnumWinProc, VarPtr(msndata))
'Clean memory
'# Not the same as clear memory though; Clearing the memory would
'# mean that the variables wouldn't exist anymore. And this is not
'# the case when you assign new values to the variables (no matter
'# if they are 0 or Null or whatever; the memory will still contain
'# the variables)
'#
'# For safety reasons, the previous source still contained:
'# msndata.dwData = 0
'# msndata.cbData = 0
'# msndata.lpData = 0
'# Because msndata was a global variable. But now notice that we
'# don't need to clean msnpush and neither msndata. Because they
'# were defined inside the procedure they both will be automatically
'# cleared when the procedure exists. Thus since there are no global
'# variables anymore, there is nothing to clean :P.
End Sub
'# Notice the lack of the parameter "frmOwner As Form" which was
'# present in the previous source. It is not needed anymore.
Public Sub StopSong()
'Create our structures
'# See notes in ChangeSong
Dim msnpush As MSNMSGSTRUCT
Dim msndata As COPYDATASTRUCT
''Set msncommand
'msnpush.MsnCommand = 0
''# The above is not realy needed in VB though, it will be initialized
''# to 0 already when the structure is created. And if MsnCommand
''# is 0, the rest of the msnpush data will be ignored by Messenger
''# also, no matter what it contains, but may I stress that msndata
''# and msnpush _are_ initialized to all zero's in VB, and thus will not
''# contain 'whatever'. (hence all this is double commented out;
''# if you do want to set MsnCommand to 0 yourself, then you only need
''# to remove one comment character of this entire comment block ;))
''# Initializing data to zero is however a commmon practice, especially
''# in larger programs where you quickly can loose the scope of things.
''# However, in small programs where you exactly know and can see what
''# the scope is of every data, it is not wrong to not initialize them
''# yourself, in fact you're code will be smaller and run a few ticks
''# faster as you wouldn't do a double job.
''#
''# Also in the previous source there was:
''# msnpush.Title = vbNullChar 'Zero terminate the null string
''# msnpush.Artist = vbNullChar 'Zero terminate the null string
''# msnpush.Album = vbNullChar 'Zero terminate the null string
''# msnpush.WmCommand = vbNullChar 'Zero terminate the null string
''# This was done, as an example, to make sure that the strings
''# contained at least 1 terminate/null character. Previously I said
''# that VB fills fixed length strings with spaces upon the creation
''# of the strings, unlike C which fills it with zero's. However, VB
''# indeed does fill them with zero's also. It is only when you assign
''# text to the variables that the remaining unused characters of the
''# fixed strings will be filled with spaces. In other words, assigning
''# a null character to those strings was even more not needed and a
''# waste of processor time then assigning 0 to MsnCommand ;)
'Set the pointers and size to msnpush
msndata.dwData = &H547
msndata.cbData = &H2AC
msndata.lpData = VarPtr(msnpush)
'Loop trough all top-level windows and pass the pointer to the
'msndata structure with it.
'# See notes in ChangeSong
Call EnumWindows(AddressOf EnumWinProc, VarPtr(msndata))
'Clean memory
'# See notes in ChangeSong
End Sub
'Callback loop function
'# As previously said, we are going to use the application-defined
'# 32bit value of lParam, which is passed from the calling EnumWindows
'# function. It would be a waste not to make a use of this. In previous
'# source we passed the formhandle with it. But as this wasn't needed
'# anyways we can now pass the pointer to msndata with it. See also the
'# intro notes and the notes in ChangeSong for more explaination.
Private Function EnumWinProc(ByVal LhWnd As Long, ByVal lParam As Long) As Long
Dim RetVal As Long
Dim WinClassBuf As String * 255
'Retrieve the class name
'# If GetClassName succeeds, the return value is the number of
'# characters copied to the specified buffer. If it fails, the
'# return value is 0. To get extended error information, call
'# the GetLastError API (not implemented here).
'#
'# Earlier in the notes of ChangeSong I spoke of the fact that VB
'# automatically converts his internaly used strings back to ANSI
'# when they are passed to an API. In the case of WinClassBuf this
'# is no different. Because it is a string it will internaly be
'# handled as Unicode, and thus will take 255*2 bytes to store in
'# memory. But when we pass it to the GetClassName API it will
'# automatically be converted to ANSI, and thus will take only 255
'# bytes. Hence the third parameter of GetClassName is 255.
'#
'# Once the temporarly ANSI string is created, the pointer to it will
'# be passed to the API. When the API returns, VB will convert the
'# returned ANSI string back to Unicode and store that in the place
'# of the previous unicode string (and freeing the memory used for
'# converting).
RetVal = GetClassName(LhWnd, WinClassBuf, 255)
'Get full returned string and see if it is the right class
'# Notice the use of Left$() instead of Left(), this is slightly
'# faster. Left$() will return a string while Left() will return a
'# stringtype variant which takes more time to process.
If Left$(WinClassBuf, RetVal) = "MsnMsgrUIManager" Then
'Send the message to the found window
'# Notice the 0&. In the previous source that parameter was the
'# formhandle which was passed thru lParam. As this wasn't needed,
'# we can simply pass a zero to that parameter. And use lParam
'# to pass the pointer to the msndata structure.
Call SendMessage(LhWnd, WM_COPYDATA, 0&, lParam)
End If
EnumWinProc = True
End Function
'# The End
The full package with all the example modules (updated with SETSONG_FINAL.BAS):
Attached File(s)
-
setsong.zip (13.07K)
Number of downloads: 367
This post has been edited by CookieRevised: 18 February 2005 - 07:02 AM
#33
Posted 17 February 2005 - 10:28 AM
http://forums.winamp...581#post1593581
@CookieRevised: I would recommend using FindWindowEx rather than EnumWindows, its designed for this purpose.
Also, its generally good practice to initialise variables manually rather than rely on the quirks of a language and the state the variables "should" be in. There are plenty of programming texts on the web which discuss this.
The WMP plugin zeroes out the entire structure, so even if not zeroing out the entire structure works, its generally a good idea to emulate the plugin exactly. Messenger may decide to use the structure differently, and depend on the whole structure being initialised, at a later date.
#34
Posted 17 February 2005 - 07:08 PM
shaneh, on Feb 17 2005, 11:28 AM, said:
Anyways, What we are doing in the source is enumerating the windows, so how can EnumWindows not be designed for this? When you closely look at the API and how it is used in the example, EnumWindows is the perfect one to use for this. Note: in this example source though, where I showed how to pass a variable to a function without the use of a global defined variable.
FindWindowEx hasn't the extra user-definable parameter which I used as the important example on how to pass our data, of which almost every comment is based upon.
shaneh, on Feb 17 2005, 11:28 AM, said:
If I wouldn't had commented in the source on the initializing stuff (to inform about the automatic initialization) I would indeed initialize it myself to give as an example, but in the end it realy _is_ double code (in VB). If my comments are read carefully, you'll see that I don't say you _may not_ do it, but that I rather say that it isn't _needed_ (thus, in VB that is!).
"it is a good practice to..." comes from the fact that not all programming language set things to zero. Knowing this, and knowing that VB _does_ initialize everything to zero is equaly (if not more) important. Heck, the initializing problem has a very big comment block in the sourcecode just because of this and explaining all this (and the MsnCommand = 0 is still there, because of it). But if I explicitly must add: "it is a good practice to..." to the inline comments to make this clearer, I'll will do, no problem. Although I thought it would have been clear...
shaneh, on Feb 17 2005, 11:28 AM, said:
Above all, like I said before, the structure _is_ zero'ed out upon initialization! That's the way declaring works in VB (and that's exactly of what I have commented on in the example source itself). It would have been a different story if I did not commented on why I wouldn't initialize the variable myself; then you could say I forgot it, or didn't know about it...
The comments in the source are almost more important to read then the source itself, heck I wrote/compiled the entire thing just to be able to comment on such things/issues.
As I said in the source, the program depicts some many interesting issues/twirks about VB. Otherwise I even wouldn't have bothered and the thread would have ended 10 posts ago or something with another half-finished and buggy VB source, and the C programmers would have had again a reason to call VB crap (no pun intended though).
It is one thing to be able to program, it is another to know in what environment you're programming and what that environment has to offer or can/can't do.
shaneh, on Feb 17 2005, 11:28 AM, said:
This is also the way that the receiving program should and will handle. It _must_ make a copy of the passed data via the WM_COPYDATA message if it wants to keep it. See MSDN Library SendMessage/WM_COPYDATA. And if it doesn't need it anymore (MsnCommand = 0) it will be cleared by Messenger anyways. The lifespan of the passed structure is only as long as the time it takes to pass the data with WM_COPYDATA, nothing longer (and this goes for every data you send thru WM_COPYDATA to any program btw). Also it is very inlogic if Messenger decides to use his copy of the structure without first writing new stuff to it when MsnCommand was 0. And may I remind that the passed structure from VB _was_ initialized even if MsnCommand was 0 (see comments in source and above replies). If it wasn't, Messenger wouldn't even accepted it, which is a very logical/mandatory thing to do (see ASM listing earlier in this thread).
This post has been edited by CookieRevised: 18 February 2005 - 01:17 AM
#35
Posted 18 February 2005 - 01:37 AM
The use of lParam etc is a moot point if you are just doing:
while hwnd=findwindowex
sendmessage hwnd, msndata etc
It doesnt buy you any savings. Savings which are irrelevant when you are using a language such as VB which has a lot more overhead than the odd int here and there anyway.
Although the MS compiled VB code may initialise variables to zero, there could be other VB compilers (existing or will exist) that might not initialise variables. Thats why its good practice. Given that the code to do it is neglible to execute, its silly not to. Of course, few VB coders do, but seeing as you did a big write up of what should only be a few lines of code, it would appear you are trying to do it the 'right' way.
You want to emulate the plugin, because MS wrote it. And they are the ones that wrote MSN messenger. You want to emulate it exactly, because MSN messenger shouldnt be able to tell the difference between the WMP plugin or yours. They could change MSN messenger at any time, and its best to have the plugin operating exactly the same as the other "official" plugin.
By "at a later date", I mean at a later vesion of MSN messenger. For example, a later version of MSN messenger may check that the structure is initialised to 0 for whatever reason, when you pass the msncommand=0, or that there are no trailing spaces after the NULL in the titles etc. Its quite common to use NULL deliminated strings terminated by a double-NULL, so the WMP plugin could pass extra data in this way in a later version.
The WMP plugin initialises it, so Messenger can safely do this check without worrying. Unless of course your plugin doesnt operate exactly the same.
Of course, if the particular compiler you choose to compile your code does initialise the structure to 0, youve got nothing to worry about ;)
This post has been edited by shaneh: 18 February 2005 - 02:44 AM
#36
Posted 18 February 2005 - 06:01 AM
shaneh, on Feb 18 2005, 02:37 AM, said:
(...)
shaneh, on Feb 18 2005, 02:37 AM, said:
shaneh, on Feb 18 2005, 02:37 AM, said:
shaneh, on Feb 18 2005, 02:37 AM, said:
Edit: btw, If so concearned about emulating it exactly and that messenger shouldn't be able to tell the difference, then why did you put "foo_care_not_for_drm" in wmcontentid instead of something else? Because you know it would have no (side)effect? That's almost the same as assuming that the data being send, will be of the same structure for a long time to come. If every program should be made so that futur things will not brake it, then the IT bussiness is up for a big shock.
Again, this wasn't about the "perfect" code at all, that's up to the people who make the actual stuff. I compiled the sources together as a working base to comment on seemingly trivial VB stuff. If someone wants to specifically fill every used fixed length string with zero's, then by all means do it, nothing in the comments tells you that you may not do it, the comments only tell why it is/isn't needed (in the current scope of the program) so that people can decide for themselfs if they want to make it or not. The sources are meant as a base to know what to do/not to do, not as a "here is your perfect code, copy/paste it"-source.
------------------------
Anyways, in reference to these previous posts, I made a final version (again NOT the perfect version, yet heavly commented again). After this, I probably will not look anymore to this thread. Those who want to know some things about VB or why some coders do something in a certain way can have a free look to all the files or look up some info on the web (msdn library is an excellent starting point).
Following attached file is also included in the previous package with the other sources:
------------------------
EDIT: Also see "Msn Messenger 7 Current Playing Song, the new code" for an alternative method with some benefits (longer strings possible for artist, album, etc...).
Attached File(s)
-
SETSONG_FINAL.ZIP (3.48K)
Number of downloads: 256
This post has been edited by CookieRevised: 27 March 2005 - 04:51 PM
#37
Posted 11 April 2005 - 01:08 PM
It compiles fine, the SendMessage statement is reached successfully. I can't see anything wrong anywhere and as far as both mIRC and the DLL are concerned, everything is fine.
Except MSN simply never shows the notification. Windows Media Player manages it, but when I close that and call SongOn it seems to have no effect.
Are there any thoughts as to what I may be missing??! Thanks...
#include <windows.h>
#include <stdio.h>
#define MAGIC_NUMBER 0x547
COPYDATASTRUCT msndata;
struct msnmsgstruct;
struct msnmsgstruct {
int msncommand;
WCHAR title[100];
WCHAR artist[100];
WCHAR album[100];
WCHAR wmcontentid[40];
};
typedef struct {
DWORD mVersion;
HWND mHwnd;
BOOL mKeep;
} LOADINFO;
HWND msnui;
int __declspec(dllexport) __stdcall SongOn(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause) {
int i = 0;
while (msnui = FindWindowEx(NULL, msnui, "MsnMsgrUIManager", NULL)) {
msnmsgstruct msnpush;
ZeroMemory(&msnpush, sizeof(msnpush));
msnpush.msncommand = 1;
lstrcpyW(msnpush.title, L"It Can't Come Quickly Enough");
lstrcpyW(msnpush.artist, L"Scissor Sisters");
lstrcpyW(msnpush.album, L"Scissor Sisters");
lstrcpyW(msnpush.wmcontentid, L"123456");
msndata.dwData = MAGIC_NUMBER;
msndata.lpData = &msnpush;
msndata.cbData = sizeof(msnpush);
SendMessage(msnui, WM_COPYDATA, (WPARAM)(HWND) mWnd, (LPARAM)&msndata);
//MessageBox(msnui, "Success", "DLL Call", 0x00000020L);
i++;
}
if (i == 0) {
MessageBox(mWnd, "Failure", "DLL Call", 0x00000020L);
}
return 1;
}
int __declspec(dllexport) __stdcall SongOff(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause) {
while (msnui = FindWindowEx(NULL, msnui, "MsnMsgrUIManager", NULL)) {
msnmsgstruct msnpush;
ZeroMemory(&msnpush, sizeof(msnpush));
msnpush.msncommand = 0;
lstrcpyW(msnpush.title, L"Title");
lstrcpyW(msnpush.artist, L"Artist");
lstrcpyW(msnpush.album, L"Album");
lstrcpyW(msnpush.wmcontentid, L"WMContentID");
msndata.dwData = MAGIC_NUMBER;
msndata.lpData = &msnpush;
msndata.cbData = sizeof(msnpush);
SendMessage(msnui, WM_COPYDATA, (WPARAM)(HWND) mWnd, (LPARAM)&msndata);
}
return 3;
}
int __declspec(dllexport) __stdcall UnloadDll(int mTimeout) {
return 0;
}
#40
Posted 21 April 2005 - 08:29 PM
Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal Hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Private Const WM_COPYDATA = &H4A
' eg: Call SetMusicInfo("artist", "album", "title")
' eg: Call SetMusicInfo("artist", "album", "title", "WMContentID")
' eg: Call SetMusicInfo("artist", "album", "title", , "{1} by {0}")
' eg: Call SetMusicInfo("", "", "", , , False)
Public Sub SetMusicInfo(ByRef r_sArtist As String, ByRef r_sAlbum As String, ByRef r_sTitle As String, Optional ByRef r_sWMContentID As String = vbNullString, Optional ByRef r_sFormat As String = "{0} - {1}", Optional ByRef r_bShow As Boolean = True)
Dim udtData As COPYDATASTRUCT
Dim sBuffer As String
Dim hMSGRUI As Long
'Total length can not be longer then 256 characters!
'Any longer will simply be ignored by Messenger.
sBuffer = "\0Music\0" & Abs(r_bShow) & "\0" & r_sFormat & "\0" & r_sArtist & "\0" & r_sTitle & "\0" & r_sAlbum & "\0" & r_sWMContentID & "\0" & vbNullChar
udtData.dwData = &H547
udtData.lpData = StrPtr(sBuffer)
udtData.cbData = LenB(sBuffer)
Do
hMSGRUI = FindWindowEx(0&, hMSGRUI, "MsnMsgrUIManager", vbNullString)
If (hMSGRUI > 0) Then
Call SendMessage(hMSGRUI, WM_COPYDATA, 0, VarPtr(udtData))
End If
Loop Until (hMSGRUI = 0)
End Sub
Private Sub Command1_Click()
SetMusicInfo "Artist", "album", "title"
End SubPaste that in a form and add a commandbutton. Works fine for MSN Messenger 7.0.0777

Sign In
Register
Help

MultiQuote
