From 96b27e5a977eab8818769a533a2c1c225b07efea Mon Sep 17 00:00:00 2001 From: suke Date: Wed, 13 Aug 2014 15:28:26 +0000 Subject: * ext/win32ole/win32ole.c: seperate WIN32OLE_RECORD src from win32ole.c * ext/win32ole/win32ole.h: ditto. * ext/win32ole/win32ole_record.c: ditto. * ext/win32ole/win32ole_record.h: ditto. * ext/win32ole/depend: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@47174 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/win32ole/depend | 1 + ext/win32ole/win32ole.c | 575 +--------------------------------------- ext/win32ole/win32ole.h | 3 +- ext/win32ole/win32ole_record.c | 577 +++++++++++++++++++++++++++++++++++++++++ ext/win32ole/win32ole_record.h | 10 + 5 files changed, 593 insertions(+), 573 deletions(-) create mode 100644 ext/win32ole/win32ole_record.c create mode 100644 ext/win32ole/win32ole_record.h (limited to 'ext') diff --git a/ext/win32ole/depend b/ext/win32ole/depend index 940d9a5597..d46250c886 100644 --- a/ext/win32ole/depend +++ b/ext/win32ole/depend @@ -7,4 +7,5 @@ win32ole_variable.o : win32ole_variable.c $(WIN32OLE_HEADERS) win32ole_method.o : win32ole_method.c $(WIN32OLE_HEADERS) win32ole_param.o : win32ole_param.c $(WIN32OLE_HEADERS) win32ole_variant.o : win32ole_variant.c $(WIN32OLE_HEADERS) +win32ole_record.o : win32ole_record.c $(WIN32OLE_HEADERS) win32ole_error.o : win32ole_error.c $(WIN32OLE_HEADERS) diff --git a/ext/win32ole/win32ole.c b/ext/win32ole/win32ole.c index 9008155222..24cc184fcb 100644 --- a/ext/win32ole/win32ole.c +++ b/ext/win32ole/win32ole.c @@ -33,7 +33,6 @@ const IID IID_IMultiLanguage2 = {0xDCCFC164, 0x2B38, 0x11d2, {0xB7, 0xEC, 0x00, }\ } - #define WIN32OLE_VERSION "1.7.7" typedef HRESULT (STDAPICALLTYPE FNCOCREATEINSTANCEEX) @@ -93,7 +92,6 @@ typedef struct tagIEVENTSINKOBJ { VALUE cWIN32OLE; VALUE cWIN32OLE_EVENT; -VALUE cWIN32OLE_RECORD; static VALUE ary_ole_event; static ID id_events; @@ -139,7 +137,6 @@ struct oledata { IDispatch *pDispatch; }; - struct oleeventdata { DWORD dwCookie; IConnectionPoint *pConnectionPoint; @@ -151,10 +148,6 @@ struct oleparam { OLECHAR** pNamedArgs; }; -struct olerecorddata { - IRecordInfo *pri; - void *pdata; -}; static HRESULT ( STDMETHODCALLTYPE QueryInterface )(IDispatch __RPC_FAR *, REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject); static ULONG ( STDMETHODCALLTYPE AddRef )(IDispatch __RPC_FAR * This); @@ -185,8 +178,6 @@ static void * get_ptr_of_variant(VARIANT *pvar); static void ole_set_safe_array(long n, SAFEARRAY *psa, LONG *pid, long *pub, VALUE val, long dim, VARTYPE vt); static long dimension(VALUE val); static long ary_len_of_dim(VALUE ary, long dim); -static int hash2olerec(VALUE key, VALUE val, VALUE rec); -static void ole_rec2variant(VALUE rec, VARIANT *var); static void ole_val2ptr_variant(VALUE val, VARIANT *var); static VALUE ole_set_member(VALUE self, IDispatch *dispatch); static VALUE fole_s_allocate(VALUE klass); @@ -280,20 +271,6 @@ static VALUE evs_delete(long i); static VALUE evs_entry(long i); static VALUE evs_length(void); -static HRESULT typelib_from_val(VALUE obj, ITypeLib **pTypeLib); -static HRESULT recordinfo_from_itypelib(ITypeLib *pTypeLib, VALUE name, IRecordInfo **ppri); -static void olerecord_set_ivar(VALUE obj, IRecordInfo *pri, void *prec); -static void olerecord_free(struct olerecorddata *pvar); -static VALUE folerecord_s_allocate(VALUE klass); -static VALUE folerecord_initialize(VALUE self, VALUE typename, VALUE oleobj); -static VALUE folerecord_to_h(VALUE self); -static VALUE folerecord_typename(VALUE self); -static VALUE olerecord_ivar_get(VALUE self, VALUE name); -static VALUE olerecord_ivar_set(VALUE self, VALUE name, VALUE val); -static VALUE folerecord_method_missing(int argc, VALUE *argv, VALUE self); -static VALUE folerecord_ole_instance_variable_get(VALUE self, VALUE name); -static VALUE folerecord_ole_instance_variable_set(VALUE self, VALUE name, VALUE val); -static VALUE folerecord_inspect(VALUE self); static void init_enc2cp(void); static void free_enc2cp(void); @@ -1319,67 +1296,6 @@ ole_val_ary2variant_ary(VALUE val, VARIANT *var, VARTYPE vt) return hr; } -static int -hash2olerec(VALUE key, VALUE val, VALUE rec) -{ - VARIANT var; - OLECHAR *pbuf; - struct olerecorddata *prec; - IRecordInfo *pri; - HRESULT hr; - - if (val != Qnil) { - Data_Get_Struct(rec, struct olerecorddata, prec); - pri = prec->pri; - VariantInit(&var); - ole_val2variant(val, &var); - pbuf = ole_vstr2wc(key); - hr = pri->lpVtbl->PutField(pri, INVOKE_PROPERTYPUT, prec->pdata, pbuf, &var); - SysFreeString(pbuf); - VariantClear(&var); - if (FAILED(hr)) { - ole_raise(hr, eWIN32OLERuntimeError, "failed to putfield of `%s`", StringValuePtr(key)); - } - } - return ST_CONTINUE; -} - -static void -ole_rec2variant(VALUE rec, VARIANT *var) -{ - struct olerecorddata *prec; - ULONG size = 0; - IRecordInfo *pri; - HRESULT hr; - VALUE fields; - Data_Get_Struct(rec, struct olerecorddata, prec); - pri = prec->pri; - if (pri) { - hr = pri->lpVtbl->GetSize(pri, &size); - if (FAILED(hr)) { - ole_raise(hr, eWIN32OLERuntimeError, "failed to get size for allocation of VT_RECORD object"); - } - if (prec->pdata) { - free(prec->pdata); - } - prec->pdata = ALLOC_N(char, size); - if (!prec->pdata) { - rb_raise(rb_eRuntimeError, "failed to memory allocation of %lu bytes", (unsigned long)size); - } - hr = pri->lpVtbl->RecordInit(pri, prec->pdata); - if (FAILED(hr)) { - ole_raise(hr, eWIN32OLERuntimeError, "failed to initialize VT_RECORD object"); - } - fields = folerecord_to_h(rec); - rb_hash_foreach(fields, hash2olerec, rec); - V_RECORDINFO(var) = pri; - V_RECORD(var) = prec->pdata; - V_VT(var) = VT_RECORD; - } else { - rb_raise(eWIN32OLERuntimeError, "failed to retrieve IRecordInfo interface"); - } -} - void ole_val2variant(VALUE val, VARIANT *var) { @@ -1864,8 +1780,7 @@ ole_variant2val(VARIANT *pvar) { IRecordInfo *pri = V_RECORDINFO(pvar); void *prec = V_RECORD(pvar); - obj = folerecord_s_allocate(cWIN32OLE_RECORD); - olerecord_set_ivar(obj, pri, prec); + obj = create_win32ole_record(pri, prec); break; } @@ -5118,7 +5033,7 @@ fev_get_handler(VALUE self) return rb_ivar_get(self, rb_intern("handler")); } -static HRESULT +HRESULT typelib_from_val(VALUE obj, ITypeLib **pTypeLib) { LCID lcid = cWIN32OLE_lcid; @@ -5137,480 +5052,6 @@ typelib_from_val(VALUE obj, ITypeLib **pTypeLib) return hr; } -static HRESULT -recordinfo_from_itypelib(ITypeLib *pTypeLib, VALUE name, IRecordInfo **ppri) -{ - - unsigned int count; - unsigned int i; - ITypeInfo *pTypeInfo; - HRESULT hr = OLE_E_LAST; - BSTR bstr; - - count = pTypeLib->lpVtbl->GetTypeInfoCount(pTypeLib); - for (i = 0; i < count; i++) { - hr = pTypeLib->lpVtbl->GetDocumentation(pTypeLib, i, - &bstr, NULL, NULL, NULL); - if (FAILED(hr)) - continue; - - hr = pTypeLib->lpVtbl->GetTypeInfo(pTypeLib, i, &pTypeInfo); - if (FAILED(hr)) - continue; - - if (rb_str_cmp(WC2VSTR(bstr), name) == 0) { - hr = GetRecordInfoFromTypeInfo(pTypeInfo, ppri); - OLE_RELEASE(pTypeInfo); - return hr; - } - OLE_RELEASE(pTypeInfo); - } - hr = OLE_E_LAST; - return hr; -} - -static void -olerecord_set_ivar(VALUE obj, IRecordInfo *pri, void *prec) -{ - HRESULT hr; - BSTR bstr; - BSTR *bstrs; - ULONG count = 0; - ULONG i; - VALUE fields; - VALUE val; - VARIANT var; - void *pdata = NULL; - struct olerecorddata *pvar; - - Data_Get_Struct(obj, struct olerecorddata, pvar); - OLE_ADDREF(pri); - OLE_RELEASE(pvar->pri); - pvar->pri = pri; - - hr = pri->lpVtbl->GetName(pri, &bstr); - if (SUCCEEDED(hr)) { - rb_ivar_set(obj, rb_intern("typename"), WC2VSTR(bstr)); - } - - hr = pri->lpVtbl->GetFieldNames(pri, &count, NULL); - if (FAILED(hr) || count == 0) - return; - bstrs = ALLOCA_N(BSTR, count); - hr = pri->lpVtbl->GetFieldNames(pri, &count, bstrs); - if (FAILED(hr)) { - return; - } - - fields = rb_hash_new(); - rb_ivar_set(obj, rb_intern("fields"), fields); - for (i = 0; i < count; i++) { - pdata = NULL; - VariantInit(&var); - val = Qnil; - if (prec) { - hr = pri->lpVtbl->GetFieldNoCopy(pri, prec, bstrs[i], &var, &pdata); - if (SUCCEEDED(hr)) { - val = ole_variant2val(&var); - } - } - rb_hash_aset(fields, WC2VSTR(bstrs[i]), val); - } -} - -/* - * Document-class: WIN32OLE_RECORD - * - * WIN32OLE_RECORD objects represents VT_RECORD OLE variant. - * Win32OLE returns WIN32OLE_RECORD object if the result value of invoking - * OLE methods. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure Book - * _ - * Public title As String - * Public cost As Integer - * End Structure - * Public Function getBook() As Book - * Dim book As New Book - * book.title = "The Ruby Book" - * book.cost = 20 - * Return book - * End Function - * End Class - * - * then, you can retrieve getBook return value from the following - * Ruby script: - * - * require 'win32ole' - * obj = WIN32OLE.new('ComServer.ComClass') - * book = obj.getBook - * book.class # => WIN32OLE_RECORD - * book.title # => "The Ruby Book" - * book.cost # => 20 - * - */ - -static void -olerecord_free(struct olerecorddata *pvar) { - OLE_FREE(pvar->pri); - if (pvar->pdata) { - free(pvar->pdata); - } - free(pvar); -} - -static VALUE -folerecord_s_allocate(VALUE klass) { - VALUE obj = Qnil; - struct olerecorddata *pvar; - obj = Data_Make_Struct(klass, struct olerecorddata, 0, olerecord_free, pvar); - pvar->pri = NULL; - pvar->pdata = NULL; - return obj; -} - -/* - * call-seq: - * WIN32OLE_RECORD.new(typename, obj) -> WIN32OLE_RECORD object - * - * Returns WIN32OLE_RECORD object. The first argument is struct name (String - * or Symbol). - * The second parameter obj should be WIN32OLE object or WIN32OLE_TYPELIB object. - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure Book - * _ - * Public title As String - * Public cost As Integer - * End Structure - * End Class - * - * then, you can create WIN32OLE_RECORD object is as following: - * - * require 'win32ole' - * obj = WIN32OLE.new('ComServer.ComClass') - * book1 = WIN32OLE_RECORD.new('Book', obj) # => WIN32OLE_RECORD object - * tlib = obj.ole_typelib - * book2 = WIN32OLE_RECORD.new('Book', tlib) # => WIN32OLE_RECORD object - * - */ -static VALUE -folerecord_initialize(VALUE self, VALUE typename, VALUE oleobj) { - HRESULT hr; - ITypeLib *pTypeLib = NULL; - IRecordInfo *pri = NULL; - - if (!RB_TYPE_P(typename, T_STRING) && !RB_TYPE_P(typename, T_SYMBOL)) { - rb_raise(rb_eArgError, "1st argument should be String or Symbol"); - } - if (RB_TYPE_P(typename, T_SYMBOL)) { - typename = rb_sym_to_s(typename); - } - - hr = S_OK; - if(rb_obj_is_kind_of(oleobj, cWIN32OLE)) { - hr = typelib_from_val(oleobj, &pTypeLib); - } else if (rb_obj_is_kind_of(oleobj, cWIN32OLE_TYPELIB)) { - pTypeLib = itypelib(oleobj); - OLE_ADDREF(pTypeLib); - if (pTypeLib) { - hr = S_OK; - } else { - hr = E_FAIL; - } - } else { - rb_raise(rb_eArgError, "2nd argument should be WIN32OLE object or WIN32OLE_TYPELIB object"); - } - - if (FAILED(hr)) { - ole_raise(hr, eWIN32OLERuntimeError, "fail to query ITypeLib interface"); - } - - hr = recordinfo_from_itypelib(pTypeLib, typename, &pri); - OLE_RELEASE(pTypeLib); - if (FAILED(hr)) { - ole_raise(hr, eWIN32OLERuntimeError, "fail to query IRecordInfo interface for `%s'", StringValuePtr(typename)); - } - - olerecord_set_ivar(self, pri, NULL); - - return self; -} - -/* - * call-seq: - * WIN32OLE_RECORD#to_h #=> Ruby Hash object. - * - * Returns Ruby Hash object which represents VT_RECORD variable. - * The keys of Hash object are member names of VT_RECORD OLE variable and - * the values of Hash object are values of VT_RECORD OLE variable. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure Book - * _ - * Public title As String - * Public cost As Integer - * End Structure - * Public Function getBook() As Book - * Dim book As New Book - * book.title = "The Ruby Book" - * book.cost = 20 - * Return book - * End Function - * End Class - * - * then, the result of WIN32OLE_RECORD#to_h is the following: - * - * require 'win32ole' - * obj = WIN32OLE.new('ComServer.ComClass') - * book = obj.getBook - * book.to_h # => {"title"=>"The Ruby Book", "cost"=>20} - * - */ -static VALUE -folerecord_to_h(VALUE self) -{ - return rb_ivar_get(self, rb_intern("fields")); -} - -/* - * call-seq: - * WIN32OLE_RECORD#typename #=> String object - * - * Returns the type name of VT_RECORD OLE variable. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure Book - * _ - * Public title As String - * Public cost As Integer - * End Structure - * Public Function getBook() As Book - * Dim book As New Book - * book.title = "The Ruby Book" - * book.cost = 20 - * Return book - * End Function - * End Class - * - * then, the result of WIN32OLE_RECORD#typename is the following: - * - * require 'win32ole' - * obj = WIN32OLE.new('ComServer.ComClass') - * book = obj.getBook - * book.typename # => "Book" - * - */ -static VALUE -folerecord_typename(VALUE self) -{ - return rb_ivar_get(self, rb_intern("typename")); -} - -static VALUE -olerecord_ivar_get(VALUE self, VALUE name) -{ - VALUE fields; - fields = rb_ivar_get(self, rb_intern("fields")); - return rb_hash_fetch(fields, name); -} - -static VALUE -olerecord_ivar_set(VALUE self, VALUE name, VALUE val) -{ - long len; - char *p; - VALUE fields; - len = RSTRING_LEN(name); - p = RSTRING_PTR(name); - if (p[len-1] == '=') { - name = rb_str_subseq(name, 0, len-1); - } - fields = rb_ivar_get(self, rb_intern("fields")); - rb_hash_fetch(fields, name); - return rb_hash_aset(fields, name, val); -} - -/* - * call-seq: - * WIN32OLE_RECORD#method_missing(name) - * - * Returns value specified by the member name of VT_RECORD OLE variable. - * Or sets value specified by the member name of VT_RECORD OLE variable. - * If the member name is not correct, KeyError exception is raised. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure Book - * _ - * Public title As String - * Public cost As Integer - * End Structure - * End Class - * - * Then getting/setting value from Ruby is as the following: - * - * obj = WIN32OLE.new('ComServer.ComClass') - * book = WIN32OLE_RECORD.new('Book', obj) - * book.title # => nil ( book.method_missing(:title) is invoked. ) - * book.title = "Ruby" # ( book.method_missing(:title=, "Ruby") is invoked. ) - */ -static VALUE -folerecord_method_missing(int argc, VALUE *argv, VALUE self) -{ - VALUE name; - rb_check_arity(argc, 1, 2); - name = rb_sym_to_s(argv[0]); - -#if SIZEOF_SIZE_T > SIZEOF_LONG - { - size_t n = strlen(StringValueCStr(name)); - if (n >= LONG_MAX) { - rb_raise(rb_eRuntimeError, "too long member name"); - } - } -#endif - - if (argc == 1) { - return olerecord_ivar_get(self, name); - } else if (argc == 2) { - return olerecord_ivar_set(self, name, argv[1]); - } - return Qnil; -} - -/* - * call-seq: - * WIN32OLE_RECORD#ole_instance_variable_get(name) - * - * Returns value specified by the member name of VT_RECORD OLE object. - * If the member name is not correct, KeyError exception is raised. - * If you can't access member variable of VT_RECORD OLE object directly, - * use this method. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * Public Structure ComObject - * Public object_id As Ineger - * End Structure - * End Class - * - * and Ruby Object class has title attribute: - * - * then accessing object_id of ComObject from Ruby is as the following: - * - * srver = WIN32OLE.new('ComServer.ComClass') - * obj = WIN32OLE_RECORD.new('ComObject', server) - * # obj.object_id returns Ruby Object#object_id - * obj.ole_instance_variable_get(:object_id) # => nil - * - */ -static VALUE -folerecord_ole_instance_variable_get(VALUE self, VALUE name) -{ - VALUE sname; - if(!RB_TYPE_P(name, T_STRING) && !RB_TYPE_P(name, T_SYMBOL)) { - rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)"); - } - sname = name; - if (RB_TYPE_P(name, T_SYMBOL)) { - sname = rb_sym_to_s(name); - } - return olerecord_ivar_get(self, sname); -} - -/* - * call-seq: - * WIN32OLE_RECORD#ole_instance_variable_set(name, val) - * - * Sets value specified by the member name of VT_RECORD OLE object. - * If the member name is not correct, KeyError exception is raised. - * If you can't set value of member of VT_RECORD OLE object directly, - * use this method. - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * _ - * Public title As String - * Public cost As Integer - * End Class - * - * then setting value of the `title' member is as following: - * - * srver = WIN32OLE.new('ComServer.ComClass') - * obj = WIN32OLE_RECORD.new('Book', server) - * obj.ole_instance_variable_set(:title, "The Ruby Book") - * - */ -static VALUE -folerecord_ole_instance_variable_set(VALUE self, VALUE name, VALUE val) -{ - VALUE sname; - if(!RB_TYPE_P(name, T_STRING) && !RB_TYPE_P(name, T_SYMBOL)) { - rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)"); - } - sname = name; - if (RB_TYPE_P(name, T_SYMBOL)) { - sname = rb_sym_to_s(name); - } - return olerecord_ivar_set(self, sname, val); -} - -/* - * call-seq: - * WIN32OLE_RECORD#inspect -> String - * - * Returns the OLE struct name and member name and the value of member - * - * If COM server in VB.NET ComServer project is the following: - * - * Imports System.Runtime.InteropServices - * Public Class ComClass - * _ - * Public title As String - * Public cost As Integer - * End Class - * - * then - * - * srver = WIN32OLE.new('ComServer.ComClass') - * obj = WIN32OLE_RECORD.new('Book', server) - * obj.inspect # => nil, "cost" => nil}> - * - */ -static VALUE -folerecord_inspect(VALUE self) -{ - VALUE tname; - VALUE field; - tname = folerecord_typename(self); - if (tname == Qnil) { - tname = rb_inspect(tname); - } - field = rb_inspect(folerecord_to_h(self)); - return rb_sprintf("#", - tname, - field); -} - static void init_enc2cp(void) { @@ -5801,17 +5242,7 @@ Init_win32ole(void) rb_define_method(cWIN32OLE_EVENT, "handler", fev_get_handler, 0); Init_win32ole_variant(); - - cWIN32OLE_RECORD = rb_define_class("WIN32OLE_RECORD", rb_cObject); - rb_define_alloc_func(cWIN32OLE_RECORD, folerecord_s_allocate); - rb_define_method(cWIN32OLE_RECORD, "initialize", folerecord_initialize, 2); - rb_define_method(cWIN32OLE_RECORD, "to_h", folerecord_to_h, 0); - rb_define_method(cWIN32OLE_RECORD, "typename", folerecord_typename, 0); - rb_define_method(cWIN32OLE_RECORD, "method_missing", folerecord_method_missing, -1); - rb_define_method(cWIN32OLE_RECORD, "ole_instance_variable_get", folerecord_ole_instance_variable_get, 1); - rb_define_method(cWIN32OLE_RECORD, "ole_instance_variable_set", folerecord_ole_instance_variable_set, 2); - rb_define_method(cWIN32OLE_RECORD, "inspect", folerecord_inspect, 0); - + Init_win32ole_record(); Init_win32ole_error(); init_enc2cp(); diff --git a/ext/win32ole/win32ole.h b/ext/win32ole/win32ole.h index 6be2d63795..672e3472f3 100644 --- a/ext/win32ole/win32ole.h +++ b/ext/win32ole/win32ole.h @@ -108,7 +108,6 @@ #define OLE_GET_TYPEATTR(X, Y) ((X)->lpVtbl->GetTypeAttr((X), (Y))) #define OLE_RELEASE_TYPEATTR(X, Y) ((X)->lpVtbl->ReleaseTypeAttr((X), (Y))) - VALUE cWIN32OLE; LCID cWIN32OLE_lcid; @@ -134,6 +133,7 @@ void ole_val2variant_ex(VALUE val, VARIANT *var, VARTYPE vt); VALUE ole_variant2val(VARIANT *pvar); HRESULT ole_val_ary2variant_ary(VALUE val, VARIANT *var, VARTYPE vt); VOID *val2variant_ptr(VALUE val, VARIANT *var, VARTYPE vt); +HRESULT typelib_from_val(VALUE obj, ITypeLib **pTypeLib); #include "win32ole_variant_m.h" #include "win32ole_typelib.h" @@ -142,6 +142,7 @@ VOID *val2variant_ptr(VALUE val, VARIANT *var, VARTYPE vt); #include "win32ole_method.h" #include "win32ole_param.h" #include "win32ole_variant.h" +#include "win32ole_record.h" #include "win32ole_error.h" #endif diff --git a/ext/win32ole/win32ole_record.c b/ext/win32ole/win32ole_record.c new file mode 100644 index 0000000000..b7ce75bbee --- /dev/null +++ b/ext/win32ole/win32ole_record.c @@ -0,0 +1,577 @@ +#include "win32ole.h" + +struct olerecorddata { + IRecordInfo *pri; + void *pdata; +}; + +static HRESULT recordinfo_from_itypelib(ITypeLib *pTypeLib, VALUE name, IRecordInfo **ppri); +static int hash2olerec(VALUE key, VALUE val, VALUE rec); +static void olerecord_free(struct olerecorddata *pvar); +static VALUE folerecord_s_allocate(VALUE klass); +static VALUE folerecord_initialize(VALUE self, VALUE typename, VALUE oleobj); +static VALUE folerecord_to_h(VALUE self); +static VALUE folerecord_typename(VALUE self); +static VALUE olerecord_ivar_get(VALUE self, VALUE name); +static VALUE olerecord_ivar_set(VALUE self, VALUE name, VALUE val); +static VALUE folerecord_method_missing(int argc, VALUE *argv, VALUE self); +static VALUE folerecord_ole_instance_variable_get(VALUE self, VALUE name); +static VALUE folerecord_ole_instance_variable_set(VALUE self, VALUE name, VALUE val); +static VALUE folerecord_inspect(VALUE self); + +static HRESULT +recordinfo_from_itypelib(ITypeLib *pTypeLib, VALUE name, IRecordInfo **ppri) +{ + + unsigned int count; + unsigned int i; + ITypeInfo *pTypeInfo; + HRESULT hr = OLE_E_LAST; + BSTR bstr; + + count = pTypeLib->lpVtbl->GetTypeInfoCount(pTypeLib); + for (i = 0; i < count; i++) { + hr = pTypeLib->lpVtbl->GetDocumentation(pTypeLib, i, + &bstr, NULL, NULL, NULL); + if (FAILED(hr)) + continue; + + hr = pTypeLib->lpVtbl->GetTypeInfo(pTypeLib, i, &pTypeInfo); + if (FAILED(hr)) + continue; + + if (rb_str_cmp(WC2VSTR(bstr), name) == 0) { + hr = GetRecordInfoFromTypeInfo(pTypeInfo, ppri); + OLE_RELEASE(pTypeInfo); + return hr; + } + OLE_RELEASE(pTypeInfo); + } + hr = OLE_E_LAST; + return hr; +} + +static int +hash2olerec(VALUE key, VALUE val, VALUE rec) +{ + VARIANT var; + OLECHAR *pbuf; + struct olerecorddata *prec; + IRecordInfo *pri; + HRESULT hr; + + if (val != Qnil) { + Data_Get_Struct(rec, struct olerecorddata, prec); + pri = prec->pri; + VariantInit(&var); + ole_val2variant(val, &var); + pbuf = ole_vstr2wc(key); + hr = pri->lpVtbl->PutField(pri, INVOKE_PROPERTYPUT, prec->pdata, pbuf, &var); + SysFreeString(pbuf); + VariantClear(&var); + if (FAILED(hr)) { + ole_raise(hr, eWIN32OLERuntimeError, "failed to putfield of `%s`", StringValuePtr(key)); + } + } + return ST_CONTINUE; +} + +void +ole_rec2variant(VALUE rec, VARIANT *var) +{ + struct olerecorddata *prec; + ULONG size = 0; + IRecordInfo *pri; + HRESULT hr; + VALUE fields; + Data_Get_Struct(rec, struct olerecorddata, prec); + pri = prec->pri; + if (pri) { + hr = pri->lpVtbl->GetSize(pri, &size); + if (FAILED(hr)) { + ole_raise(hr, eWIN32OLERuntimeError, "failed to get size for allocation of VT_RECORD object"); + } + if (prec->pdata) { + free(prec->pdata); + } + prec->pdata = ALLOC_N(char, size); + if (!prec->pdata) { + rb_raise(rb_eRuntimeError, "failed to memory allocation of %lu bytes", (unsigned long)size); + } + hr = pri->lpVtbl->RecordInit(pri, prec->pdata); + if (FAILED(hr)) { + ole_raise(hr, eWIN32OLERuntimeError, "failed to initialize VT_RECORD object"); + } + fields = folerecord_to_h(rec); + rb_hash_foreach(fields, hash2olerec, rec); + V_RECORDINFO(var) = pri; + V_RECORD(var) = prec->pdata; + V_VT(var) = VT_RECORD; + } else { + rb_raise(eWIN32OLERuntimeError, "failed to retrieve IRecordInfo interface"); + } +} + +void +olerecord_set_ivar(VALUE obj, IRecordInfo *pri, void *prec) +{ + HRESULT hr; + BSTR bstr; + BSTR *bstrs; + ULONG count = 0; + ULONG i; + VALUE fields; + VALUE val; + VARIANT var; + void *pdata = NULL; + struct olerecorddata *pvar; + + Data_Get_Struct(obj, struct olerecorddata, pvar); + OLE_ADDREF(pri); + OLE_RELEASE(pvar->pri); + pvar->pri = pri; + + hr = pri->lpVtbl->GetName(pri, &bstr); + if (SUCCEEDED(hr)) { + rb_ivar_set(obj, rb_intern("typename"), WC2VSTR(bstr)); + } + + hr = pri->lpVtbl->GetFieldNames(pri, &count, NULL); + if (FAILED(hr) || count == 0) + return; + bstrs = ALLOCA_N(BSTR, count); + hr = pri->lpVtbl->GetFieldNames(pri, &count, bstrs); + if (FAILED(hr)) { + return; + } + + fields = rb_hash_new(); + rb_ivar_set(obj, rb_intern("fields"), fields); + for (i = 0; i < count; i++) { + pdata = NULL; + VariantInit(&var); + val = Qnil; + if (prec) { + hr = pri->lpVtbl->GetFieldNoCopy(pri, prec, bstrs[i], &var, &pdata); + if (SUCCEEDED(hr)) { + val = ole_variant2val(&var); + } + } + rb_hash_aset(fields, WC2VSTR(bstrs[i]), val); + } +} + +VALUE +create_win32ole_record(IRecordInfo *pri, void *prec) +{ + VALUE obj = folerecord_s_allocate(cWIN32OLE_RECORD); + olerecord_set_ivar(obj, pri, prec); + return obj; +} + +/* + * Document-class: WIN32OLE_RECORD + * + * WIN32OLE_RECORD objects represents VT_RECORD OLE variant. + * Win32OLE returns WIN32OLE_RECORD object if the result value of invoking + * OLE methods. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure Book + * _ + * Public title As String + * Public cost As Integer + * End Structure + * Public Function getBook() As Book + * Dim book As New Book + * book.title = "The Ruby Book" + * book.cost = 20 + * Return book + * End Function + * End Class + * + * then, you can retrieve getBook return value from the following + * Ruby script: + * + * require 'win32ole' + * obj = WIN32OLE.new('ComServer.ComClass') + * book = obj.getBook + * book.class # => WIN32OLE_RECORD + * book.title # => "The Ruby Book" + * book.cost # => 20 + * + */ + +static void +olerecord_free(struct olerecorddata *pvar) { + OLE_FREE(pvar->pri); + if (pvar->pdata) { + free(pvar->pdata); + } + free(pvar); +} + +static VALUE +folerecord_s_allocate(VALUE klass) { + VALUE obj = Qnil; + struct olerecorddata *pvar; + obj = Data_Make_Struct(klass, struct olerecorddata, 0, olerecord_free, pvar); + pvar->pri = NULL; + pvar->pdata = NULL; + return obj; +} + +/* + * call-seq: + * WIN32OLE_RECORD.new(typename, obj) -> WIN32OLE_RECORD object + * + * Returns WIN32OLE_RECORD object. The first argument is struct name (String + * or Symbol). + * The second parameter obj should be WIN32OLE object or WIN32OLE_TYPELIB object. + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure Book + * _ + * Public title As String + * Public cost As Integer + * End Structure + * End Class + * + * then, you can create WIN32OLE_RECORD object is as following: + * + * require 'win32ole' + * obj = WIN32OLE.new('ComServer.ComClass') + * book1 = WIN32OLE_RECORD.new('Book', obj) # => WIN32OLE_RECORD object + * tlib = obj.ole_typelib + * book2 = WIN32OLE_RECORD.new('Book', tlib) # => WIN32OLE_RECORD object + * + */ +static VALUE +folerecord_initialize(VALUE self, VALUE typename, VALUE oleobj) { + HRESULT hr; + ITypeLib *pTypeLib = NULL; + IRecordInfo *pri = NULL; + + if (!RB_TYPE_P(typename, T_STRING) && !RB_TYPE_P(typename, T_SYMBOL)) { + rb_raise(rb_eArgError, "1st argument should be String or Symbol"); + } + if (RB_TYPE_P(typename, T_SYMBOL)) { + typename = rb_sym_to_s(typename); + } + + hr = S_OK; + if(rb_obj_is_kind_of(oleobj, cWIN32OLE)) { + hr = typelib_from_val(oleobj, &pTypeLib); + } else if (rb_obj_is_kind_of(oleobj, cWIN32OLE_TYPELIB)) { + pTypeLib = itypelib(oleobj); + OLE_ADDREF(pTypeLib); + if (pTypeLib) { + hr = S_OK; + } else { + hr = E_FAIL; + } + } else { + rb_raise(rb_eArgError, "2nd argument should be WIN32OLE object or WIN32OLE_TYPELIB object"); + } + + if (FAILED(hr)) { + ole_raise(hr, eWIN32OLERuntimeError, "fail to query ITypeLib interface"); + } + + hr = recordinfo_from_itypelib(pTypeLib, typename, &pri); + OLE_RELEASE(pTypeLib); + if (FAILED(hr)) { + ole_raise(hr, eWIN32OLERuntimeError, "fail to query IRecordInfo interface for `%s'", StringValuePtr(typename)); + } + + olerecord_set_ivar(self, pri, NULL); + + return self; +} + +/* + * call-seq: + * WIN32OLE_RECORD#to_h #=> Ruby Hash object. + * + * Returns Ruby Hash object which represents VT_RECORD variable. + * The keys of Hash object are member names of VT_RECORD OLE variable and + * the values of Hash object are values of VT_RECORD OLE variable. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure Book + * _ + * Public title As String + * Public cost As Integer + * End Structure + * Public Function getBook() As Book + * Dim book As New Book + * book.title = "The Ruby Book" + * book.cost = 20 + * Return book + * End Function + * End Class + * + * then, the result of WIN32OLE_RECORD#to_h is the following: + * + * require 'win32ole' + * obj = WIN32OLE.new('ComServer.ComClass') + * book = obj.getBook + * book.to_h # => {"title"=>"The Ruby Book", "cost"=>20} + * + */ +static VALUE +folerecord_to_h(VALUE self) +{ + return rb_ivar_get(self, rb_intern("fields")); +} + +/* + * call-seq: + * WIN32OLE_RECORD#typename #=> String object + * + * Returns the type name of VT_RECORD OLE variable. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure Book + * _ + * Public title As String + * Public cost As Integer + * End Structure + * Public Function getBook() As Book + * Dim book As New Book + * book.title = "The Ruby Book" + * book.cost = 20 + * Return book + * End Function + * End Class + * + * then, the result of WIN32OLE_RECORD#typename is the following: + * + * require 'win32ole' + * obj = WIN32OLE.new('ComServer.ComClass') + * book = obj.getBook + * book.typename # => "Book" + * + */ +static VALUE +folerecord_typename(VALUE self) +{ + return rb_ivar_get(self, rb_intern("typename")); +} + +static VALUE +olerecord_ivar_get(VALUE self, VALUE name) +{ + VALUE fields; + fields = rb_ivar_get(self, rb_intern("fields")); + return rb_hash_fetch(fields, name); +} + +static VALUE +olerecord_ivar_set(VALUE self, VALUE name, VALUE val) +{ + long len; + char *p; + VALUE fields; + len = RSTRING_LEN(name); + p = RSTRING_PTR(name); + if (p[len-1] == '=') { + name = rb_str_subseq(name, 0, len-1); + } + fields = rb_ivar_get(self, rb_intern("fields")); + rb_hash_fetch(fields, name); + return rb_hash_aset(fields, name, val); +} + +/* + * call-seq: + * WIN32OLE_RECORD#method_missing(name) + * + * Returns value specified by the member name of VT_RECORD OLE variable. + * Or sets value specified by the member name of VT_RECORD OLE variable. + * If the member name is not correct, KeyError exception is raised. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure Book + * _ + * Public title As String + * Public cost As Integer + * End Structure + * End Class + * + * Then getting/setting value from Ruby is as the following: + * + * obj = WIN32OLE.new('ComServer.ComClass') + * book = WIN32OLE_RECORD.new('Book', obj) + * book.title # => nil ( book.method_missing(:title) is invoked. ) + * book.title = "Ruby" # ( book.method_missing(:title=, "Ruby") is invoked. ) + */ +static VALUE +folerecord_method_missing(int argc, VALUE *argv, VALUE self) +{ + VALUE name; + rb_check_arity(argc, 1, 2); + name = rb_sym_to_s(argv[0]); + +#if SIZEOF_SIZE_T > SIZEOF_LONG + { + size_t n = strlen(StringValueCStr(name)); + if (n >= LONG_MAX) { + rb_raise(rb_eRuntimeError, "too long member name"); + } + } +#endif + + if (argc == 1) { + return olerecord_ivar_get(self, name); + } else if (argc == 2) { + return olerecord_ivar_set(self, name, argv[1]); + } + return Qnil; +} + +/* + * call-seq: + * WIN32OLE_RECORD#ole_instance_variable_get(name) + * + * Returns value specified by the member name of VT_RECORD OLE object. + * If the member name is not correct, KeyError exception is raised. + * If you can't access member variable of VT_RECORD OLE object directly, + * use this method. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * Public Structure ComObject + * Public object_id As Ineger + * End Structure + * End Class + * + * and Ruby Object class has title attribute: + * + * then accessing object_id of ComObject from Ruby is as the following: + * + * srver = WIN32OLE.new('ComServer.ComClass') + * obj = WIN32OLE_RECORD.new('ComObject', server) + * # obj.object_id returns Ruby Object#object_id + * obj.ole_instance_variable_get(:object_id) # => nil + * + */ +static VALUE +folerecord_ole_instance_variable_get(VALUE self, VALUE name) +{ + VALUE sname; + if(!RB_TYPE_P(name, T_STRING) && !RB_TYPE_P(name, T_SYMBOL)) { + rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)"); + } + sname = name; + if (RB_TYPE_P(name, T_SYMBOL)) { + sname = rb_sym_to_s(name); + } + return olerecord_ivar_get(self, sname); +} + +/* + * call-seq: + * WIN32OLE_RECORD#ole_instance_variable_set(name, val) + * + * Sets value specified by the member name of VT_RECORD OLE object. + * If the member name is not correct, KeyError exception is raised. + * If you can't set value of member of VT_RECORD OLE object directly, + * use this method. + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * _ + * Public title As String + * Public cost As Integer + * End Class + * + * then setting value of the `title' member is as following: + * + * srver = WIN32OLE.new('ComServer.ComClass') + * obj = WIN32OLE_RECORD.new('Book', server) + * obj.ole_instance_variable_set(:title, "The Ruby Book") + * + */ +static VALUE +folerecord_ole_instance_variable_set(VALUE self, VALUE name, VALUE val) +{ + VALUE sname; + if(!RB_TYPE_P(name, T_STRING) && !RB_TYPE_P(name, T_SYMBOL)) { + rb_raise(rb_eTypeError, "wrong argument type (expected String or Symbol)"); + } + sname = name; + if (RB_TYPE_P(name, T_SYMBOL)) { + sname = rb_sym_to_s(name); + } + return olerecord_ivar_set(self, sname, val); +} + +/* + * call-seq: + * WIN32OLE_RECORD#inspect -> String + * + * Returns the OLE struct name and member name and the value of member + * + * If COM server in VB.NET ComServer project is the following: + * + * Imports System.Runtime.InteropServices + * Public Class ComClass + * _ + * Public title As String + * Public cost As Integer + * End Class + * + * then + * + * srver = WIN32OLE.new('ComServer.ComClass') + * obj = WIN32OLE_RECORD.new('Book', server) + * obj.inspect # => nil, "cost" => nil}> + * + */ +static VALUE +folerecord_inspect(VALUE self) +{ + VALUE tname; + VALUE field; + tname = folerecord_typename(self); + if (tname == Qnil) { + tname = rb_inspect(tname); + } + field = rb_inspect(folerecord_to_h(self)); + return rb_sprintf("#", + tname, + field); +} + +void +Init_win32ole_record() +{ + cWIN32OLE_RECORD = rb_define_class("WIN32OLE_RECORD", rb_cObject); + rb_define_alloc_func(cWIN32OLE_RECORD, folerecord_s_allocate); + rb_define_method(cWIN32OLE_RECORD, "initialize", folerecord_initialize, 2); + rb_define_method(cWIN32OLE_RECORD, "to_h", folerecord_to_h, 0); + rb_define_method(cWIN32OLE_RECORD, "typename", folerecord_typename, 0); + rb_define_method(cWIN32OLE_RECORD, "method_missing", folerecord_method_missing, -1); + rb_define_method(cWIN32OLE_RECORD, "ole_instance_variable_get", folerecord_ole_instance_variable_get, 1); + rb_define_method(cWIN32OLE_RECORD, "ole_instance_variable_set", folerecord_ole_instance_variable_set, 2); + rb_define_method(cWIN32OLE_RECORD, "inspect", folerecord_inspect, 0); +} diff --git a/ext/win32ole/win32ole_record.h b/ext/win32ole/win32ole_record.h new file mode 100644 index 0000000000..2f84d104cc --- /dev/null +++ b/ext/win32ole/win32ole_record.h @@ -0,0 +1,10 @@ +#ifndef WIN32OLE_RECORD_H +#define WIN32OLE_RECORD_H 1 + +VALUE cWIN32OLE_RECORD; +void ole_rec2variant(VALUE rec, VARIANT *var); +void olerecord_set_ivar(VALUE obj, IRecordInfo *pri, void *prec); +VALUE create_win32ole_record(IRecordInfo *pri, void *prec); +void Init_win32ole_record(); + +#endif -- cgit v1.2.3