1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/ext/win32ole/win32ole_event.c
suke 5b800bef9e * ext/win32ole/win32ole_event.c(ev_advise, ole_event_free,
fev_s_allocate, fev_unadvise): avoid segmentation fault when COM
  server freed before calling Unadvise from WIN32OLE_EVENT object.
* ext/win32ole/win32ole.c: ditto.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@47570 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2014-09-13 02:28:47 +00:00

1258 lines
35 KiB
C

#include "win32ole.h"
/*
* Document-class: WIN32OLE_EVENT
*
* <code>WIN32OLE_EVENT</code> objects controls OLE event.
*/
typedef struct {
struct IEventSinkVtbl * lpVtbl;
} IEventSink, *PEVENTSINK;
typedef struct IEventSinkVtbl IEventSinkVtbl;
struct IEventSinkVtbl {
STDMETHOD(QueryInterface)(
PEVENTSINK,
REFIID,
LPVOID *);
STDMETHOD_(ULONG, AddRef)(PEVENTSINK);
STDMETHOD_(ULONG, Release)(PEVENTSINK);
STDMETHOD(GetTypeInfoCount)(
PEVENTSINK,
UINT *);
STDMETHOD(GetTypeInfo)(
PEVENTSINK,
UINT,
LCID,
ITypeInfo **);
STDMETHOD(GetIDsOfNames)(
PEVENTSINK,
REFIID,
OLECHAR **,
UINT,
LCID,
DISPID *);
STDMETHOD(Invoke)(
PEVENTSINK,
DISPID,
REFIID,
LCID,
WORD,
DISPPARAMS *,
VARIANT *,
EXCEPINFO *,
UINT *);
};
typedef struct tagIEVENTSINKOBJ {
IEventSinkVtbl *lpVtbl;
DWORD m_cRef;
IID m_iid;
long m_event_id;
ITypeInfo *pTypeInfo;
}IEVENTSINKOBJ, *PIEVENTSINKOBJ;
struct oleeventdata {
DWORD dwCookie;
IConnectionPoint *pConnectionPoint;
IDispatch *pDispatch;
long event_id;
};
static VALUE ary_ole_event;
static ID id_events;
VALUE cWIN32OLE_EVENT;
static BOOL g_IsEventSinkVtblInitialized = FALSE;
static IEventSinkVtbl vtEventSink;
void EVENTSINK_Destructor(PIEVENTSINKOBJ);
static void ole_val2ptr_variant(VALUE val, VARIANT *var);
static void hash2ptr_dispparams(VALUE hash, ITypeInfo *pTypeInfo, DISPID dispid, DISPPARAMS *pdispparams);
static VALUE hash2result(VALUE hash);
static void ary2ptr_dispparams(VALUE ary, DISPPARAMS *pdispparams);
static VALUE exec_callback(VALUE arg);
static VALUE rescue_callback(VALUE arg);
static HRESULT find_iid(VALUE ole, char *pitf, IID *piid, ITypeInfo **ppTypeInfo);
static HRESULT find_coclass(ITypeInfo *pTypeInfo, TYPEATTR *pTypeAttr, ITypeInfo **pTypeInfo2, TYPEATTR **pTypeAttr2);
static HRESULT find_default_source_from_typeinfo(ITypeInfo *pTypeInfo, TYPEATTR *pTypeAttr, ITypeInfo **ppTypeInfo);
static HRESULT find_default_source(VALUE ole, IID *piid, ITypeInfo **ppTypeInfo);
static long ole_search_event_at(VALUE ary, VALUE ev);
static VALUE ole_search_event(VALUE ary, VALUE ev, BOOL *is_default);
static VALUE ole_search_handler_method(VALUE handler, VALUE ev, BOOL *is_default_handler);
static void ole_delete_event(VALUE ary, VALUE ev);
static void ole_event_free(struct oleeventdata *poleev);
static VALUE fev_s_allocate(VALUE klass);
static VALUE ev_advise(int argc, VALUE *argv, VALUE self);
static VALUE fev_initialize(int argc, VALUE *argv, VALUE self);
static void ole_msg_loop(void);
static VALUE fev_s_msg_loop(VALUE klass);
static void add_event_call_back(VALUE obj, VALUE event, VALUE data);
static VALUE ev_on_event(int argc, VALUE *argv, VALUE self, VALUE is_ary_arg);
static VALUE fev_on_event(int argc, VALUE *argv, VALUE self);
static VALUE fev_on_event_with_outargs(int argc, VALUE *argv, VALUE self);
static VALUE fev_off_event(int argc, VALUE *argv, VALUE self);
static VALUE fev_unadvise(VALUE self);
static VALUE fev_set_handler(VALUE self, VALUE val);
static VALUE fev_get_handler(VALUE self);
static VALUE evs_push(VALUE ev);
static VALUE evs_delete(long i);
static VALUE evs_entry(long i);
static long evs_length(void);
STDMETHODIMP EVENTSINK_Invoke(
PEVENTSINK pEventSink,
DISPID dispid,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pdispparams,
VARIANT *pvarResult,
EXCEPINFO *pexcepinfo,
UINT *puArgErr
) {
HRESULT hr;
BSTR bstr;
unsigned int count;
unsigned int i;
ITypeInfo *pTypeInfo;
VARIANT *pvar;
VALUE ary, obj, event, args, outargv, ev, result;
VALUE handler = Qnil;
VALUE arg[3];
VALUE mid;
VALUE is_outarg = Qfalse;
BOOL is_default_handler = FALSE;
int state;
PIEVENTSINKOBJ pEV = (PIEVENTSINKOBJ)pEventSink;
pTypeInfo = pEV->pTypeInfo;
obj = evs_entry(pEV->m_event_id);
if (!rb_obj_is_kind_of(obj, cWIN32OLE_EVENT)) {
return NOERROR;
}
ary = rb_ivar_get(obj, id_events);
if (NIL_P(ary) || !RB_TYPE_P(ary, T_ARRAY)) {
return NOERROR;
}
hr = pTypeInfo->lpVtbl->GetNames(pTypeInfo, dispid,
&bstr, 1, &count);
if (FAILED(hr)) {
return NOERROR;
}
ev = WC2VSTR(bstr);
event = ole_search_event(ary, ev, &is_default_handler);
if (RB_TYPE_P(event, T_ARRAY)) {
handler = rb_ary_entry(event, 0);
mid = rb_intern("call");
is_outarg = rb_ary_entry(event, 3);
} else {
handler = rb_ivar_get(obj, rb_intern("handler"));
if (handler == Qnil) {
return NOERROR;
}
mid = ole_search_handler_method(handler, ev, &is_default_handler);
}
if (handler == Qnil || mid == Qnil) {
return NOERROR;
}
args = rb_ary_new();
if (is_default_handler) {
rb_ary_push(args, ev);
}
/* make argument of event handler */
for (i = 0; i < pdispparams->cArgs; ++i) {
pvar = &pdispparams->rgvarg[pdispparams->cArgs-i-1];
rb_ary_push(args, ole_variant2val(pvar));
}
outargv = Qnil;
if (is_outarg == Qtrue) {
outargv = rb_ary_new();
rb_ary_push(args, outargv);
}
/*
* if exception raised in event callback,
* then you receive cfp consistency error.
* to avoid this error we use begin rescue end.
* and the exception raised then error message print
* and exit ruby process by Win32OLE itself.
*/
arg[0] = handler;
arg[1] = mid;
arg[2] = args;
result = rb_protect(exec_callback, (VALUE)arg, &state);
if (state != 0) {
rescue_callback(Qnil);
}
if(RB_TYPE_P(result, T_HASH)) {
hash2ptr_dispparams(result, pTypeInfo, dispid, pdispparams);
result = hash2result(result);
}else if (is_outarg == Qtrue && RB_TYPE_P(outargv, T_ARRAY)) {
ary2ptr_dispparams(outargv, pdispparams);
}
if (pvarResult) {
VariantInit(pvarResult);
ole_val2variant(result, pvarResult);
}
return NOERROR;
}
STDMETHODIMP
EVENTSINK_QueryInterface(
PEVENTSINK pEV,
REFIID iid,
LPVOID* ppv
) {
if (IsEqualIID(iid, &IID_IUnknown) ||
IsEqualIID(iid, &IID_IDispatch) ||
IsEqualIID(iid, &((PIEVENTSINKOBJ)pEV)->m_iid)) {
*ppv = pEV;
}
else {
*ppv = NULL;
return E_NOINTERFACE;
}
((LPUNKNOWN)*ppv)->lpVtbl->AddRef((LPUNKNOWN)*ppv);
return NOERROR;
}
STDMETHODIMP_(ULONG)
EVENTSINK_AddRef(
PEVENTSINK pEV
){
PIEVENTSINKOBJ pEVObj = (PIEVENTSINKOBJ)pEV;
return ++pEVObj->m_cRef;
}
STDMETHODIMP_(ULONG) EVENTSINK_Release(
PEVENTSINK pEV
) {
PIEVENTSINKOBJ pEVObj = (PIEVENTSINKOBJ)pEV;
--pEVObj->m_cRef;
if(pEVObj->m_cRef != 0)
return pEVObj->m_cRef;
EVENTSINK_Destructor(pEVObj);
return 0;
}
STDMETHODIMP EVENTSINK_GetTypeInfoCount(
PEVENTSINK pEV,
UINT *pct
) {
*pct = 0;
return NOERROR;
}
STDMETHODIMP EVENTSINK_GetTypeInfo(
PEVENTSINK pEV,
UINT info,
LCID lcid,
ITypeInfo **pInfo
) {
*pInfo = NULL;
return DISP_E_BADINDEX;
}
STDMETHODIMP EVENTSINK_GetIDsOfNames(
PEVENTSINK pEventSink,
REFIID riid,
OLECHAR **szNames,
UINT cNames,
LCID lcid,
DISPID *pDispID
) {
ITypeInfo *pTypeInfo;
PIEVENTSINKOBJ pEV = (PIEVENTSINKOBJ)pEventSink;
pTypeInfo = pEV->pTypeInfo;
if (pTypeInfo) {
return pTypeInfo->lpVtbl->GetIDsOfNames(pTypeInfo, szNames, cNames, pDispID);
}
return DISP_E_UNKNOWNNAME;
}
PIEVENTSINKOBJ
EVENTSINK_Constructor() {
PIEVENTSINKOBJ pEv;
if (!g_IsEventSinkVtblInitialized) {
vtEventSink.QueryInterface=EVENTSINK_QueryInterface;
vtEventSink.AddRef = EVENTSINK_AddRef;
vtEventSink.Release = EVENTSINK_Release;
vtEventSink.Invoke = EVENTSINK_Invoke;
vtEventSink.GetIDsOfNames = EVENTSINK_GetIDsOfNames;
vtEventSink.GetTypeInfoCount = EVENTSINK_GetTypeInfoCount;
vtEventSink.GetTypeInfo = EVENTSINK_GetTypeInfo;
g_IsEventSinkVtblInitialized = TRUE;
}
pEv = ALLOC_N(IEVENTSINKOBJ, 1);
if(pEv == NULL) return NULL;
pEv->lpVtbl = &vtEventSink;
pEv->m_cRef = 0;
pEv->m_event_id = 0;
pEv->pTypeInfo = NULL;
return pEv;
}
void
EVENTSINK_Destructor(
PIEVENTSINKOBJ pEVObj
) {
if(pEVObj != NULL) {
OLE_RELEASE(pEVObj->pTypeInfo);
free(pEVObj);
pEVObj = NULL;
}
}
static void
ole_val2ptr_variant(VALUE val, VARIANT *var)
{
switch (TYPE(val)) {
case T_STRING:
if (V_VT(var) == (VT_BSTR | VT_BYREF)) {
*V_BSTRREF(var) = ole_vstr2wc(val);
}
break;
case T_FIXNUM:
switch(V_VT(var)) {
case (VT_UI1 | VT_BYREF) :
*V_UI1REF(var) = NUM2CHR(val);
break;
case (VT_I2 | VT_BYREF) :
*V_I2REF(var) = (short)NUM2INT(val);
break;
case (VT_I4 | VT_BYREF) :
*V_I4REF(var) = NUM2INT(val);
break;
case (VT_R4 | VT_BYREF) :
*V_R4REF(var) = (float)NUM2INT(val);
break;
case (VT_R8 | VT_BYREF) :
*V_R8REF(var) = NUM2INT(val);
break;
default:
break;
}
break;
case T_FLOAT:
switch(V_VT(var)) {
case (VT_I2 | VT_BYREF) :
*V_I2REF(var) = (short)NUM2INT(val);
break;
case (VT_I4 | VT_BYREF) :
*V_I4REF(var) = NUM2INT(val);
break;
case (VT_R4 | VT_BYREF) :
*V_R4REF(var) = (float)NUM2DBL(val);
break;
case (VT_R8 | VT_BYREF) :
*V_R8REF(var) = NUM2DBL(val);
break;
default:
break;
}
break;
case T_BIGNUM:
if (V_VT(var) == (VT_R8 | VT_BYREF)) {
*V_R8REF(var) = rb_big2dbl(val);
}
break;
case T_TRUE:
if (V_VT(var) == (VT_BOOL | VT_BYREF)) {
*V_BOOLREF(var) = VARIANT_TRUE;
}
break;
case T_FALSE:
if (V_VT(var) == (VT_BOOL | VT_BYREF)) {
*V_BOOLREF(var) = VARIANT_FALSE;
}
break;
default:
break;
}
}
static void
hash2ptr_dispparams(VALUE hash, ITypeInfo *pTypeInfo, DISPID dispid, DISPPARAMS *pdispparams)
{
BSTR *bstrs;
HRESULT hr;
UINT len, i;
VARIANT *pvar;
VALUE val;
VALUE key;
len = 0;
bstrs = ALLOCA_N(BSTR, pdispparams->cArgs + 1);
hr = pTypeInfo->lpVtbl->GetNames(pTypeInfo, dispid,
bstrs, pdispparams->cArgs + 1,
&len);
if (FAILED(hr))
return;
for (i = 0; i < len - 1; i++) {
key = WC2VSTR(bstrs[i + 1]);
val = rb_hash_aref(hash, INT2FIX(i));
if (val == Qnil)
val = rb_hash_aref(hash, key);
if (val == Qnil)
val = rb_hash_aref(hash, rb_str_intern(key));
pvar = &pdispparams->rgvarg[pdispparams->cArgs-i-1];
ole_val2ptr_variant(val, pvar);
}
}
static VALUE
hash2result(VALUE hash)
{
VALUE ret = Qnil;
ret = rb_hash_aref(hash, rb_str_new2("return"));
if (ret == Qnil)
ret = rb_hash_aref(hash, rb_str_intern(rb_str_new2("return")));
return ret;
}
static void
ary2ptr_dispparams(VALUE ary, DISPPARAMS *pdispparams)
{
int i;
VALUE v;
VARIANT *pvar;
for(i = 0; i < RARRAY_LEN(ary) && (unsigned int) i < pdispparams->cArgs; i++) {
v = rb_ary_entry(ary, i);
pvar = &pdispparams->rgvarg[pdispparams->cArgs-i-1];
ole_val2ptr_variant(v, pvar);
}
}
static VALUE
exec_callback(VALUE arg)
{
VALUE *parg = (VALUE *)arg;
VALUE handler = parg[0];
VALUE mid = parg[1];
VALUE args = parg[2];
return rb_apply(handler, mid, args);
}
static VALUE
rescue_callback(VALUE arg)
{
VALUE error;
VALUE e = rb_errinfo();
VALUE bt = rb_funcall(e, rb_intern("backtrace"), 0);
VALUE msg = rb_funcall(e, rb_intern("message"), 0);
bt = rb_ary_entry(bt, 0);
error = rb_sprintf("%"PRIsVALUE": %"PRIsVALUE" (%s)\n", bt, msg, rb_obj_classname(e));
rb_write_error(StringValuePtr(error));
rb_backtrace();
ruby_finalize();
exit(-1);
return Qnil;
}
static HRESULT
find_iid(VALUE ole, char *pitf, IID *piid, ITypeInfo **ppTypeInfo)
{
HRESULT hr;
IDispatch *pDispatch;
ITypeInfo *pTypeInfo;
ITypeLib *pTypeLib;
TYPEATTR *pTypeAttr;
HREFTYPE RefType;
ITypeInfo *pImplTypeInfo;
TYPEATTR *pImplTypeAttr;
struct oledata *pole;
unsigned int index;
unsigned int count;
int type;
BSTR bstr;
char *pstr;
BOOL is_found = FALSE;
LCID lcid = cWIN32OLE_lcid;
OLEData_Get_Struct(ole, pole);
pDispatch = pole->pDispatch;
hr = pDispatch->lpVtbl->GetTypeInfo(pDispatch, 0, lcid, &pTypeInfo);
if (FAILED(hr))
return hr;
hr = pTypeInfo->lpVtbl->GetContainingTypeLib(pTypeInfo,
&pTypeLib,
&index);
OLE_RELEASE(pTypeInfo);
if (FAILED(hr))
return hr;
if (!pitf) {
hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib,
piid,
ppTypeInfo);
OLE_RELEASE(pTypeLib);
return hr;
}
count = pTypeLib->lpVtbl->GetTypeInfoCount(pTypeLib);
for (index = 0; index < count; index++) {
hr = pTypeLib->lpVtbl->GetTypeInfo(pTypeLib,
index,
&pTypeInfo);
if (FAILED(hr))
break;
hr = OLE_GET_TYPEATTR(pTypeInfo, &pTypeAttr);
if(FAILED(hr)) {
OLE_RELEASE(pTypeInfo);
break;
}
if(pTypeAttr->typekind == TKIND_COCLASS) {
for (type = 0; type < pTypeAttr->cImplTypes; type++) {
hr = pTypeInfo->lpVtbl->GetRefTypeOfImplType(pTypeInfo,
type,
&RefType);
if (FAILED(hr))
break;
hr = pTypeInfo->lpVtbl->GetRefTypeInfo(pTypeInfo,
RefType,
&pImplTypeInfo);
if (FAILED(hr))
break;
hr = pImplTypeInfo->lpVtbl->GetDocumentation(pImplTypeInfo,
-1,
&bstr,
NULL, NULL, NULL);
if (FAILED(hr)) {
OLE_RELEASE(pImplTypeInfo);
break;
}
pstr = ole_wc2mb(bstr);
if (strcmp(pitf, pstr) == 0) {
hr = pImplTypeInfo->lpVtbl->GetTypeAttr(pImplTypeInfo,
&pImplTypeAttr);
if (SUCCEEDED(hr)) {
is_found = TRUE;
*piid = pImplTypeAttr->guid;
if (ppTypeInfo) {
*ppTypeInfo = pImplTypeInfo;
(*ppTypeInfo)->lpVtbl->AddRef((*ppTypeInfo));
}
pImplTypeInfo->lpVtbl->ReleaseTypeAttr(pImplTypeInfo,
pImplTypeAttr);
}
}
free(pstr);
OLE_RELEASE(pImplTypeInfo);
if (is_found || FAILED(hr))
break;
}
}
OLE_RELEASE_TYPEATTR(pTypeInfo, pTypeAttr);
OLE_RELEASE(pTypeInfo);
if (is_found || FAILED(hr))
break;
}
OLE_RELEASE(pTypeLib);
if(!is_found)
return E_NOINTERFACE;
return hr;
}
static HRESULT
find_coclass(
ITypeInfo *pTypeInfo,
TYPEATTR *pTypeAttr,
ITypeInfo **pCOTypeInfo,
TYPEATTR **pCOTypeAttr)
{
HRESULT hr = E_NOINTERFACE;
ITypeLib *pTypeLib;
int count;
BOOL found = FALSE;
ITypeInfo *pTypeInfo2;
TYPEATTR *pTypeAttr2;
int flags;
int i,j;
HREFTYPE href;
ITypeInfo *pRefTypeInfo;
TYPEATTR *pRefTypeAttr;
hr = pTypeInfo->lpVtbl->GetContainingTypeLib(pTypeInfo, &pTypeLib, NULL);
if (FAILED(hr)) {
return hr;
}
count = pTypeLib->lpVtbl->GetTypeInfoCount(pTypeLib);
for (i = 0; i < count && !found; i++) {
hr = pTypeLib->lpVtbl->GetTypeInfo(pTypeLib, i, &pTypeInfo2);
if (FAILED(hr))
continue;
hr = OLE_GET_TYPEATTR(pTypeInfo2, &pTypeAttr2);
if (FAILED(hr)) {
OLE_RELEASE(pTypeInfo2);
continue;
}
if (pTypeAttr2->typekind != TKIND_COCLASS) {
OLE_RELEASE_TYPEATTR(pTypeInfo2, pTypeAttr2);
OLE_RELEASE(pTypeInfo2);
continue;
}
for (j = 0; j < pTypeAttr2->cImplTypes && !found; j++) {
hr = pTypeInfo2->lpVtbl->GetImplTypeFlags(pTypeInfo2, j, &flags);
if (FAILED(hr))
continue;
if (!(flags & IMPLTYPEFLAG_FDEFAULT))
continue;
hr = pTypeInfo2->lpVtbl->GetRefTypeOfImplType(pTypeInfo2, j, &href);
if (FAILED(hr))
continue;
hr = pTypeInfo2->lpVtbl->GetRefTypeInfo(pTypeInfo2, href, &pRefTypeInfo);
if (FAILED(hr))
continue;
hr = OLE_GET_TYPEATTR(pRefTypeInfo, &pRefTypeAttr);
if (FAILED(hr)) {
OLE_RELEASE(pRefTypeInfo);
continue;
}
if (IsEqualGUID(&(pTypeAttr->guid), &(pRefTypeAttr->guid))) {
found = TRUE;
}
}
if (!found) {
OLE_RELEASE_TYPEATTR(pTypeInfo2, pTypeAttr2);
OLE_RELEASE(pTypeInfo2);
}
}
OLE_RELEASE(pTypeLib);
if (found) {
*pCOTypeInfo = pTypeInfo2;
*pCOTypeAttr = pTypeAttr2;
hr = S_OK;
} else {
hr = E_NOINTERFACE;
}
return hr;
}
static HRESULT
find_default_source_from_typeinfo(
ITypeInfo *pTypeInfo,
TYPEATTR *pTypeAttr,
ITypeInfo **ppTypeInfo)
{
int i = 0;
HRESULT hr = E_NOINTERFACE;
int flags;
HREFTYPE hRefType;
/* Enumerate all implemented types of the COCLASS */
for (i = 0; i < pTypeAttr->cImplTypes; i++) {
hr = pTypeInfo->lpVtbl->GetImplTypeFlags(pTypeInfo, i, &flags);
if (FAILED(hr))
continue;
/*
looking for the [default] [source]
we just hope that it is a dispinterface :-)
*/
if ((flags & IMPLTYPEFLAG_FDEFAULT) &&
(flags & IMPLTYPEFLAG_FSOURCE)) {
hr = pTypeInfo->lpVtbl->GetRefTypeOfImplType(pTypeInfo,
i, &hRefType);
if (FAILED(hr))
continue;
hr = pTypeInfo->lpVtbl->GetRefTypeInfo(pTypeInfo,
hRefType, ppTypeInfo);
if (SUCCEEDED(hr))
break;
}
}
return hr;
}
static HRESULT
find_default_source(VALUE ole, IID *piid, ITypeInfo **ppTypeInfo)
{
HRESULT hr;
IProvideClassInfo2 *pProvideClassInfo2;
IProvideClassInfo *pProvideClassInfo;
void *p;
IDispatch *pDispatch;
ITypeInfo *pTypeInfo;
ITypeInfo *pTypeInfo2 = NULL;
TYPEATTR *pTypeAttr;
TYPEATTR *pTypeAttr2 = NULL;
struct oledata *pole;
OLEData_Get_Struct(ole, pole);
pDispatch = pole->pDispatch;
hr = pDispatch->lpVtbl->QueryInterface(pDispatch,
&IID_IProvideClassInfo2,
&p);
if (SUCCEEDED(hr)) {
pProvideClassInfo2 = p;
hr = pProvideClassInfo2->lpVtbl->GetGUID(pProvideClassInfo2,
GUIDKIND_DEFAULT_SOURCE_DISP_IID,
piid);
OLE_RELEASE(pProvideClassInfo2);
if (SUCCEEDED(hr)) {
hr = find_iid(ole, NULL, piid, ppTypeInfo);
}
}
if (SUCCEEDED(hr)) {
return hr;
}
hr = pDispatch->lpVtbl->QueryInterface(pDispatch,
&IID_IProvideClassInfo,
&p);
if (SUCCEEDED(hr)) {
pProvideClassInfo = p;
hr = pProvideClassInfo->lpVtbl->GetClassInfo(pProvideClassInfo,
&pTypeInfo);
OLE_RELEASE(pProvideClassInfo);
}
if (FAILED(hr)) {
hr = pDispatch->lpVtbl->GetTypeInfo(pDispatch, 0, cWIN32OLE_lcid, &pTypeInfo );
}
if (FAILED(hr))
return hr;
hr = OLE_GET_TYPEATTR(pTypeInfo, &pTypeAttr);
if (FAILED(hr)) {
OLE_RELEASE(pTypeInfo);
return hr;
}
*ppTypeInfo = 0;
hr = find_default_source_from_typeinfo(pTypeInfo, pTypeAttr, ppTypeInfo);
if (!*ppTypeInfo) {
hr = find_coclass(pTypeInfo, pTypeAttr, &pTypeInfo2, &pTypeAttr2);
if (SUCCEEDED(hr)) {
hr = find_default_source_from_typeinfo(pTypeInfo2, pTypeAttr2, ppTypeInfo);
OLE_RELEASE_TYPEATTR(pTypeInfo2, pTypeAttr2);
OLE_RELEASE(pTypeInfo2);
}
}
OLE_RELEASE_TYPEATTR(pTypeInfo, pTypeAttr);
OLE_RELEASE(pTypeInfo);
/* Now that would be a bad surprise, if we didn't find it, wouldn't it? */
if (!*ppTypeInfo) {
if (SUCCEEDED(hr))
hr = E_UNEXPECTED;
return hr;
}
/* Determine IID of default source interface */
hr = (*ppTypeInfo)->lpVtbl->GetTypeAttr(*ppTypeInfo, &pTypeAttr);
if (SUCCEEDED(hr)) {
*piid = pTypeAttr->guid;
(*ppTypeInfo)->lpVtbl->ReleaseTypeAttr(*ppTypeInfo, pTypeAttr);
}
else
OLE_RELEASE(*ppTypeInfo);
return hr;
}
static long
ole_search_event_at(VALUE ary, VALUE ev)
{
VALUE event;
VALUE event_name;
long i, len;
long ret = -1;
len = RARRAY_LEN(ary);
for(i = 0; i < len; i++) {
event = rb_ary_entry(ary, i);
event_name = rb_ary_entry(event, 1);
if(NIL_P(event_name) && NIL_P(ev)) {
ret = i;
break;
}
else if (RB_TYPE_P(ev, T_STRING) &&
RB_TYPE_P(event_name, T_STRING) &&
rb_str_cmp(ev, event_name) == 0) {
ret = i;
break;
}
}
return ret;
}
static VALUE
ole_search_event(VALUE ary, VALUE ev, BOOL *is_default)
{
VALUE event;
VALUE def_event;
VALUE event_name;
int i, len;
*is_default = FALSE;
def_event = Qnil;
len = RARRAY_LEN(ary);
for(i = 0; i < len; i++) {
event = rb_ary_entry(ary, i);
event_name = rb_ary_entry(event, 1);
if(NIL_P(event_name)) {
*is_default = TRUE;
def_event = event;
}
else if (rb_str_cmp(ev, event_name) == 0) {
*is_default = FALSE;
return event;
}
}
return def_event;
}
static VALUE
ole_search_handler_method(VALUE handler, VALUE ev, BOOL *is_default_handler)
{
VALUE mid;
*is_default_handler = FALSE;
mid = rb_to_id(rb_sprintf("on%"PRIsVALUE, ev));
if (rb_respond_to(handler, mid)) {
return mid;
}
mid = rb_intern("method_missing");
if (rb_respond_to(handler, mid)) {
*is_default_handler = TRUE;
return mid;
}
return Qnil;
}
static void
ole_delete_event(VALUE ary, VALUE ev)
{
long at = -1;
at = ole_search_event_at(ary, ev);
if (at >= 0) {
rb_ary_delete_at(ary, at);
}
}
static void
ole_event_free(struct oleeventdata *poleev)
{
if (poleev->pConnectionPoint) {
poleev->pConnectionPoint->lpVtbl->Unadvise(poleev->pConnectionPoint, poleev->dwCookie);
OLE_RELEASE(poleev->pConnectionPoint);
poleev->pConnectionPoint = NULL;
}
OLE_RELEASE(poleev->pDispatch);
free(poleev);
}
static VALUE
fev_s_allocate(VALUE klass)
{
VALUE obj;
struct oleeventdata *poleev;
obj = Data_Make_Struct(klass,struct oleeventdata,0,ole_event_free,poleev);
poleev->dwCookie = 0;
poleev->pConnectionPoint = NULL;
poleev->event_id = 0;
poleev->pDispatch = NULL;
return obj;
}
static VALUE
ev_advise(int argc, VALUE *argv, VALUE self)
{
VALUE ole, itf;
struct oledata *pole;
char *pitf;
HRESULT hr;
IID iid;
ITypeInfo *pTypeInfo = 0;
IDispatch *pDispatch;
IConnectionPointContainer *pContainer;
IConnectionPoint *pConnectionPoint;
IEVENTSINKOBJ *pIEV;
DWORD dwCookie;
struct oleeventdata *poleev;
void *p;
rb_scan_args(argc, argv, "11", &ole, &itf);
if (!rb_obj_is_kind_of(ole, cWIN32OLE)) {
rb_raise(rb_eTypeError, "1st parameter must be WIN32OLE object");
}
if(!RB_TYPE_P(itf, T_NIL)) {
pitf = StringValuePtr(itf);
if (rb_safe_level() > 0 && OBJ_TAINTED(itf)) {
rb_raise(rb_eSecurityError, "insecure event creation - `%s'",
StringValuePtr(itf));
}
hr = find_iid(ole, pitf, &iid, &pTypeInfo);
}
else {
hr = find_default_source(ole, &iid, &pTypeInfo);
}
if (FAILED(hr)) {
ole_raise(hr, rb_eRuntimeError, "interface not found");
}
OLEData_Get_Struct(ole, pole);
pDispatch = pole->pDispatch;
hr = pDispatch->lpVtbl->QueryInterface(pDispatch,
&IID_IConnectionPointContainer,
&p);
if (FAILED(hr)) {
OLE_RELEASE(pTypeInfo);
ole_raise(hr, rb_eRuntimeError,
"failed to query IConnectionPointContainer");
}
pContainer = p;
hr = pContainer->lpVtbl->FindConnectionPoint(pContainer,
&iid,
&pConnectionPoint);
OLE_RELEASE(pContainer);
if (FAILED(hr)) {
OLE_RELEASE(pTypeInfo);
ole_raise(hr, rb_eRuntimeError, "failed to query IConnectionPoint");
}
pIEV = EVENTSINK_Constructor();
pIEV->m_iid = iid;
hr = pConnectionPoint->lpVtbl->Advise(pConnectionPoint,
(IUnknown*)pIEV,
&dwCookie);
if (FAILED(hr)) {
ole_raise(hr, rb_eRuntimeError, "Advise Error");
}
Data_Get_Struct(self, struct oleeventdata, poleev);
pIEV->m_event_id = evs_length();
pIEV->pTypeInfo = pTypeInfo;
poleev->dwCookie = dwCookie;
poleev->pConnectionPoint = pConnectionPoint;
poleev->event_id = pIEV->m_event_id;
poleev->pDispatch = pDispatch;
OLE_ADDREF(pDispatch);
return self;
}
/*
* call-seq:
* WIN32OLE_EVENT.new(ole, event) #=> WIN32OLE_EVENT object.
*
* Returns OLE event object.
* The first argument specifies WIN32OLE object.
* The second argument specifies OLE event name.
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie, 'DWebBrowserEvents')
*/
static VALUE
fev_initialize(int argc, VALUE *argv, VALUE self)
{
ev_advise(argc, argv, self);
evs_push(self);
rb_ivar_set(self, id_events, rb_ary_new());
fev_set_handler(self, Qnil);
return self;
}
static void
ole_msg_loop() {
MSG msg;
while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/*
* call-seq:
* WIN32OLE_EVENT.message_loop
*
* Translates and dispatches Windows message.
*/
static VALUE
fev_s_msg_loop(VALUE klass)
{
ole_msg_loop();
return Qnil;
}
static void
add_event_call_back(VALUE obj, VALUE event, VALUE data)
{
VALUE events = rb_ivar_get(obj, id_events);
if (NIL_P(events) || !RB_TYPE_P(events, T_ARRAY)) {
events = rb_ary_new();
rb_ivar_set(obj, id_events, events);
}
ole_delete_event(events, event);
rb_ary_push(events, data);
}
static VALUE
ev_on_event(int argc, VALUE *argv, VALUE self, VALUE is_ary_arg)
{
struct oleeventdata *poleev;
VALUE event, args, data;
Data_Get_Struct(self, struct oleeventdata, poleev);
if (poleev->pConnectionPoint == NULL) {
rb_raise(eWIN32OLERuntimeError, "IConnectionPoint not found. You must call advise at first.");
}
rb_scan_args(argc, argv, "01*", &event, &args);
if(!NIL_P(event)) {
if(!RB_TYPE_P(event, T_STRING) && !RB_TYPE_P(event, T_SYMBOL)) {
rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)");
}
if (RB_TYPE_P(event, T_SYMBOL)) {
event = rb_sym_to_s(event);
}
}
data = rb_ary_new3(4, rb_block_proc(), event, args, is_ary_arg);
add_event_call_back(self, event, data);
return Qnil;
}
/*
* call-seq:
* WIN32OLE_EVENT#on_event([event]){...}
*
* Defines the callback event.
* If argument is omitted, this method defines the callback of all events.
* If you want to modify reference argument in callback, return hash in
* callback. If you want to return value to OLE server as result of callback
* use `return' or :return.
*
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie)
* ev.on_event("NavigateComplete") {|url| puts url}
* ev.on_event() {|ev, *args| puts "#{ev} fired"}
*
* ev.on_event("BeforeNavigate2") {|*args|
* ...
* # set true to BeforeNavigate reference argument `Cancel'.
* # Cancel is 7-th argument of BeforeNavigate,
* # so you can use 6 as key of hash instead of 'Cancel'.
* # The argument is counted from 0.
* # The hash key of 0 means first argument.)
* {:Cancel => true} # or {'Cancel' => true} or {6 => true}
* }
*
* ev.on_event(...) {|*args|
* {:return => 1, :xxx => yyy}
* }
*/
static VALUE
fev_on_event(int argc, VALUE *argv, VALUE self)
{
return ev_on_event(argc, argv, self, Qfalse);
}
/*
* call-seq:
* WIN32OLE_EVENT#on_event_with_outargs([event]){...}
*
* Defines the callback of event.
* If you want modify argument in callback,
* you could use this method instead of WIN32OLE_EVENT#on_event.
*
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie)
* ev.on_event_with_outargs('BeforeNavigate2') {|*args|
* args.last[6] = true
* }
*/
static VALUE
fev_on_event_with_outargs(int argc, VALUE *argv, VALUE self)
{
return ev_on_event(argc, argv, self, Qtrue);
}
/*
* call-seq:
* WIN32OLE_EVENT#off_event([event])
*
* removes the callback of event.
*
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie)
* ev.on_event('BeforeNavigate2') {|*args|
* args.last[6] = true
* }
* ...
* ev.off_event('BeforeNavigate2')
* ...
*/
static VALUE
fev_off_event(int argc, VALUE *argv, VALUE self)
{
VALUE event = Qnil;
VALUE events;
rb_scan_args(argc, argv, "01", &event);
if(!NIL_P(event)) {
if(!RB_TYPE_P(event, T_STRING) && !RB_TYPE_P(event, T_SYMBOL)) {
rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)");
}
if (RB_TYPE_P(event, T_SYMBOL)) {
event = rb_sym_to_s(event);
}
}
events = rb_ivar_get(self, id_events);
if (NIL_P(events)) {
return Qnil;
}
ole_delete_event(events, event);
return Qnil;
}
/*
* call-seq:
* WIN32OLE_EVENT#unadvise -> nil
*
* disconnects OLE server. If this method called, then the WIN32OLE_EVENT object
* does not receive the OLE server event any more.
* This method is trial implementation.
*
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie)
* ev.on_event() {...}
* ...
* ev.unadvise
*
*/
static VALUE
fev_unadvise(VALUE self)
{
struct oleeventdata *poleev;
Data_Get_Struct(self, struct oleeventdata, poleev);
if (poleev->pConnectionPoint) {
ole_msg_loop();
evs_delete(poleev->event_id);
poleev->pConnectionPoint->lpVtbl->Unadvise(poleev->pConnectionPoint, poleev->dwCookie);
OLE_RELEASE(poleev->pConnectionPoint);
poleev->pConnectionPoint = NULL;
}
OLE_FREE(poleev->pDispatch);
return Qnil;
}
static VALUE
evs_push(VALUE ev)
{
return rb_ary_push(ary_ole_event, ev);
}
static VALUE
evs_delete(long i)
{
rb_ary_store(ary_ole_event, i, Qnil);
return Qnil;
}
static VALUE
evs_entry(long i)
{
return rb_ary_entry(ary_ole_event, i);
}
static long
evs_length(void)
{
return RARRAY_LEN(ary_ole_event);
}
/*
* call-seq:
* WIN32OLE_EVENT#handler=
*
* sets event handler object. If handler object has onXXX
* method according to XXX event, then onXXX method is called
* when XXX event occurs.
*
* If handler object has method_missing and there is no
* method according to the event, then method_missing
* called and 1-st argument is event name.
*
* If handler object has onXXX method and there is block
* defined by WIN32OLE_EVENT#on_event('XXX'){},
* then block is executed but handler object method is not called
* when XXX event occurs.
*
* class Handler
* def onStatusTextChange(text)
* puts "StatusTextChanged"
* end
* def onPropertyChange(prop)
* puts "PropertyChanged"
* end
* def method_missing(ev, *arg)
* puts "other event #{ev}"
* end
* end
*
* handler = Handler.new
* ie = WIN32OLE.new('InternetExplorer.Application')
* ev = WIN32OLE_EVENT.new(ie)
* ev.on_event("StatusTextChange") {|*args|
* puts "this block executed."
* puts "handler.onStatusTextChange method is not called."
* }
* ev.handler = handler
*
*/
static VALUE
fev_set_handler(VALUE self, VALUE val)
{
return rb_ivar_set(self, rb_intern("handler"), val);
}
/*
* call-seq:
* WIN32OLE_EVENT#handler
*
* returns handler object.
*
*/
static VALUE
fev_get_handler(VALUE self)
{
return rb_ivar_get(self, rb_intern("handler"));
}
void
Init_win32ole_event()
{
ary_ole_event = rb_ary_new();
rb_gc_register_mark_object(ary_ole_event);
id_events = rb_intern("events");
cWIN32OLE_EVENT = rb_define_class("WIN32OLE_EVENT", rb_cObject);
rb_define_singleton_method(cWIN32OLE_EVENT, "message_loop", fev_s_msg_loop, 0);
rb_define_alloc_func(cWIN32OLE_EVENT, fev_s_allocate);
rb_define_method(cWIN32OLE_EVENT, "initialize", fev_initialize, -1);
rb_define_method(cWIN32OLE_EVENT, "on_event", fev_on_event, -1);
rb_define_method(cWIN32OLE_EVENT, "on_event_with_outargs", fev_on_event_with_outargs, -1);
rb_define_method(cWIN32OLE_EVENT, "off_event", fev_off_event, -1);
rb_define_method(cWIN32OLE_EVENT, "unadvise", fev_unadvise, 0);
rb_define_method(cWIN32OLE_EVENT, "handler=", fev_set_handler, 1);
rb_define_method(cWIN32OLE_EVENT, "handler", fev_get_handler, 0);
}