Messenger Plus! Scripts enable its users and developers to create their own functionalities for Windows Live Messeger. The scripting engine allows scripts to call arbitrary Win32 DLLs (through the Interop
object), and to allocate raw memory (through the DataBloc
object), making scripts capable of virtually anything. However, there are still occasions where making your own DLLs is inevitable; the most important one is when you need to have your own asynchronous callbacks. Currently, Plus! only supports synchronous callbacks for scripts. Asynchronous callbacks such as Window Procedures, which are crucial in subclassing various windows, are not supported, and have to be implemented with external DLLs. Now, what if you want to call the scripting engine objects (like MsgPlus, Messenger or ChatWnd instances) inside the DLL function's C++ codes? If you've ever wondered the question, then this tutorial is for you. In this tutorial, I'll illustrate how to let your own DLLs talk with Messenger Plus! scripting objects, step by step.
In this tutorial, I assume that you're an adequate C++ programmer, and know how to create a native Win32 DLL with Microsoft Visual Studio. I also assume you're already experienced in Plus! scripting. I use Microsoft Visual Studio 2010 when writing this tutorial, but things should be pretty much the same with other versions of Visual Studio (down to 2005, I presume; for even earlier versions I can't guarantee).
The first step is to let your DLL know what Plus! scripting objects are and where to locate them. For this, you have to import the Messenger Plus! Type library. Add the following line (depending on your Plus! version) to your code:
// For Plus! 4
#import "C:\Program Files (x86)\Messenger Plus! Live\MsgPlusLive.dll" tlbid(1)
// For Plus! 5
#import "C:\Program Files (x86)\Yuna Software\Messenger Plus!\MsgPlusLive.dll" tlbid(1)
Note that you may need to change the directory to match the directory where you installed Plus!. For example, take out " (x86)" if you're not using 64-bit Windows. Your DLL built against the Type Library on one Plus! version will typically work on another Plus! version, as long as version-specific features are taken care of. For example, as of build 706, Plus! 5 doesn't support changing personal messages through scripts with Messenger.MyPersonalMessage
. Your DLL built against Plus! 4's Type Library may behave unexpectedly on Plus! 5 if it modifies the user's personal message in that way.
After you add the above #import
line, you need to build your project first, so that msgpluslive.tlh
, the Typelib Generated C++ Header, and msgpluslive.tli
, the Typelib Generated C++ Inline File, will appear in the output directory (Debug or Release, depending on your build configuration). After you do this, Visual Studio's IntelliSense on Plus! scripting objects will kick in to work.
The type library you've just imported creates a namespace MPLiveScriptingLib
, in which various Plus! scripting objects and constants are declared and defined. The following table outlines these objects and constants along with their JScript equivalents.
JScript | Raw Interface | COM Smart Interface Pointer |
---|---|---|
Object Classes | ||
ChatWnds | MPLiveScriptingLib::_IMPChatWnds* | MPLiveScriptingLib::_IMPChatWndsPtr |
ChatWnd | MPLiveScriptingLib::_IMPChatWnd* | MPLiveScriptingLib::_IMPChatWndPtr |
Contacts | MPLiveScriptingLib::_IMPContacts* | MPLiveScriptingLib::_IMPContactsPtr |
Contact | MPLiveScriptingLib::_IMPContact* | MPLiveScriptingLib::_IMPContactPtr |
Emoticons | MPLiveScriptingLib::_IMPEmoticons* | MPLiveScriptingLib::_IMPEmoticonsPtr |
Emoticon | MPLiveScriptingLib::_IMPEmoticon* | MPLiveScriptingLib::_IMPEmoticonPtr |
PlusWnd | MPLiveScriptingLib::_IMPPlusWnd* | MPLiveScriptingLib::_IMPPlusWndPtr |
DataBloc | MPLiveScriptingLib::_IMPDataBloc* | MPLiveScriptingLib::_IMPDataBlocPtr |
Utility Classes | ||
Debug | MPLiveScriptingLib::_IMPDebug* | MPLiveScriptingLib::_IMPDebugPtr |
Messenger | MPLiveScriptingLib::_IMPMessenger* | MPLiveScriptingLib::_IMPMessengerPtr |
MsgPlus | MPLiveScriptingLib::_IMPMsgPlus* | MPLiveScriptingLib::_IMPMsgPlusPtr |
Interop | MPLiveScriptingLib::_IMPInterop* | MPLiveScriptingLib::_IMPInteropPtr |
Constants | ||
STATUS_INVISIBLE , STATUS_ONLINE , ... | MPLiveScriptingLib::STATUS_INVISIBLE , MPLiveScriptingLib::STATUS_ONLINE , ... |
|
WNDOPT_NORMAL , WNDOPT_INVISIBLE , ... | MPLiveScriptingLib::WNDOPT_NORMAL , MPLiveScriptingLib::WNDOPT_INVISIBLE , ... |
|
(not all constant enumerations are outlined here, but I think you get the idea) |
You might notice that for each object, two kinds of interfacs are available: one with the _IM
prefix, like _IMPDebug
, and another one without the prefix, like MPDebug
. The difference of two is that the former use raw functions to interface with Plus!, while the latter uses _com_dispatch_method()
. The two should function the same, while I prefer to use the former. Also, we want COM smart interface pointers to manage (e.g. automatically release when applicable) the pointers, so they will be the case from now on. Notice that from here on, I assume you're using the MPLiveScriptingLib
namespace (by adding using namespace MPLiveScriptingLib;
), so instead of saying MPLiveScriptingLib::_IMPMsgPlusPtr
, I'll just say _IMPMsgPlusPtr
.
Simplying declaring the interface pointers will get you nowhere. You need to have them point to the actual objects. For this, you need to pass those objects from scripts to the DLL. For example, if your DLL needs to use MsgPlus
and Messenger
utility objects, it can acquire them in one function it exports, say, acquire()
. Then, your script can use Interop.Call()
to pass the objects to the DLL:
Interop.Call("myowndll.dll", "acquire", MsgPlus, Messenger);
In the DLL codes, scripting objects are passed as IDispatch*
pointers. So, the declaration of the above acquire()
will look like:
extern "C" __declspec(dllexport) int __stdcall acquire(IDispatch* dispMsgPlus, IDispatch* dispMessenger);
You will need to convert these IDispatch*
pointers to their corresponding _IMPxxxxxxPtr
pointers. The following code will do the job, taking _IMPMsgPlusPtr
as example:
_IMPMsgPlusPtr pMsgPlus; // Note: I assume using namespace MPLiveScriptingLib
_IMPMsgPlusPtr::Interface* rawMsgPlus;
dispMsgPlus->QueryInterface(&rawMsgPlus);
pMsgPlus.Attach(rawMsgPlus);
The code above will get you a fully functional _IMPMsgPlusPtr
smart interface pointer named pMsgPlus
. I will detail how to use the interface pointers in the next section.
Of course, duplicating codes to acquire each interface pointer should be avoided. We can write a template function:
template<typename IPtr>
HRESULT acquireInterfacePtr(IDispatch* pDispatch, IPtr& ptr){
IPtr::Interface* raw;
HRESULT hr;
if(SUCCEEDED(hr = pDispatch->QueryInterface(&raw))){
ptr.Attach(raw);
}
return hr;
}
The above template function can be reused to acquire different types of interface pointers, like the following codes:
_IMPMsgPlus pMsgPlus;
_IMPMessenger pMessenger;
extern "C" __declspec(dllexport) int __stdcall acquire(IDispatch* dispMsgPlus, IDispatch* dispMessenger){
acquireInterfacePtr(dispMsgPlus, pMsgPlus);
acquireInterfacePtr(dispMessenger, pMessenger);
// the interface pointers are usable now
}
The above template function also takes care of the return value of QueryInterface()
, so in case anything fails (can be checked with the return value of acquireInterfacePtr()
), you will know it.
After your interface pointers do point to the actual objects, you can use them in your C++ code just like you do in JScript, with some minor twists. First, you use the ->
"member by pointer" operator to access the members of the acquired interface pointers (the .
"member" operator is used to manipulate the smart pointer, and we are not interested in it).
If you ever type pMessenger->
and let IntelliSense come up with the AutoComplete list, you'd find that there are three available options for one single property, and two available options for one single method.
MyDisplayPicture
), the three are: one being the property itself , as outlined in the scripting documentation, like a variable (i.e. MyDisplayPicture
); another being two functions, named as the name of the property with "Get" and "Put" prepended (like GetMyDisplayPicture()
and PutMyDisplayPicture()
); yet another being two functions, named as the name of the property with "get_" and "put_" prepended (like get_MyDisplayPicture()
and put_MyDisplayPicture()
). The functions are essentially the getter and setter of the property in question; the ones with the capitalized "G" and "P" are the wrapped and error-checking version of the lower-case and underscored "g" and "p" counterparts, and you should always prefer the former over the latter. If you set or retrieve the property value using the variable-like property name directly, in actuality the captialized "G" and "P" getter and setter are called. My preference is to use the capitalized "G" and "P" functions, but you may opt to use the member variable. I would discourage using the lower-case and underscored "g" and "p" ones, because you lose all the benefits of automatic wrapping and error checking.AutoSignin()
), the two are: the one with the method name, as outlined in the scripting documentation (i.e. AutoSignin()
); another one named as the method name with "raw_" prepended (like raw_AutoSignin()
). Needless to say, the former is the wrapped and error-checking version of the latter, and I encourage you to use the former.Unlike JScript, C++ is a strong-typed language, requiring that all function parameters and return values have their types explicitly declared. The following table outlines the types, as used in the scripting documentation, and their corresponding ones used in the wrapped functions in the type library.
Type in Scripting Documentation | Type in Type Library | Description |
---|---|---|
bool |
VARIANT_BOOL |
Can be VARIANT_TRUE or VARIANT_FALSE . |
string |
_bstr_t |
_bstr_t is a wrapper of BSTR strings. The constructors of it support converting from char* and wchar_t* . It can also be cast into char* and wchar_t* . They're all done implicitly, so it shouldn't be a problem. |
object |
IDispatchPtr |
When objects are returned by methods or properties, you need to convert the pointer to the correct interface pointers, using the methods outlined in the section above. (Just use IDispatchPtr like IDispatch* )Example: Messenger.CurrentChats , ChatWnd::Contacts , and many others. |
number |
int or long (integers) |
|
float (floating-points, i.e. "fractions") |
||
enum |
enum of the corresponding enumeration |
Incidentally, some methods that are documented as "no return value" in the scripting documentation actually return a HRESULT. The value is returned by Plus! and I don't know it means.
Optional parameters are always declared as a constant reference to _variant_t
regardless of their actual types, and can also be optional in your C++ codes. Don't let this queerish type scare you! The type, essentially a class (thanks to the automatic wrapping), can be implicitly cast from a variety of other types without any trouble, including the ones used in scripts: integers, booleans and strings. So when you need to pass an optional parameter, just think it as though it was defined with its real type. For example, to use the PlaySound()
with our earlier pMsgPlus
interface pointer with MaxPlayTime
specified as 3.5 seconds, the code will do the trick: pMsgPlus->PlaySound(L"sound.wav", 3500);
, despite that the second parameter is delcared as const _variant_t &
. Nicely done, right?
What has been left so far is the use of Enumerator
s to enumerator contacts from Contacts
, chat windows from ChatWnds
, and emoticons from Emoticons
. The following codes will illustrate how to enumerate contacts from a chat window.
_IMPChatWndPtr pChatWnd;
// (here, do something to make pChatWnd actually points to a chat window object)
IDispatchPtr dispContacts = pChatWnd->GetContacts();
_IMPContactsPtr pContacts;
acquireInterfacePtr(dispContacts, pContacts);
IEnumVARIANT *pEnumVariant;
IUnknownPtr pUnknown = pContacts->Get_NewEnum();
pUnknown->QueryInterface(&pEnumVariant);
VARIANT var;
VariantInit(&var);
while(pEnumVariant->Next(1, &var, NULL) == S_OK){
IDispatch* pDispatch = V_DISPATCH(&var);
_IMPContactPtr pContact;
acquireInterfacePtr(pDispatch, pContact);
// now you've got the contact in pContact!
VariantClear(&var);
}
The example codes pack the source codes of a DLL that does two things:
ChatWnd
object when a chat window is created, and then subclasses the chat window, and posts a message to the Plus! script debug window whenever the chat window is resized (by capturing WM_SIZE
in the subclass Window Procedure).The source codes of the corresponding Plus! script are also packed. Note that the DLL part is a Visual Studio 2010 solution. The solution file itself probably can't be opened by prior versions of Visual Studio, but since I didn't do anything special with the solution and the project properties (aside from changing "Multi-Thread (Debug) DLL" to "Multi-Thread (Debug)", and assigning a Module-Definition File), you should be able to reconstruct the solution and the project with prior versions of Visual Studio without major troubles.
Sometimes it would be desirable to call JScript functions inside our DLL. To let the DLL know what function to call, you need to pass the function object to the DLL. A function object is passed with its name directly, without parentheses, and not in the form of a string. So, to let your DLL call a function named callbackFunc
with three arguments, the JScript codes will look like:
function callbackFunc(stringArg, fractionArg, integerArg){
// do something here
}
// somewhere else
Interop.Call("myowndll.dll", "pleaseCallback", callbackFunc);
And the C++ codes inside the DLL will look like this:
extern "C" __declspec(dllexport) int __stdcall pleaseCallback(IDispatch* dispFunc){
OLECHAR* methodName = L"call";
DISPID idDisp;
dispFunc->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_USER_DEFAULT, &idDisp);
DISPPARAMS params;
params.cArgs = 4; // yes, four, instead of three; explanation below
params.cNamedArgs = 0;
params.rgvarg = new VARIANTARG[4];
// assign the parameters in the reverse order
params.rgvarg[3].vt = VT_NULL;
params.rgvarg[2].vt = VT_INT;
params.rgvarg[2].intVal = 123;
params.rgvarg[1].vt = VT_R8;
params.rgvarg[1].dblVal = 0.456;
params.rgvarg[0].vt = VT_BSTR;
params.rgvarg[0].bstrVal = SysAllocString(L"Some String");
dispFunc->Invoke(idDisp, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
SysFreeString(params.rgvarg[0].bstrVal);
delete [] params.rgvarg;
}
You might have noticed that there is one confusing issue regarding the parameters: even though the callbackFunc()
function has only three arguments, in the C++ codes we allocate four arguments. This is because we need to specify what the usually-implicit this
object is inside the JScript function. In the codes above I use null
for the this
object, but there are indeed times you actually need a this
object. Take the following JScript codes as example:
function ExampleClass(){
this.someVar = "blah";
this.testFunc = function(stringArg, fractionArg, integerArg){
Debug.Trace(this.someVar);
}
}
// somewhere else
var exampleObject = new ExampleClass();
exampleObject.someVar = "blahblah!";
For your DLL to correctly call back exampleObject
's testFunc()
, you need to designate this
to be exampleObject
. For this you need to pass exampleObject
from JScript to the DLL, and relay it in your C++ codes:
// JScript code
Interop.Call("myowndll.dll", "pleaseCallback", exampleObject.testFunc, exampleObject); // use exampleObject.testFunc, not ExampleClass.testfunc
// C++ code
extern "C" __declspec(dllexport) int __stdcall pleaseCallback(IDispatch* dispFunc, IDispatch* dispThis){
// similar to the above C++ codes, except that...
params.rgvarg[3].vt = VT_DISPATCH;
params.rgvarg[3].pdispVal = dispThis;
}
Please note that when you pass the function to pleaseCallback()
, you use exampleObject.testFunc
, instead of ExampleClass.testFunc
. Also please note the extra argument in pleaseCallback()
's declaration for this
.
In all, please also note that the parameters are assigned in the reverse order, with the right-most argument as the 0-th parameter, and this
being the last parameter. Also, you can check the return value of GetIDsOfNames()
and Invoke()
to make sure things are going well. As for parameter types, I have demonstrated integer and fraction numbers along with strings in the codes above. If you want to use boolean values, just use VT_BOOL
for the vt
member, and assign VARIANT_TRUE
or VARIANT_FALSE
to the boolVal
member. As for arrays, I haven't tried but I think you have to fiddle around with SAFEARRAY
s. (I would honestly appreciate it if anyone could come up with some working array-passing codes, and share them with us.)
As per the scripting documentation, if you create new threads in your DLL, be sure to manipulate the scripting objects with the main thread. To quote the documentation, functions and properties will fail or may create unexpected bugs if called from a different thread.
For anything related to this tutorial, replying this thread on the official Messenger Plus! Community Forums is the easiest and the fatest way.