Author |
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 14 January 2007 at 6:42am | IP Logged
|
|
|
Hi All,
I am using mailbee objects in powerbuilder and need to write a sink in c++ to get hold of the events triggered by mailbee objects as powerbuilder does not support com object event handling.
I tried to get the event interface for the SMTP object and code a sink for the SMTP events. The code compiles and seems to work fine until I call any function that triggers events like Send or SendEx...
I get "error callling external function .. R0035" (Powerbuilder error).
Without the sink everything works fine ,but then I do not have access to the events!
I am lost.
Any ideas ?
Regards
johan
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 14 January 2007 at 4:52pm | IP Logged
|
|
|
Actually, COM event sinks is very complicated thing and it's hard to tell what exactly causes the problem. To make the life easier, I developed a small wrapper which creates ActiveX control from a MailBee object. The original version of this wrapper supported IMAP4 object only, now I added POP3 and SMTP. You can get it from http://www.afterlogic.com/updates/mailbee_ctls.zip
You'll also need the most recent version of MailBee.dll
Re-register MailBee.dll and register MailBeeCtls.dll with regsrv32. Now you should be able to add it as a control to your application form and subscribe to the events using UI of your development environment (PowerBuilder allows subscribing to events of visual ActiveX controls). Every ActiveX control in MailBeeCtls library has writable property (SMTP, POP3, or IMAP4) which provides access to the corresponding object. You can attach your existing SMTP object like below (in VBScript syntax):
Code:
Set MySMTPControl.SMTP = MySMTPObject
|
|
|
Of course, these MailBee "visual" controls are not visible at runtime. They are design-time components.
Note to other readers. The same approach can be used to listen to MailBee events in other languages which can consume events of ActiveX controls only (but cannot consume generic COM events).
Regards,
Alex
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 15 January 2007 at 6:11am | IP Logged
|
|
|
Alex,
Thanks for the reply.
This will make my life a lot easier.
I appreciate the response.
Regards
johan
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 15 January 2007 at 2:24pm | IP Logged
|
|
|
Hi Alex,
Tried to insert it as activex control in powerbuilder, but got error:
"OLE Object is missing critical interface 'IPersistStorage'.
I managed to get my sink working for the SMTP object. Events get triggered in my custom non-visual object in powerbuilder now through this sink. The only problem is that events with pointer parameters like bool* does not seem to send the modified pointer value back to the control even if you change it in the event. It just uses the default values. Are you able to send data back to control via the sink outgoing interface of the control ?
Any ideas ?
Regards
johan
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 15 January 2007 at 4:09pm | IP Logged
|
|
|
Quote:
"OLE Object is missing critical interface 'IPersistStorage' |
|
|
Ah, seems powerbuilder requires ActiveX control be Full, not Lite control. I now added IPersistStorage and IDataStorage to the supported interfaces. The link to the update is the same.
Quote:
Are you able to send data back to control via the sink outgoing interface of the control? |
|
|
Actually, I got the same problem. I think this is limitation of script-oriented languages (I found the same problem with javascript). Seems passing parameters by reference (not by value) is not always supported by host environments. However, there is a couple of workarounds available:
1) Why do you need to set Proceed to false? To abort things if desired? If yes, you can call POP3.Abort instead.
2) You can change event object model in your wrapper to somewhat like MailBee.NET event object model. I assume you have no problems when changing values of event parameters in C++ code (i.e. the value does not get updated when you attempt to change it in your wrapper ActiveX event handler in powerbuilder but you can change event parameter values of MailBee.SMTP object in C++ code of your wrapper). For instance, create OnSendProgressEventArgs class containing get/put Proceed property and pass a reference to the instance of this class as an event parameter. Powerbuilder event handler will simply set eventParam.Proceed=False, but the object reference eventParam will remain the same and lack of passing parameters by reference will not be a concern. When your C++ code then gets results from the host, it updates Proceed value of MailBee.SMTP event with eventParam.Proceed value.
Note: as mentioned above, I assume everything is fine on C++ side of things. However, it might not be so if you're using VC++ 6.0. By default, their ClassWizard "Implement Connection Point" feature incorrectly implements BOOL* (VT_BYREF is missing). For instance, it generates something like this:
Code:
HRESULT Fire_OnReceiveData(LONG BytesReceived, VARIANT_BOOL * Proceed)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[2];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
VariantClear(&varResult);
pvars [1] = BytesReceived;
pvars [0] = Proceed;
DISPPARAMS disp = { pvars, NULL, 2, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
delete[] pvars;
return varResult.scode;
}
|
|
|
"pvars[0] = Proceed;" makes it ByVal (which is default). To make it ByRef, replace it with 2 lines:
Code:
pvars[0].vt = VT_BYREF|VT_BOOL;
pvars[0].pboolVal = Proceed;
|
|
|
Backup this change since you have to repeat it every time you ask wizard to implement connection point.
I hope this helps.
Regards,
Alex
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 16 January 2007 at 2:41am | IP Logged
|
|
|
Hi Alex,
Thanks for the reply.
I have tried to set the bproceed=false value directly on the c++ side ,but with the same (wrong) result.
I am using the PBNI (powerbuilder native interface) and VC2003 to write the sink.
I am not using a wizard.
My code looks like this:
MyEventSink.h
=============
class CEventSink : public CCmdTarget
{
DECLARE_DYNCREATE(CEventSink)
// constructor
CEventSink();   ;
public:
virtual void OnFinalRelease();
// destructor
virtual ~CEventSink();
// public function
void PassUserData ( LPARAM userData ) ;
protected:
//
DECLARE_MESSAGE_MAP()
afx_msg HRESULT OnSendStart(bool* bProceed);
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
// User data structure
struct UserData
{
IPB_Session* session;
pbobject object;
};
// ref to user data structure
UserData* &nb sp; m_userdata ;
};
MyEventSink.cpp
===============
IMPLEMENT_DYNCREATE(CEventSink, CCmdTarget)
// constructor for class CEventSink
CEventSink::CEventSink()
{
EnableAutomation();
}
// destructor for class CEventSink
CEventSink::~CEventSink()
{
}
// OLE function call
void CEventSink::OnFinalRelease()
{
CCmdTarget::OnFinalRelease();
}
// function to set data
void CEventSink::PassUserData ( LPARAM userData )
{
m_userdata = (UserData*)userData;
}
// Begins the definition of your message map.
BEGIN_MESSAGE_MAP(CEventSink, CCmdTarget)
END_MESSAGE_MAP()
// Declares the definition of your dispatch map.
BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)
DISP_FUNCTION(CEventSink, "OnSendStart", OnSendStart, VT_I4, VTS_PBOOL) &n bsp;
END_DISPATCH_MAP()
// set GUID of interface
static const GUID IID_ICEventSink = {0x4CEFF81B,0x2B0F,0x4E6A,{0xB7,0x53,0x48,0xE2,0x34,0x82,0xF 0,0xFE}};
// Begins the definition of the interfaced map when used in the implementation file.
BEGIN_INTERFACE_MAP(CEventSink, CCmdTarget)
INTERFACE_PART(CEventSink, IID_ICEventSink, Dispatch)
END_INTERFACE_MAP()
HRESULT CEventSink::OnSendStart(bool* bProceed)
{
// omitted pbni specific code
*bProceed = false;
return 0;
}
powerbuilder specific code omitted.
I am not a OLE guru, but it seems to me that the bool* datatype in the sink is not able to handle and set the memory to the passed in pointer correctly so that the control have access to the modified value.
Should I change bool* to VARIANT_BOOL *? I do not think powerbuilder will like this ?
Any ideas?
Regards
johan
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 16 January 2007 at 9:50am | IP Logged
|
|
|
Actually, we never used VC++ 2003 so I'm not familiar with the macros used in your code and cannot tell for sure what exactly is wrong.
Anyway, using VARIANT_BOOL is recommended since it's standard ActiveX version of Boolean type. bool, BOOL, and VARIANT_BOOL are all different types because they define True differently (non-zero in bool, 1 in BOOL, and -1 (0xFFFFFFFF) in VARIANT_BOOL).
However, since you're assigning to zero (which has the same meaning in all boolean types), I'm not sure migrating to VARIANT_BOOL will help with your particular problem.
On other hand, there is yet another difference between VARIANT_BOOL and bool. bool is 1-byte length, VARIANT_BOOL is 4-byte length. In theory, the following scenario is possible: "*bProceed = false" succeeds and the memory gets updated but only 1 byte of 4. The other 3 bytes remain 0xFF and the entire 4 byte value is still non-zero. Thus, it's still worth trying to use VARIANT_BOOL, and it will not do any harm since using VARIANT_BOOL is more correct anyway.
Regards,
Alex
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 17 January 2007 at 1:03am | IP Logged
|
|
|
Hi Alex,
Your response send me in the correct direction.
Changing bool to VARIANT_BOOL solved the problem.
Everything seems to work fine now.
Appreciate your feedback and responses.
Regards
johan
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 22 January 2007 at 10:07am | IP Logged
|
|
|
Hi Alex,
The above implementation works 100% when I did it for the SMTP event interface.
I want to do the same now for POP3. The problem is that
m_lpDispatch->QueryInterface(IID_IConnectionPointContaine r,...)
ONLY returns the SMTP event interface GUID.
Enumerating through the list only returns the one interface. Any ideas ?
Regards
johan
|
Back to Top |
|
|
Alex AfterLogic Support
Joined: 19 November 2003
Online Status: Offline Posts: 2206
|
Posted: 22 January 2007 at 10:26am | IP Logged
|
|
|
I'm not sure I understood everything correctly. Do you mean m_lpDispatch is a reference to the instance of MailBee.POP3 interface but IConnectionPointContainer request returns event interface for SMTP rather than for POP3?
Maybe, lpDispatch simply refers to the wrong object (SMTP instead of POP3)?
Regards,
Alex
|
Back to Top |
|
|
johan Newbie
Joined: 14 January 2007 Location: South Africa
Online Status: Offline Posts: 7
|
Posted: 22 January 2007 at 10:38am | IP Logged
|
|
|
Hi Alex,
I am sorry. You are correct. m_lpDispatch is pointing to MailBee.SMTP while I am looking for MailBee.POP3 !
I am trying to incorporate all the outgoing event interfaces into one non-visual object, but this seems a bit tricky if not do-able at all...
regards
johan
|
Back to Top |
|
|