Anyhow, I've looked up "passing custom data types" on the web and saw a
_very_ painful way to do it here:
http://www.codeproject.com/KB/atl/udtdemo.aspx
Frankly I have too many structs to do this, and they'll change periodically
and i don't want to re-write the interface.
I'm looking at some code examples where people use a SAFEARRAY and a
DISPPARAMS and memcpy the struct into, but it seems overly complex for me , i
don't need to use the Invoke() method.
can anyone suggest any alternatives? Are there parts of this puzzle that I
can safely cut out? I'm having a hard time understanding which parts do what.
Thank you.
--
Raj Banerjee, LogMeIn inc
http://vcfaq.mvps.org/com/4.htm
http://vcfaq.mvps.org/com/5.htm
If you want simplicity and don't need to worry about clients written in
VB, go with the second one.
> Frankly I have too many structs to do this, and they'll change
> periodically and i don't want to re-write the interface.
Why do you feel you would need to rewrite the interface?
--
With best wishes,
Igor Tandetnik
With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925
Hi, I'm in the same situation: I need to pass a struct to an event to
VB.
Visual Studio simply create this code:
Where T_ErrorInfo is:
typedef /* [helpstring][version][uuid] */ DECLSPEC_UUID
("BF5573C3-28DD-11DE-ABF5-005056C00008") struct T_ErrorInfo
{
long err_no;
BSTR description;
BSTR routine;
} T_ErrorInfo;
//------------------------------------
HRESULT Fire_OnError( T_ErrorInfo error_info )
{
..... code ....
CComVariant avarParams[1];
avarParams[0] = error_info;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(6,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
¶ms,
&varResult,
NULL,
NULL);
}
//------------------------------------
but of course doesn't work because I cannot simply assign: avarParams
[0] = error_info;
I've changed this piece of code with:
//------------------------------------
HRESULT Fire_OnError( T_ErrorInfo error_info )
{
..... code ....
CComVariant avarParams[1];
IRecordInfo *pRI;
hr = GetRecordInfoFromGuids(&LIBID_AxEuroATLib,
1,
0,
LOCALE_USER_DEFAULT,
&__uuidof(T_ErrorInfo),
&pRI);
VariantInit(&avarParams[0]);
avarParams[0].vt = VT_RECORD;
avarParams[0].pvRecord = &error_info;
avarParams[0].pRecInfo = pRI;
pRI = NULL;
//CComVariant avarParams[1];
//avarParams[0] = error_info;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(6,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD,
¶ms,
&varResult,
NULL,
NULL);
}
//------------------------------------
But I get an error:
'T_ErrorInfo' : no GUID has been associated with this object
on calling GetRecordInfoFromGuids.
Is my code right? Or, where is the mistake?
Thanks!
Daniele
The wizard doesn't handle UDTs correctly. You will have to fix generated
code by hand.
Ok, I dont' understand one crucial thing..
VB wants to pass UDT in events ByRef so in my IDL I put:
[id(1), helpstring("method OnError")] HRESULT OnError([in] T_ErrorInfo
* error_info);
So, if I think right, for any [in] params the caller (VB runtime) is
responsible to create and destroy the object..
In my C++ code I made this Connection Point implementation:
HRESULT Fire_OnError( T_ErrorInfo * error_info )
{
...
CComVariant avarParams[1];
VariantInit(&avarParams[0]);
avarParams[0].vt = VT_RECORD;
avarParams[0].pvRecord = error_info;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
...
}
But, when I have to raise the event calling the Fire_OnError(), how
(and who) the struct is allocated?
In my c++ coclass I cannot do something like the following (I get a
VB6.EXE GPF) because the struct is destroyed, no?
{
T_ErrorInfo t_error_info;
t_error_info.description = _bstr_t( "DESC" );
t_error_info.routine = _bstr_t( "ROUTINE" );
t_error_info.err_no = 1000;
_ConnPointImpl.Fire_OnError( t_error_info );
}
Could someone give me some suggestions?
Thanks a lot!
Daniele.
There are two symbols in this code - a struct name "struct T_ErrorInfo"
and a typedef name T_ErrorInfo. The GUID is associated with the former
but not with the latter. Hence the error.
To avoid this confusion, it's best not to use a typedef in your IDL.
There's a minor inconvenience of having to write "struct T_ErrorInfo"
everywhere in the IDL, but it's worth it.
I'm confused. First you say you (I presume, a C++ program of yours) want
to pass a UDT to VB, and now you say VB wants to pass UDT to you. Which
way is it?
> so in my IDL I put:
>
> [id(1), helpstring("method OnError")] HRESULT OnError([in] T_ErrorInfo
> * error_info);
>
> So, if I think right, for any [in] params the caller (VB runtime) is
> responsible to create and destroy the object..
Yes and no. It's true that an [in] parameter is allocated and freed by
the caller. But note that, when you fire an event, _you_ are the caller,
and event hanlder (presumably on VB side) is the callee.
> In my C++ code I made this Connection Point implementation:
>
> HRESULT Fire_OnError( T_ErrorInfo * error_info )
> }
>
> But, when I have to raise the event calling the Fire_OnError(), how
> (and who) the struct is allocated?
Just put an instance on the stack:
T_ErrorInfo error_info;
Fire_OnError(&error_info);
> {
> T_ErrorInfo t_error_info;
>
> t_error_info.description = _bstr_t( "DESC" );
At the semicolon, a temporary _bstr_t object is destroyed and
deallocates the BSTR. t_error_info.description ends up a dangling
reference.
> t_error_info.routine = _bstr_t( "ROUTINE" );
> t_error_info.err_no = 1000;
>
> _ConnPointImpl.Fire_OnError( t_error_info );
This shouldn't even compile. Fire_OnError takes a pointer to
T_ErrorInfo.
I didn't explain well because I'm confused too!!
But one step at time I'll see the light!!
Here, I mean simply that VB wants events parameters to be passed
ByRef..
> Yes and no. It's true that an [in] parameter is allocated and freed by
> the caller. But note that, when you fire an event, _you_ are the caller,
> and event hanlder (presumably on VB side) is the callee.
Oh! Here is my mistake!
Ok..so, I summarize my understanding..
1) VB wants an UDT parameters to be passed ByRef in the events so in
IDL I use :
[id(1), helpstring("method OnError")] HRESULT OnError([in] T_ErrorInfo
* error_info);
2) In the Connection Point implementation, can I pass the struct by
value to avoid problems?
HRESULT Fire_OnError( T_ErrorInfo error_info )
3) In the Fire_OnError body I wrote:
CComVariant avarParams[1];
IRecordInfo *pRI;
hr = GetRecordInfoFromGuids(LIBID_AxEuroATLib,
1,
0,
LOCALE_USER_DEFAULT,
T_SDSInfo_IID,
&pRI);
VariantInit(&avarParams[0]);
avarParams[0].vt = VT_RECORD;
avarParams[0].pvRecord = &sds_info;
avarParams[0].pRecInfo = pRI;
pRI = NULL;
That works, and in VB I receive the event but, when I access to the
UDT it crash:
Private Sub MyObj_OnError(error_info As AxEuroATLib.T_ErrorInfo)
Debug.Print "ERROR : " & error_info.Description <<<<<< CRASH!!!!
End Sub
I hope to be near the end!!
Thanks a lot for the support!!
Daniele.
You can, but I don't quite see how it helps. Precisely what kind of
problems is this supposed to avoid?
> 3) In the Fire_OnError body I wrote:
>
> CComVariant avarParams[1];
> IRecordInfo *pRI;
>
> hr = GetRecordInfoFromGuids(LIBID_AxEuroATLib,
> 1,
> 0,
> LOCALE_USER_DEFAULT,
> T_SDSInfo_IID,
> &pRI);
> VariantInit(&avarParams[0]);
> avarParams[0].vt = VT_RECORD;
> avarParams[0].pvRecord = &sds_info;
> avarParams[0].pRecInfo = pRI;
> pRI = NULL;
Now I remember what the deal is with UDTs. When you pack a UDT into a
VARIANT, and later call VariantClear on it (which CComVariant's
destructor would do automatically in your case), VariantClear does three
things:
1. Calls IRecordInfo::RecordClear on your struct. This would deallocate
any fields that require deallocation (such as BSTR fields you have).
2. Releases IRecordInfo pointer.
3. Deallocates the struct itself with CoTaskMemFree (under assumption
that it was allocated by CoTaskMemAlloc).
So you have two options. Either you clean up with VariantClear, then you
need to allocate your struct with CoTaskMemAlloc. Or you avoid
VariantClear (by using plain VARIANT rather than CComVariant) and
perform steps #1 and #2 manually, then the structure can be allocated in
any way you want, including on the stack.
> That works, and in VB I receive the event but, when I access to the
> UDT it crash:
>
> Private Sub MyObj_OnError(error_info As AxEuroATLib.T_ErrorInfo)
> Debug.Print "ERROR : " & error_info.Description <<<<<< CRASH!!!!
> End Sub
Have you noticed the part of my previous response where I pointed out
that you are passing garbage pointers instead of valid BSTRs?
Thanks a lot Igor, using CoTaskMemAlloc works!
So, I want say "I don't know how to thank you!!"
Thanks for any explanations! Now I'm a little bit more aware on COM in
ATL!!
Daniele.
> Now I remember what the deal is with UDTs. When you pack a UDT into a
> VARIANT, and later call VariantClear on it (which CComVariant's
> destructor would do automatically in your case), VariantClear does three
> things:
>
> 1. Calls IRecordInfo::RecordClear on your struct. This would deallocate
> any fields that require deallocation (such as BSTR fields you have).
> 2. Releases IRecordInfo pointer.
> 3. Deallocates the struct itself with CoTaskMemFree (under assumption
> that it was allocated by CoTaskMemAlloc).
>
> So you have two options. Either you clean up with VariantClear, then you
> need to allocate your struct with CoTaskMemAlloc. Or you avoid
> VariantClear (by using plain VARIANT rather than CComVariant) and
> perform steps #1 and #2 manually, then the structure can be allocated in
> any way you want, including on the stack.
Thanks Igor!
Using CoTaskMemAlloc works!
Thanks again for the support and for your explanations!
Daniele.