Ok, I have been happily coding for hours in COM.. (it was surprisingly ez to get going at with some examples, and C++ code..
Anywho, Im hacking out MSAGENT com controls.. I have only gotten as far as CoInitailize the IAgentEx control..
My problem is i need to load a character from here. The method requires a VARIANT type to hold the file address of the character when calling, so i made a special proto for the Interface to handle this varient type. I cant seem to create a BSTR to fill the variant type with.
If anyone can help me with these wide strings it would be appreciated.. (I tried L.inc.. but it doesnt like D:\...)
Here is my code thus far:
;
; MS AGENT IN ASM
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\ole32.inc
include \masm32\include\bstrlib.inc
include \masm32\include\oleaut32.inc
include \masm32\include\L.inc
include \masm32\com\include\oaidl.inc
include \masm32\com\include\shlobj.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\bstrlib.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\ole32.lib
includelib \masm32\lib\oleaut32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
comethodspProto typedef proto :DWORD,:VARIANT,:DWORD,:DWORD
comethodsp typedef ptr comethodspProto
IAgentEx STRUCT DWORD
IAgentEx_Load comethodsp ?
IAgentEx_Unload comethod2 ?
IAgentEx_Register comethod3 ?
IAgentEx_Unregister comethod2 ?
IAgentEx_GetCharacter comethod3 ?
IAgentEx ENDS
comment %
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Load )(
IAgent __RPC_FAR * This,
/* */ VARIANT vLoadKey,
/* */ long __RPC_FAR *pdwCharID,
/* */ long __RPC_FAR *pdwReqID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Unload )(
IAgent __RPC_FAR * This,
/* */ long dwCharID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Register )(
IAgent __RPC_FAR * This,
/* */ IUnknown __RPC_FAR *punkNotifySink,
/* */ long __RPC_FAR *pdwSinkID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Unregister )(
IAgent __RPC_FAR * This,
/* */ long dwSinkID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetCharacter )(
IAgent __RPC_FAR * This,
/* */ long dwCharID,
/* */ IDispatch __RPC_FAR *__RPC_FAR *ppunkCharacter);
%
.data
hInstance dd 0
hResult dd 0
pAgentEx dd 0
ICharID dd 0
IRequestID dd 0
; useage: sztext wchar L()
szTheChar wchar L()
szAppName db "MS AGENT IN ASM",0
sCLSID_AgentServer TEXTEQU <{0D45FD2FCH, 5C6EH, 11D1H, \
{09EH, 0C1H, 000H, 0C0H, 04FH, 0D7H, 008H, 01FH}}>
sIID_IAgentEx TEXTEQU <{048D12BA0H,5B77H,11d1H, \
{09EH,0C1H,000H,0C0H,04FH,0D7H,008H,01FH}}>
CLSID_AgentServer GUID sCLSID_AgentServer
IID_IAgentEx GUID sIID_IAgentEx
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain,hInstance,NULL,NULL,NULL
invoke ExitProcess,eax
WinMain proc hInst :DWORD,
hPrevInst :DWORD,
CmdLine :DWORD,
CmdShow :DWORD
LOCAL vPath :VARIANT
LOCAL p :DWORD
invoke CoInitialize, NULL
.IF SUCCEEDED
MakeMessage "CoInitialized!"
.endif
invoke CoCreateInstance, ADDR CLSID_AgentServer, NULL,
CLSCTX_SERVER,
ADDR IID_IAgentEx, ADDR pAgentEx
mov hResult, eax
tes
Gee, havn't you been the busy one? ;-)
Some points in order:
1) L can handle backslashes such as "D:\pathstuff", you just have to double them as in C:
wszMyPath wchar L()
2) Your interface proto looks fine, except I'm not so sure the variant is passed directly, and not by reference. I've never seen anything that resolved to larger then a dword pushed like that. (Question: Does this mean VARIANTs are always passed by reference? I ran thru some literature, it did not seem to say so.)
A very good way to check param size and count is put the coinvoke INSIDE a procedure. If the param count is wrong, this will result in a GPF due to an incorrect stack pointer (or if you use Dmacros, spy on esp with 'DPrintValD esp "the stack"' before and after the coinvoke)
3)BSTR's are a whole topic to themselves. A BSTR is a POINTER to a string structure. Never change this struct by yourself, always let the API's do it for you (although changing the words inside is OK, that's ALL you should do).
BSTRs are always unicode. Basically, if you made it, you destroy it, unless you give it away byref:
wszTest wchar L()
MyBstr BSTR 0 ; defines a null bstr
invoke SysAllocString, wszTest
mov MyBstr, eax
coinvoke pSomething, ISomething, MyBstr ; you still own the string
coinvoke pSomethingElse, ISomethingElse, ADDR MyBstr ; you just gave it away.
; You no longer own it
In a like fashion, and bstr's passed back to you as an byref you also own, and thus must destroy.
A bstr passed in to you as an param must be byref, and any pre-existing string must be deleted, then you are free to change the orgional. The SysReAllocString API will do this for you.
Think of it this way: If you just have the address of the string the bstr itself), leave it along. If you have the pointer to the address (ADDR bstr), you can change this pointer to another string and delete the orgional.
(There is a nice tut in my MSDN on BSTRs. I found it by searching for "Rules of BSTR")
Other then this, I'm out of ideas. This is not an interface I am familiar with, and the only times I've passed (or received) VARIANTS has been byref.Thanx Ernie, I will keep hacking at it.. Im starting to doubt that my structure IS correct..
Q1) Can i leave out function memebers from the structure if i dont intend to use them? (i have only defined 5, but there are more.)
Q2) Of this structure, is there an ordering preference that must be met?
Q3) There is a hash-key im not familair with in the C++ header preceeding this structure.. could this be a source of error??
This is the Entire IAgentEx as defined in C++:
(Look it over and see if it helps you understand the VARIANT situation.. :) )
EXTERN_C const IID IID_IAgentEx;
#if defined(__cplusplus) && !defined(CINTERFACE)
interface DECLSPEC_UUID("48D12BA0-5B77-11d1-9EC1-00C04FD7081F")
IAgentEx : public IAgent
{
public:
virtual HRESULT STDMETHODCALLTYPE GetCharacterEx(
/* */ long dwCharID,
/* */ IAgentCharacterEx __RPC_FAR *__RPC_FAR *ppCharacterEx) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVersion(
/* */ short __RPC_FAR *psMajor,
/* */ short __RPC_FAR *psMinor) = 0;
virtual HRESULT STDMETHODCALLTYPE ShowDefaultCharacterProperties(
/* */ short x,
/* */ short y,
/* */ long bUseDefaultPosition) = 0;
};
#else /* C style interface */
typedef struct IAgentExVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
IAgentEx __RPC_FAR * This,
/* */ REFIID riid,
/* */ void __RPC_FAR *__RPC_FAR *ppvObject);
ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
IAgentEx __RPC_FAR * This);
ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
IAgentEx __RPC_FAR * This);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
IAgentEx __RPC_FAR * This,
/* */ UINT __RPC_FAR *pctinfo);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
IAgentEx __RPC_FAR * This,
/* */ UINT iTInfo,
/* */ LCID lcid,
/* */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
IAgentEx __RPC_FAR * This,
/* */ REFIID riid,
/* */ DISPID __RPC_FAR *rgDispId);
/* */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
IAgentEx __RPC_FAR * This,
/* */ DISPID dispIdMember,
/* */ REFIID riid,
/* */ LCID lcid,
/* */ WORD wFlags,
/* */ DISPPARAMS __RPC_FAR *pDispParams,
/* */ VARIANT __RPC_FAR *pVarResult,
/* */ EXCEPINFO __RPC_FAR *pExcepInfo,
/* */ UINT __RPC_FAR *puArgErr);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Load )(
IAgentEx __RPC_FAR * This,
/* */ VARIANT vLoadKey,
/* */ long __RPC_FAR *pdwCharID,
/* */ long __RPC_FAR *pdwReqID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Unload )(
IAgentEx __RPC_FAR * This,
/* */ long dwCharID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Register )(
IAgentEx __RPC_FAR * This,
/* */ IUnknown __RPC_FAR *punkNotifySink,
/* */ long __RPC_FAR *pdwSinkID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Unregister )(
IAgentEx __RPC_FAR * This,
/* */ long dwSinkID);
HRESULT ( STDMETHODCALLTYPE __RPC_FAR
/* */ VARIANT vLoadKey,
(This)->lpVtbl -> Load(This,vLoadKey,pdwCharID,pdwReqID)
Either way, looks like BYVAL and not BYREF. IE, you push the whole variant, not ADDR variant.
You only need to define the interface members up to the highest one you use... if say it has 101 methods, but you only use up to 12, just defining those first 12 (in order) is A-OK.
Ahhh, a light slowly dawned on me. IAgentEx is a Dispinterface! That means it has the 3 IUnknown and 4 IDispatch functions first at the start! So it would look like so:
IAgentEx_LoadProto typedef proto :DWORD,:VARIANT,:DWORD,:DWORD
IAgentEx_LoadPtr typedef ptr IAgentEx_LoadProto
_vtIAgentEx MACRO CastName:REQ
; IDispatch methods first
_vtIDispatch CastName ; this also defines IUnknown, 7 methods total
; now the IAgent methods you defined
&CastName&_Load IAgentEx_LoadPtr ?
&CastName&_Unload comethod2 ?
&CastName&_Register comethod3 ?
&CastName&_Unregister comethod2 ?
&CastName&_GetCharacter comethod3 ?
; can add other methods here later
ENDM
IAgentEx STRUCT
_vtIAgentEx IAgentEx
IAgentEx ENDS
I hope you can read my interface macro ;-)
About the hash key, I'm not sure what you meant. Was that this:
interface DECLSPEC_UUID("48D12BA0-5B77-11d1-9EC1-00C04FD7081F")
That's just the interface IID GUID.
(I think you converted those GUIDs by hand... check the MASM32 SP2, there is a QuickEditor plug-in to do that for you in there)Ernie, you did it again!.. Thanx.. (I have gotten it working now..)
It was the BSTR issues.. I discovered that LOADSTRING from a resouce file turned the data back into ansii.. A bit of a surprise because the MSDN gives indication it can suport this.
My resouce was : 5051, L"needed stuff"
and the data came back as normal chars.. I have to use MultiByteToWideChar to make it back to Unicode, and then pass it though SysAllocString to make it a BSTR.. ( Im definitely thinking of a macro set here.. :)
Anywho, thanx for your advice and help.. My first little project with COMs was actually easier and more interesting than i thought it would be. (why i was putting it off in the first place.)
Im going to clean up my code and make a little tutorial outa it for others in the next week or so.
Oh BTW, when I defined my IAgentCharacter interface structure, i ran across a few functions with 'short' in their parameters... Seeing this is 32bit, i assumed everything is a DWORD regardless.. is this correct?
I havent tested these functions yet as they are a bit more obscure... but here is an example:
HRESULT STDMETHODCALLTYPE IAgentEx_GetVersion_Proxy(
IAgentEx __RPC_FAR * This,
/* */ short __RPC_FAR *psMajor,
/* */ short __RPC_FAR *psMinor);
NaNYep, Windows 9x has abysmal unicode support. MultiByteToWideChar and the inverse really get overused. I dislike these functions so much, I put em in macro wrappers so I don't have to look at them (macros at the end of bstrLib)
In my masm book, a C short is defined as a SWORD, so a custom (not a comethod) proto will be needed.
Sorry I didn't notice where you got your string, I knew LoadString only returns ASCII. (rc.exe BUILDS in unicode, that's what that mysterious "Using codepage xxxx as default" means (it's your system's default language).
It's good to hear things are working. Keep up the good work! I wanna see yout tut.
I have to say, after going through all the translations, when i look back at the ASM version verses the C++ version, the ASM is conscise and to the point! There is soooo much convolution in that C++ crap.. (why i pulled a homer on that 'hash-key' comment, my head was spinning while wading thru all that crap and didnt clue in at all.. )
w32asm sometimes is a bit more keystrokes, but it makes up in time spent trying to read source code...
NaN