Take_tk
ggb03****@nifty*****
Wed Nov 13 00:23:51 JST 2002
Hi! "repeater" <repea****@lucen*****>. This is take_tk [Apollo-talk:48] methods "repeater" <repea****@lucen*****> wrote: : 1.) the process of wrapping the methods, where and how ---- Pursuit of edit.on_click : 1.1.1. at the time of 'require "phi"' 'require "phi"' calls "phi.rb", and "phi.rb" calls "phi.so". =>C:/PROGRA~1/Apollo/bin/phi.rb[phi] require 'win32.rb' =>C:/PROGRA~1/Apollo/bin/win32.rb[phi] require 'phi.so' Ruby performs Init_phi procedure, when it reads a library "phi.so". =>C:/PROGRA~1/Apollo/src/Phi.dpr[Init_phi] exports Init_phi; =>C:/PROGRA~1/Apollo/src/PhiMainUnit.pas[Init_phi] procedure Init_phi; cdecl; begin .... Init_Edit; .... end; =>C:/PROGRA~1/Apollo/src/u/uEdit.pas[OutputPersistentClass] procedure Init_edit; begin .... cEdit := OutputPersistentClass(mPhi, TEdit, cWinControl, ap_iEdit_v); rb_define_method(cEdit, 'event_handle', @Edit_event_handle, 1); .... end OutputPersistentClass becomes effective, when phi.dpr is compiled with MAKE_PROP definition. It outputs the followings to a file called PhiProp.inc. This is a Delphi source which add methods to the Edit object of ruby. Then recompile Phi.dpr with the code. I will explain this later. Here, I'll explain as the code exists already. =>C:/PROGRA~1/Apollo/src/PhiProp.inc[edit] klass := cEdit; rb_define_method(klass, 'on_click=', @Prop_set_method, 1); rb_define_method(klass, 'on_click', @retnil, -1); The line of "rb_define_method( .. );" defines a Ruby method called Edit#event_handle. Contents is a Delphi function Edit_event_handle. =>C:/PROGRA~1/Apollo/src/u/uEdit.pas[Edit_event_handle] function Edit_event_handle(This, name: Tvalue): Tvalue; cdecl; begin EventHandle(This, name, [Handle]); result := Qnil; end; 1.1.2. when "Edit.new" This "Edit::event_handle" method is called in the following portion of phi.rb. It is called by "initialize" of a Persistent class. That is, it is called at the time of "Edit.new". =>C:/PROGRA~1/Apollo/bin/phi.rb[EventHandle] module Phi EventHandle = {} class Persistent .... def initialize(*args) super(*args) if EventHandle[type] EventHandle[type].each do|id| begin event_handle id rescue DelphiError # end end end end end end This "EventHandle" in the ruby script is a Hash object which contains { class => [method_name1, .. ] } The method names are added, when a method is defined or added by "include" or "extend". The line of "event_handle id" performs the following Delphi code. "Handle" is a general-purpose PhiHandle object defined by uHandle.pas. =>C:/PROGRA~1/Apollo/src/u/uEdit.pas[Edit_event_handle] function Edit_event_handle(This, name: Tvalue): Tvalue; cdecl; begin EventHandle(This, name, [Handle]); result := Qnil; end; =>C:/PROGRA~1/Apollo/src/u/uHandle.pas[Handle] var Handle: TPhiHandle; The arguments of EventHandle, "This" if cEdit and "event_name" is 'on_click'. EventHandle(cEdit,"on_click") performs a Delphi program "Edit1.OnClick : = TPhiHandle.NotifyOnClick;". =>C:/PROGRA~1/Apollo/src/u/uProp.pas[EventHandle] procedure EventHandle(This, event_name: Tvalue; Handles: array of TObject); var Obj: TObject; FPropInfo: PPropInfo; ATypeInfo: PTypeInfo; AMethod: TMethod; AHandle: TObject; name: ShortString; i: Integer; begin ap_data_get_object(This, TObject, Obj); FPropInfo := GetPropInfo(Obj, Capitalize1(dl_caption(event_name))); if FPropInfo = nil then ap_raise(ap_eDelphiError, 'property not found'); ATypeInfo := FPropInfo^.PropType^; if ATypeInfo^.Kind <> tkMethod then ap_raise(ap_eDelphiError, 'not method'); name := ATypeInfo^.Name; name := Copy(name, 2, Length(name)-6) + FPropInfo^.Name; // => "NotifyOnClick" for i := Low(Handles) to High(Handles) do begin AHandle := Handles[i]; AMethod.Code := AHandle.MethodAddress(name); AMethod.Data := AHandle; if AMethod.Code = nil then else begin SetMethodProp(Obj, FPropInfo, AMethod); // OnXX := doXX; end; end; end; "name" is assigned with "NotifyOnClick", which is constructed "Notify", the core name of the class name "TNotifyEvent" and the event name "OnClick". The line "SetMethodProp(Obj, FPropInfo, and AMethod);" is equivalent to a Delphi program "Edit1.OnClick : = TPhiHandle.NotifyOnClick;". Then, when an event occurs, TPhiHandle.NotifyOnClick comes to be called. 1.1.3. when the event occurred When the event occurs, TPhiHandle.NotifyOnClick is called. NotifyOnClick is defined in the following portion. =>C:/PROGRA~1/Apollo/src/PhiHandle.pas[NotifyOnClick] procedure TPhiHandle.NotifyOnClick(Sender: TObject); begin doNotify(Sender, 'on_click'); end; procedure TPhiHandle.doNotify(Sender: TObject; name: PChar); var recv, data: Tvalue; begin recv := TComponent(Sender).tag; if recv = 0 then Exit; data := rb_ary_new; rb_ary_push(data, rb_intern(name)); rb_ary_push(data, recv); PhiCallProtect(data); end; "PhiCallProtect(data);" should perform ruby script "edit1.on_click(self)". =>C:/PROGRA~1/Apollo/src/PhiMainUnit.pas[PhiCallProtect] function PhiCallProtect(data: Tvalue): Tvalue; var state: Integer; begin result := ap_protect(PhiCall, data, state); if state <> 0 then PhiFail; end; function PhiCall(data: Tvalue): Tvalue; cdecl; var id: Tvalue; begin id := rb_ary_shift(data); result := PhiEventCall(id, data); end; function PhiEventCall(id: Tid; args: Tvalue): Tvalue; var hash, key, obj, recv: Tvalue; begin result := Qnil; try hash := rb_iv_get(ap_ary_ptr(args)^, '@events'); if RTYPE(hash) = T_HASH then begin key := ID2SYM(id); obj := rb_hash_aref(hash, key); if obj <> ap_hash_ifnone(hash) then result := rb_apply(obj, id_call, args); // call "proc" type handler end; if result = Qnil then begin recv := rb_ary_shift(args); if RTEST(recv) then result := rb_apply(recv, id, args); // call "def" type handler end; except on E: Exception do ap_raise(ap_eDelphiError, E.message); end; end; PhiEventCall で最初に on_click= でセットされた proc 型のイベントハンドラを呼び出し、次に def 型のイベントハンドラを呼び出す。これで行き止まり。 PhiEventCall calls "proc" type handler first, and secondly calls "def" type handler. 1.1.4. precompile It returns to a start. =>C:/PROGRA~1/Apollo/src/u/uEdit.pas[OutputPersistentClass] procedure Init_edit; begin .... cEdit := OutputPersistentClass(mPhi, TEdit, cWinControl, ap_iEdit_v); .... end OutputPersistentClass becomes effective, when phi.dpr is compiled with MAKE_PROP definition. It outputs the followings to a file called PhiProp.inc. This is a Delphi source which add methods to the Edit object of ruby. Then recompile Phi.dpr with the code. =>C:/PROGRA~1/Apollo/src/PhiProp.inc[edit] klass := cEdit; rb_define_method(klass, 'on_click=', @Prop_set_method, 1); rb_define_method(klass, 'on_click', @retnil, -1); =>C:/PROGRA~1/Apollo/src/PhiProp.bat[MAKE_PROP] dcc32 -B -DMAKE_PROP phi cd ..\bin ruby_ap -rphi.so -e0 cd ..\src dcc32 -B phi > out ruby PhiProp.rb out dcc32 -B phi del PhiProp.inc~ del out =>C:/PROGRA~1/Apollo/src/u/uPersistent.pas[OutputPersistentClass] function OutputPersistentClass(module: Tvalue; AClass: TPersistentClass; super: Tvalue; func: TAllocFunc): Tvalue; var v, t: Tvalue; S: string; i: Integer; begin v := cPersistent; t := rb_funcall2(super, rb_intern('>'), 1, @v); if RTEST(t) then ap_raise(ap_eDelphiError, AClass.ClassName +': '+ rb_class2name(super)+ ' > ' +rb_class2name(v)+ ' in OutputPersistentClass'); try RegisterClass(AClass); except on E: EFilerError do; end; S := AClass.ClassName; PhiAllocFuncList.AddObject(S, @func); i := 1; if Copy(S, 1, 5) = 'TPhi_' then i := 5; result := rb_define_class_under(module, PChar(S)+i, super); rb_iv_set(result, '_class', rb_data_object_alloc(ap_cObject, AClass.ClassInfo, nil, nil)); OutputProp(result, AClass); rb_ary_push(vPhiComponents, result); end; =>C:/PROGRA~1/Apollo/src/u/uHandle.pas[PhiAllocFuncList] function PhiAllocFuncList: TStringList; begin result := Handle.AllocFuncList; end; =>C:/PROGRA~1/Apollo/src/PhiHandle.pas[AllocFuncList] type TPhiHandle = class(TObject) private AppDoneThread: TNotifyEvent; public ExtentList: TObjectList; ObjectList: TObjectList; AllocFuncList: TStringList; EventFuncList: TStringList; =>C:/PROGRA~1/Apollo/src/u/uProp.pas[OutputProp] {$IFDEF MAKE_PROP} procedure OutputProp(cClass: Tvalue; AClass: TClass); var ATypeInfo: PTypeInfo; APropList: TPropList; Count, i: Integer; ATypeData: PTypeData; APropInfo: TPropInfo; name: PChar; ary, defined_p: Tvalue; argc: Integer; args: array of Tvalue; Readable, Writable: Boolean; begin argc := 1; SetLength(args, argc); args[0] := Qtrue; ary := rb_class_instance_methods(argc, @args, cClass); ATypeInfo := PTypeInfo(AClass.ClassInfo); ATypeData := GetTypeData(ATypeInfo); Count := ATypeData^.PropCount; GetPropInfos(ATypeInfo, @APropList); OutputClassName(cClass); for i := 0 to Count-1 do begin APropInfo := APropList[i]^; ATypeData := GetTypeData(APropInfo.PropType^); name := PChar(LowerCase1(APropInfo.Name)); if name = 'tag' then continue; Readable := APropInfo.GetProc <> nil; Writable := APropInfo.SetProc <> nil; { cClass#name method defined? } defined_p := rb_ary_includes(ary, rb_str_new2(name)); if not RTEST(defined_p) then begin case APropInfo.PropType^^.Kind of tkInteger, tkChar, tkWChar: begin if Writable then OutputAttrSet(cClass, name, 'Prop_set_integer'); if Readable then OutputAttrGet(cClass, name, 'Prop_get_integer'); end; .... tkMethod: begin if Writable then OutputAttrSet(cClass, name, 'Prop_set_method'); if Readable then OutputMethod(cClass, name, 'retnil'); end; end; end; end; end; {$ELSE} procedure OutputProp(cClass: Tvalue; AClass: TClass); begin // nothing end; {$ENDIF} Event handler should be matched to tkMethod. tkMethod: begin if Writable then OutputAttrSet(cClass, name, 'Prop_set_method'); if Readable then OutputMethod(cClass, name, 'retnil'); end; OutputMethod performs the following output. This is the dammy handler for "def" type handler. =>C:/PROGRA~1/Apollo/src/PhiProp.inc[edit] rb_define_method(klass, 'on_click', @retnil, -1); C:/PROGRA~1/Apollo/src/u/uProp.pas[OutputMethod] procedure OutputMethod(klass: Tvalue; name, func: PChar); begin rb_define_method(klass, name, nil, 0); WriteLn(FProp, ' rb_define_method(klass, ''', name, ''', @', func, ', -1);'); end; OutputAttrSet performs the following output. This is for setting a "proc" type handler. =>C:/PROGRA~1/Apollo/src/PhiProp.inc[edit] rb_define_method(klass, 'on_click=', @Prop_set_method, 1); =>C:/PROGRA~1/Apollo/src/u/uProp.pas[OutputAttrSet] procedure OutputAttrSet(klass: Tvalue; name, attr: PChar); begin rb_define_method(klass, PChar(joinEq(name)), nil, 0); WriteLn(FProp, ' rb_define_method(klass, ''', name, '='', @', attr, ', 1);'); end; 1.1.5. when "Edit.on_click = proc{ .. }" In this one, further pursuit of Prop_set_method is needed. =>C:/PROGRA~1/Apollo/src/u/uProp.pas[Prop_set_method] function Prop_set_method(This, v: Tvalue): Tvalue; cdecl; var hash, key: Tvalue; name: string; id: Tid; begin hash := rb_iv_get(This, '@events'); name := LowerCase1(rb_id2name(rb_frame_last_func)); SetLength(name, Length(name)-1); // chop! id := rb_intern(PChar(name)); key := ID2SYM(id); if rb_respond_to(This, id_event_handle) <> 0 then rb_funcall2(This, id_event_handle, 1, @key); rb_hash_aset(hash, key, v); result := v; end; The instance variable of a ruby Hash object called "@events" is prepared. rb_frame_last_func contains the method name "on_click=" The last "=" is removed. And puts "on_click" into "name". And it calls Edit.event_handle with "on_click". And "@events["on_click"] = proc_object" It calls Edit.event_handle method with "on_click", if it exists =>C:/PROGRA~1/Apollo/src/u/uProp.pas[id_event_handle] var id_event_handle: Tid; procedure Init_Prop; begin id_event_handle := rb_intern('event_handle'); end; event_handle is already explained. It performs a Delphi program "Edit1.OnClick : = TPhiHandle.NotifyOnClick;". =>C:/PROGRA~1/Apollo/src/u/uEdit.pas[Edit_event_handle] function Edit_event_handle(This, name: Tvalue): Tvalue; cdecl; begin EventHandle(This, name, [Handle]); result := Qnil; end; --- huiiii. take_tk = kumagai hidetake