Dr.eye , 譯點通 等螢幕抓字技術分析 |
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
我最近利用網路上有關如 Dr.eye 等有螢幕抓字功能的技術文章 , 進行實作
分析與可行性評估 , 結果發現這些文章所提的技術確實是可行的 !!! 於是我利用 VC++ 來實作一個小程式來證明它 , 其方法如下 : 1. 必需利用攔截 API 的方法 , 市面上提到的各種方法似乎都是可行的 .
2. 必需利用 HOOK 的技術 ( 要寫成 DLL 的方式 ) .
3. 此小程式進行了對 TextOut 函數的跟蹤 .
4. 進行文字串的抓取 . 結論 :
此小程式在 Win2000 上的成功率為 80 ~ 90 % ,在 XP 上為
50 ~ 60 左右 ! Win 98, ME 目前不準備實作測試 ! 而成功率
的提高可對攔截函數的總類加以擴大 , 應可再提高一些 ! 而
XP 的成功率偏低有可能是 , XP 的顯示方法函數不是我攔的
所致 ! 以上一些小心得希望對有興趣的網友減少一些時間的
浪費 !
< >< >
附加檔案:48753_TextEye.zip
|
bruce0211
版主 發表:157 回覆:668 積分:279 註冊:2002-06-13 發送簡訊給我 |
|
suda
一般會員 發表:17 回覆:63 積分:16 註冊:2002-05-10 發送簡訊給我 |
|
jackalan
初階會員 發表:20 回覆:88 積分:36 註冊:2003-11-08 發送簡訊給我 |
|
qoo1234
版主 發表:256 回覆:1167 積分:659 註冊:2003-02-24 發送簡訊給我 |
引言: KTOP是???交流的地方,不是?人成果展示,如果你真的希望?助?人????你的代?,否?我不知道你准?如何?助我?。可能我的看法也有??,但?是我想?的,如果有什么不??指教。 你的程式在WIN 9X下根本完全?效,XP下也不能?到50~60的成功率。請 jackalan 大大 看看版規: 本站十大必刪文章 一、重覆發表有灌水之嫌 二、答非所問或描述不清楚 三、抄襲他人文章或發表而未具名引用,或轉載未得原作者同意之文章 四、涉及人身攻擊或不雅文字 五、涉及政治立場 六、索求原始碼、序號、破解、非法下載連結 七、貼錯版區或主題不明確 八、答題內容明顯抄襲或重覆前答題者內容 九、很明顯的作業問題或不將自己寫的有問題的程式片段貼上而要求答案的 十、站務組保留任何對 K.Top 有不良形響文章刪除與會員停權處分的權利 敬請大家尊重本站的規定,維護本站文章的品質! 網海無涯,學無止境! |
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
|
japhenchen
高階會員 發表:51 回覆:444 積分:184 註冊:2003-07-23 發送簡訊給我 |
讓人捐款很好,可是我不喜歡這樣,讓人覺得這個技術還是要用條件交換才能得到,實在我很不以為然~~~~ 這不是我自己寫的程式,當然,我目前對這也沒什麼研究,但是,我可以發起這個研究熱,我不希望這項技術,是大陸那里的人的專利!台灣加油唄
以下提供這個畫面取詞的程式範例,這里附上了DELPHI版本的原始程式跟滑鼠HOOK的DLL ,打開BPG,二個全BUILD起來,再執行EXE就可以了~~
http://www.tommstudio.com/newclub30/d_download.asp 藏私の禁止 發表人 - japhenchen 於 2004/04/23 10:33:44
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
|
hahalin
版主 發表:295 回覆:1698 積分:823 註冊:2002-04-14 發送簡訊給我 |
|
japhenchen
高階會員 發表:51 回覆:444 積分:184 註冊:2003-07-23 發送簡訊給我 |
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
|
bruce0211
版主 發表:157 回覆:668 積分:279 註冊:2002-06-13 發送簡訊給我 |
|
anpino
版主 發表:31 回覆:477 積分:231 註冊:2003-01-02 發送簡訊給我 |
|
P.D.
版主 發表:603 回覆:4038 積分:3874 註冊:2006-10-31 發送簡訊給我 |
|
bruce0211
版主 發表:157 回覆:668 積分:279 註冊:2002-06-13 發送簡訊給我 |
會員發表區沒有提供 SOURCE CODE 的程式一堆
為何只有這篇有點爭議 ?? 1.看作者的"說明內容"有點虛晃一招,令人失望之嫌
我早就知道攔截滑鼠要用 HOOK , HOOK 要寫成 DLL....
對我來說 , 這很難以看出這叫做 "心得"
2.jackalan 長官的確太火爆, 我們雖能提出自己的建議,但不能因此認定別人就應該怎樣怎樣 ... 3.我個人認為會員發表區應該只有兩大類的作品,
(1).好用的工具發表(不含SOURCE CODE 無所謂),目的只在提供別人沒有或更方便的工具
(2)增進大家技術能力的作品範例或文章,而非蜻蜓點水式的說明
(3)如果只是測試或證明,沒有詳細技術說明的測試證明,不如直接去買一套易點通XP版來玩玩就可證明 4.對事不對人,既然扯到公益,現在社會很喜歡把"公益","法律"掛在嘴上,如果我們內心真的知道並了解真正的公益,那應該了解"取之社會,用之社會"也是一種公益,不是捐錢就叫公益,更不是較別人捐錢自己不捐,就叫支持公益, 有人天天高喊愛台灣,但你問那些在高速公路丟垃圾的人,到處吐槟郎汁的人,他愛不愛台灣.....以上只是看到本案例所延伸的想法,希望不要被認定為口水之爭 .... 回應文章內容 , 8866 長官沒有任何錯誤 , 大家也別再為難 8866 長官了,若大家真的對本篇內容有興趣, 也讓本篇討論不淪為口水之爭, 小弟找到幾個跟本篇相關且有建設性的資料 ,請參考 http://delphi.ktop.com.tw/topic.php?topic_id=22391
http://delphi.ktop.com.tw/topic.php?topic_id=32829
http://delphi.ktop.com.tw/topic.php?topic_id=19936
google 搜尋 "textout hook"
攔截 TEXTOUT HOOK 的參考資料 http://groups.google.com/groups?q=textout+hook&hl=en&lr=&ie=UTF-8&oe=UTF8&safe=off&selm=3A1951F2.DDF021B9@hotmail.com&rnum=2 library PigLatinDll; uses Windows, SysUtils, Classes, HookTextUnit in 'HookTextUnit.pas'; function PigLatinWord(s: String): String; Var start: String; Capitalize, AllCapitals: Boolean; i: Integer; begin Result:=s; if length(s)<=1 then exit; Capitalize:=IsCharUpper(s[1]); AllCapitals:=True; for i:=1 to length(s) do begin if IsCharLower(s[i]) then begin AllCapitals:=False; break; end; end; start:=lowercase(copy(s,1,2)); if (start[1]<'a') or (start[1]>'z') then exit; if (start[1] in ['a','e','i','o','u']) then start:=''; if (start<>'ch') and (start<>'th') and (start<>'sh') and (start<>'wh') and (start<>'qu') and (start<>'kn') and (start<>'wr') then delete(start,2,1); Result:=copy(s,length(start)+1,length(s))+start; if start='' then Result:=Result+'yay' else Result:=Result+'ay'; if AllCapitals then result:=Uppercase(Result) else if Capitalize then result[1]:=Upcase(result[1]); end; function IntToRoman(n: Integer): String; Var i, units, tens, hundreds, thousands: Integer; begin If (n>=5000) or (n<=0) then Result:=IntToStr(n) else begin thousands:=n div 1000; n:=n mod 1000; hundreds:=n div 100; n:=n mod 100; tens:=n div 10; n:=n mod 10; units:=n; Result:=''; for i:=1 to Thousands do begin Result:=Result+'M'; end; Case Hundreds of 1: Result:=Result+'C'; 2: Result:=Result+'CC'; 3: Result:=Result+'CCC'; 4: Result:=Result+'CD'; 5: Result:=Result+'D'; 6: Result:=Result+'DC'; 7: Result:=Result+'DCC'; 8: Result:=Result+'DCCC'; 9: Result:=Result+'CM'; end; Case Tens of 1: Result:=Result+'X'; 2: Result:=Result+'XX'; 3: Result:=Result+'XXX'; 4: Result:=Result+'XL'; 5: Result:=Result+'L'; 6: Result:=Result+'LX'; 7: Result:=Result+'LXX'; 8: Result:=Result+'LXXX'; 9: Result:=Result+'XC'; end; Case Units of 1: Result:=Result+'I'; 2: Result:=Result+'II'; 3: Result:=Result+'III'; 4: Result:=Result+'IV'; 5: Result:=Result+'V'; 6: Result:=Result+'VI'; 7: Result:=Result+'VII'; 8: Result:=Result+'VIII'; 9: Result:=Result+'IX'; end; end; end; function LatinNumber(s: String): String; Var n: Integer; begin try n:=StrToInt(s); Result:=IntToRoman(n); except Result:=s; end; end; function Conv(s: String): String; Var i: Integer; w: String; begin Result:=''; try if s='' then exit; i:=1; while (i<=length(s)) do begin while (i<=length(s)) and (s[i]<=' ') do begin Result:=Result+s[i]; Inc(i); end; // convert any numbers into latin numbers w:=''; while (i<=length(s)) and (s[i]>='0') and (s[i]<='9') do begin w:=w+s[i]; Inc(i); end; Result:=Result+LatinNumber(w); // add any other symbols unchanged (for now) w:=''; while (i<=length(s)) and not IsCharAlphaNumeric(s[i]) do begin w:=w+s[i]; Inc(i); end; Result:=Result+w; // convert whole words into pig latin w:=''; while (i<=length(s)) and IsCharAlpha(s[i]) do begin w:=w+s[i]; Inc(i); end; Result:=Result+PigLatinWord(w); end; except end; end; function GetMsgProc(code: integer; removal: integer; msg: Pointer): Integer; stdcall; begin Result:=0; end; Var HookHandle: THandle; procedure StartHook; stdcall; begin HookHandle:=SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance, 0); end; procedure StopHook; stdcall; begin UnhookWindowsHookEx(HookHandle); end; exports StartHook, StopHook; begin HookTextOut(Conv); end. ==================================================== unit HookTextUnit; interface uses Windows, SysUtils, Classes, PEStuff; type TConvertTextFunction = function(text: String): String; TTextOutA = function(hdc: HDC; x,y: Integer; text: PAnsiChar; len: Integer): BOOL; stdcall; TTextOutW = function(hdc: HDC; x,y: Integer; text: PWideChar; len: Integer): BOOL; stdcall; TExtTextOutA = function(hdc: HDC; x,y: Integer; Options: DWORD; Clip: PRect; text: PAnsiChar; len: Integer; dx: PInteger): BOOL; stdcall; TExtTextOutW = function(hdc: HDC; x,y: Integer; Options: DWORD; Clip: PRect; text: PWideChar; len: Integer; dx: PInteger): BOOL; stdcall; TDrawTextA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; format: DWORD): Integer; stdcall; TDrawTextW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; format: DWORD): Integer; stdcall; TDrawTextExA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; format: DWORD; DTParams: PDrawTextParams): Integer; stdcall; TDrawTextExW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; format: DWORD; DTParams: PDrawTextParams): Integer; stdcall; TTabbedTextOutA = function(hdc: HDC; x,y: Integer; text: PAnsiChar; len: Integer; TabCount: Integer; TabPositions: PInteger; TabOrigin: Integer): Integer; stdcall; TTabbedTextOutW = function(hdc: HDC; x,y: Integer; text: PWideChar; len: Integer; TabCount: Integer; TabPositions: PInteger; TabOrigin: Integer): Integer; stdcall; TPolyTextOutA = function(hdc: HDC; pptxt: PPOLYTEXTA; count: Integer): BOOL; stdcall; TPolyTextOutW = function(hdc: HDC; pptxt: PPOLYTEXTW; count: Integer): BOOL; stdcall; TGetTextExtentExPointA = function(hdc: HDC; text: PAnsiChar; len: Integer; maxExtent: Integer; Fit: PInteger; Dx: PInteger; Size: Pointer): BOOL; stdcall; TGetTextExtentExPointW = function(hdc: HDC; text: PWideChar; len: Integer; maxExtent: Integer; Fit: PInteger; Dx: PInteger; Size: Pointer): BOOL; stdcall; TGetTextExtentPoint32A = function(hdc: HDC; text: PAnsiChar; len: Integer; Size: Pointer): BOOL; stdcall; TGetTextExtentPoint32W = function(hdc: HDC; text: PWideChar; len: Integer; Size: Pointer): BOOL; stdcall; TGetTextExtentPointA = function(hdc: HDC; text: PAnsiChar; len: Integer; Size: Pointer): BOOL; stdcall; TGetTextExtentPointW = function(hdc: HDC; text: PWideChar; len: Integer; Size: Pointer): BOOL; stdcall; PPointer = ^Pointer; TImportCode = packed record JumpInstruction: Word; // should be $25FF AddressOfPointerToFunction: PPointer; end; PImportCode = ^TImportCode; procedure HookTextOut(ConvertFunction: TConvertTextFunction); procedure UnhookTextOut; implementation Var ConvertTextFunction: TConvertTextFunction = nil; OldTextOutA: TTextOutA = nil; OldTextOutW: TTextOutW = nil; OldExtTextOutA: TExtTextOutA = nil; OldExtTextOutW: TExtTextOutW = nil; OldDrawTextA: TDrawTextA = nil; OldDrawTextW: TDrawTextW = nil; OldDrawTextExA: TDrawTextExA = nil; OldDrawTextExW: TDrawTextExW = nil; OldTabbedTextOutA: TTabbedTextOutA = nil; OldTabbedTextOutW: TTabbedTextOutW = nil; OldPolyTextOutA: TPolyTextOutA = nil; OldPolyTextOutW: TPolyTextOutW = nil; OldGetTextExtentExPointA: TGetTextExtentExPointA = nil; OldGetTextExtentExPointW: TGetTextExtentExPointW = nil; OldGetTextExtentPoint32A: TGetTextExtentPoint32A = nil; OldGetTextExtentPoint32W: TGetTextExtentPoint32W = nil; OldGetTextExtentPointA: TGetTextExtentPointA = nil; OldGetTextExtentPointW: TGetTextExtentPointW = nil; function StrLenW(s: PWideChar): Integer; Var i: Integer; begin if s=nil then begin Result:=0; exit; end; i:=0; try while (s[i]<>#0) do inc(i); except end; Result:=i; end; function NewTextOutA(hdc: HDC; x,y: Integer; text: PAnsiChar; len: Integer): BOOL; stdcall; Var s: String; begin try if Len<0 then Len:=strlen(text); If Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldTextOutA<>nil then Result:=OldTextOutA(hdc,x,y,PAnsiChar(s),length(s)) else Result:=False; end else Result:=OldTextOutA(hdc,x,y,PAnsiChar(s),0); except Result:=False; end; end; function NewTextOutW(hdc: HDC; x,y: Integer; text: PWideChar; len: Integer): BOOL; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); If Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len*2); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldTextOutW<>nil then Result:=OldTextOutW(hdc,x,y,PWideChar(s),length(s)) else Result:=False; end else Result:=OldTextOutW(hdc,x,y,PWideChar(s),0); except Result:=False; end; end; function NewExtTextOutA(hdc: HDC; x,y: Integer; Options: DWORD; Clip: PRect; text: PAnsiChar; len: Integer; dx: PInteger): BOOL; stdcall; Var s: String; begin try if Len<0 then Len:=strlen(text); // ??? if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldExtTextOutA<>nil then Result:=OldExtTextOutA(hdc,x,y,Options,Clip,PAnsiChar(s),length(s),dx) else Result:=False; end else Result:=OldExtTextOutA(hdc,x,y,Options,Clip,text,0,dx); except Result:=False; end; end; function NewExtTextOutW(hdc: HDC; x,y: Integer; Options: DWORD; Clip: PRect; text: PWideChar; len: Integer; dx: PInteger): BOOL; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); If Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len*2); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldExtTextOutW<>nil then Result:=OldExtTextOutW(hdc,x,y,Options,Clip,PWideChar(s),length(s),dx) else Result:=False; end else Result:=OldExtTextOutW(hdc,x,y,Options,Clip,text,0,dx); except Result:=False; end; end; function NewDrawTextA(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; format: DWORD): Integer; stdcall; Var s: String; begin try if Len<0 then Len:=strlen(text); // ??? if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldDrawTextA<>nil then Result:=OldDrawTextA(hdc,PAnsiChar(s),length(s),rect,format) else Result:=0; end else Result:=OldDrawTextA(hdc,text,0,rect,format); except Result:=0; end; end; function NewDrawTextW(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; format: DWORD): Integer; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len*2); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldDrawTextW<>nil then Result:=OldDrawTextW(hdc,PWideChar(s),length(s),rect,format) else Result:=0; end else Result:=OldDrawTextW(hdc,text,0,rect,format); except Result:=0; end; end; function NewDrawTextExA(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; format: DWORD; DTParams: PDrawTextParams): Integer; stdcall; Var s: String; begin try if Len<0 then Len:=strlen(text); if len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldDrawTextExA<>nil then Result:=OldDrawTextExA(hdc,PAnsiChar(s),length(s),rect,format,DTParams) else Result:=0; end else Result:=OldDrawTextExA(hdc,text,0,rect,format,DTParams); except Result:=0; end; end; function NewDrawTextExW(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; format: DWORD; DTParams: PDrawTextParams): Integer; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len*2); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldDrawTextExW<>nil then Result:=OldDrawTextExW(hdc,PWideChar(s),length(s),rect,format,DTParams) else Result:=0; end else Result:=OldDrawTextExW(hdc,text,0,rect,format,DTParams); except Result:=0; end; end; function NewTabbedTextOutA(hdc: HDC; x,y: Integer; text: PAnsiChar; len: Integer; TabCount: Integer; TabPositions: PInteger; TabOrigin: Integer): Integer; stdcall; Var s: AnsiString; begin try if Len<0 then Len:=strlen(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldTabbedTextOutA<>nil then Result:=OldTabbedTextOutA(hdc,x,y,PAnsiChar(s),length(s),TabCount,TabPositions,TabOrigin) else Result:=0; end else Result:=OldTabbedTextOutA(hdc,x,y,text,0,TabCount,TabPositions,TabOrigin); except Result:=0; end; end; function NewTabbedTextOutW(hdc: HDC; x,y: Integer; text: PWideChar; len: Integer; TabCount: Integer; TabPositions: PInteger; TabOrigin: Integer): Integer; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len*2); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldTabbedTextOutW<>nil then Result:=OldTabbedTextOutW(hdc,x,y,PWideChar(s),length(s),TabCount,TabPositions,TabOrigin) else Result:=0; end else Result:=OldTabbedTextOutW(hdc,x,y,text,0,TabCount,TabPositions,TabOrigin); except Result:=0; end; end; function NewPolyTextOutA(hdc: HDC; pptxt: PPOLYTEXTA; count: Integer): BOOL; stdcall; Var s: String; i: Integer; ppnew: PPOLYTEXTA; begin ppnew:=nil; try Result:=False; if Count<0 then exit; if Count=0 then begin Result:=True; exit; end; GetMem(ppnew,count*sizeof(TPOLYTEXTA)); For i:=1 to count do begin ppnew^:=pptxt^; if ppnew^.n<0 then ppnew^.n:=strlen(ppnew^.PAnsiChar); if ppnew^.n>0 then begin SetLength(s,ppnew^.n); FillChar(s[1],ppnew^.n+1,0); Move(ppnew^.PAnsiChar,s[1],ppnew^.n); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); ppnew^.PAnsiChar:=PAnsiChar(s); ppnew^.n:=length(s); if @OldPolyTextOutA<>nil then Result:=OldPolyTextOutA(hdc,ppnew,1); end; Inc(pptxt); end; except Result:=False; end; if ppnew<>nil then FreeMem(ppnew); end; function NewPolyTextOutW(hdc: HDC; pptxt: PPOLYTEXTW; count: Integer): BOOL; stdcall; begin Result:=OldPolyTextOutW(hdc,pptxt,count); end; function NewGetTextExtentExPointA(hdc: HDC; text: PAnsiChar; len: Integer; maxExtent: Integer; Fit: PInteger; Dx: PInteger; Size: Pointer): BOOL; stdcall; Var s: AnsiString; begin try if Len<0 then Len:=strlen(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentExPointA<>nil then Result:=OldGetTextExtentExPointA(hdc,PAnsiChar(s),length(s),maxExtent,Fit,Dx,Size) else Result:=False; end else Result:=OldGetTextExtentExPointA(hdc,text,0,maxExtent,Fit,Dx,Size); except Result:=False; end; end; Function NewGetTextExtentExPointW(hdc: HDC; text: PWideChar; len: Integer; maxExtent: Integer; Fit: PInteger; Dx: PInteger; Size: Pointer): BOOL; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentExPointW<>nil then Result:=OldGetTextExtentExPointW(hdc,PWideChar(s),length(s),maxExtent,Fit,Dx,Size) else Result:=False; end else Result:=OldGetTextExtentExPointW(hdc,text,0,maxExtent,Fit,Dx,Size); except Result:=False; end; end; function NewGetTextExtentPoint32A(hdc: HDC; text: PAnsiChar; len: Integer; Size: Pointer): BOOL; stdcall; Var s: AnsiString; begin try if Len<0 then Len:=strlen(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentPoint32A<>nil then Result:=OldGetTextExtentPoint32A(hdc,PAnsiChar(s),length(s),Size) else Result:=False; end else Result:=OldGetTextExtentPoint32A(hdc,text,0,Size); except Result:=False; end; end; function NewGetTextExtentPoint32W(hdc: HDC; text: PWideChar; len: Integer; Size: Pointer): BOOL; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentPoint32W<>nil then Result:=OldGetTextExtentPoint32W(hdc,PWideChar(s),length(s),Size) else Result:=False; end else Result:=OldGetTextExtentPoint32W(hdc,text,0,Size); except Result:=False; end; end; function NewGetTextExtentPointA(hdc: HDC; text: PAnsiChar; len: Integer; Size: Pointer): BOOL; stdcall; Var s: AnsiString; begin try if Len<0 then Len:=strlen(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len+1,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentPointA<>nil then Result:=OldGetTextExtentPointA(hdc,PAnsiChar(s),length(s),Size) else Result:=False; end else Result:=OldGetTextExtentPointA(hdc,text,0,Size); except Result:=False; end; end; function NewGetTextExtentPointW(hdc: HDC; text: PWideChar; len: Integer; Size: Pointer): BOOL; stdcall; Var s: WideString; begin try if Len<0 then Len:=strlenW(text); if Len>0 then begin SetLength(s,len); FillChar(s[1],len*2+2,0); Move(text^,s[1],len); if @ConvertTextFunction<>nil then s:=ConvertTextFunction(s); if @OldGetTextExtentPoint32W<>nil then Result:=OldGetTextExtentPointW(hdc,PWideChar(s),length(s),Size) else Result:=False; end else Result:=OldGetTextExtentPointW(hdc,text,0,Size); except Result:=False; end; end; function PointerToFunctionAddress(Code: Pointer): PPointer; Var func: PImportCode; begin Result:=nil; if Code=nil then exit; try func:=code; if (func.JumpInstruction=$25FF) then begin Result:=func.AddressOfPointerToFunction; end; except Result:=nil; end; end; function FinalFunctionAddress(Code: Pointer): Pointer; Var func: PImportCode; begin Result:=Code; if Code=nil then exit; try func:=code; if (func.JumpInstruction=$25FF) then begin Result:=func.AddressOfPointerToFunction^; end; except Result:=nil; end; end; Function PatchAddress(OldFunc, NewFunc: Pointer): Integer; Var BeenDone: TList; Function PatchAddressInModule(hModule: THandle; OldFunc, NewFunc: Pointer): Integer; Var Dos: PImageDosHeader; NT: PImageNTHeaders; ImportDesc: PImage_Import_Entry; rva: DWORD; Func: PPointer; DLL: String; f: Pointer; written: DWORD; begin Result:=0; Dos:=Pointer(hModule); if BeenDone.IndexOf(Dos)>=0 then exit; BeenDone.Add(Dos); OldFunc:=FinalFunctionAddress(OldFunc); if IsBadReadPtr(Dos,SizeOf(TImageDosHeader)) then exit; if Dos.e_magic<>IMAGE_DOS_SIGNATURE then exit; NT :=Pointer(Integer(Dos) + dos._lfanew); // if IsBadReadPtr(NT,SizeOf(TImageNtHeaders)) then exit; RVA:=NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if RVA=0 then exit; ImportDesc := pointer(integer(Dos)+RVA); While (ImportDesc^.Name<>0) do begin DLL:=PChar(Integer(Dos)+ImportDesc^.Name); PatchAddressInModule(GetModuleHandle(PChar(DLL)),OldFunc,NewFunc); Func:=Pointer(Integer(DOS)+ImportDesc.LookupTable); While Func^<>nil do begin f:=FinalFunctionAddress(Func^); if f=OldFunc then begin WriteProcessMemory(GetCurrentProcess,Func,@NewFunc,4,written); If Written>0 then Inc(Result); end; Inc(Func); end; Inc(ImportDesc); end; end; begin BeenDone:=TList.Create; try Result:=PatchAddressInModule(GetModuleHandle(nil),OldFunc,NewFunc); finally BeenDone.Free; end; end; procedure HookTextOut(ConvertFunction: TConvertTextFunction); begin if @OldTextOutA=nil then @OldTextOutA:=FinalFunctionAddress(@TextOutA); if @OldTextOutW=nil then @OldTextOutW:=FinalFunctionAddress(@TextOutW); if @OldExtTextOutA=nil then @OldExtTextOutA:=FinalFunctionAddress(@ExtTextOutA); if @OldExtTextOutW=nil then @OldExtTextOutW:=FinalFunctionAddress(@ExtTextOutW); if @OldDrawTextA=nil then @OldDrawTextA:=FinalFunctionAddress(@DrawTextA); if @OldDrawTextW=nil then @OldDrawTextW:=FinalFunctionAddress(@DrawTextW); if @OldDrawTextExA=nil then @OldDrawTextExA:=FinalFunctionAddress(@DrawTextExA); if @OldDrawTextExW=nil then @OldDrawTextExW:=FinalFunctionAddress(@DrawTextExW); if @OldTabbedTextOutA=nil then @OldTabbedTextOutA:=FinalFunctionAddress(@TabbedTextOutA); if @OldTabbedTextOutW=nil then @OldTabbedTextOutW:=FinalFunctionAddress(@TabbedTextOutW); if @OldPolyTextOutA=nil then @OldPolyTextOutA:=FinalFunctionAddress(@PolyTextOutA); if @OldPolyTextOutW=nil then @OldPolyTextOutW:=FinalFunctionAddress(@PolyTextOutW); if @OldGetTextExtentExPointA=nil then @OldGetTextExtentExPointA:=FinalFunctionAddress(@GetTextExtentExPointA); if @OldGetTextExtentExPointW=nil then @OldGetTextExtentExPointW:=FinalFunctionAddress(@GetTextExtentExPointW); if @OldGetTextExtentPoint32A=nil then @OldGetTextExtentPoint32A:=FinalFunctionAddress(@GetTextExtentPoint32A); if @OldGetTextExtentPoint32W=nil then @OldGetTextExtentPoint32W:=FinalFunctionAddress(@GetTextExtentPoint32W); if @OldGetTextExtentPointA=nil then @OldGetTextExtentPointA:=FinalFunctionAddress(@GetTextExtentPointA); if @OldGetTextExtentPointW=nil then @OldGetTextExtentPointW:=FinalFunctionAddress(@GetTextExtentPointW); @ConvertTextFunction:=@ConvertFunction; PatchAddress(@OldTextOutA, @NewTextOutA); PatchAddress(@OldTextOutW, @NewTextOutW); PatchAddress(@OldExtTextOutA, @NewExtTextOutA); PatchAddress(@OldExtTextOutW, @NewExtTextOutW); PatchAddress(@OldDrawTextA, @NewDrawTextA); PatchAddress(@OldDrawTextW, @NewDrawTextW); PatchAddress(@OldDrawTextExA, @NewDrawTextExA); PatchAddress(@OldDrawTextExW, @NewDrawTextExW); PatchAddress(@OldTabbedTextOutA, @NewTabbedTextOutA); PatchAddress(@OldTabbedTextOutW, @NewTabbedTextOutW); PatchAddress(@OldPolyTextOutA, @NewPolyTextOutA); PatchAddress(@OldPolyTextOutW, @NewPolyTextOutW); PatchAddress(@OldGetTextExtentExPointA, @NewGetTextExtentExPointA); PatchAddress(@OldGetTextExtentExPointW, @NewGetTextExtentExPointW); PatchAddress(@OldGetTextExtentPoint32A, @NewGetTextExtentPoint32A); PatchAddress(@OldGetTextExtentPoint32W, @NewGetTextExtentPoint32W); PatchAddress(@OldGetTextExtentPointA, @NewGetTextExtentPointA); PatchAddress(@OldGetTextExtentPointW, @NewGetTextExtentPointW); end; procedure UnhookTextOut; begin If @OldTextOutA<>nil then begin PatchAddress(@NewTextOutA, @OldTextOutA); PatchAddress(@NewTextOutW, @OldTextOutW); PatchAddress(@NewExtTextOutA, @OldExtTextOutA); PatchAddress(@NewExtTextOutW, @OldExtTextOutW); PatchAddress(@NewDrawTextA, @OldDrawTextA); PatchAddress(@NewDrawTextW, @OldDrawTextW); PatchAddress(@NewDrawTextExA, @OldDrawTextExA); PatchAddress(@NewDrawTextExW, @OldDrawTextExW); PatchAddress(@NewTabbedTextOutA, @OldTabbedTextOutA); PatchAddress(@NewTabbedTextOutW, @OldTabbedTextOutW); PatchAddress(@NewPolyTextOutA, @OldPolyTextOutA); PatchAddress(@NewPolyTextOutW, @OldPolyTextOutW); PatchAddress(@NewGetTextExtentExPointA, @OldGetTextExtentExPointA); PatchAddress(@NewGetTextExtentExPointW, @OldGetTextExtentExPointW); PatchAddress(@NewGetTextExtentPoint32A, @OldGetTextExtentPoint32A); PatchAddress(@NewGetTextExtentPoint32W, @OldGetTextExtentPoint32W); PatchAddress(@NewGetTextExtentPointA, @OldGetTextExtentPointA); PatchAddress(@NewGetTextExtentPointW, @OldGetTextExtentPointW); end; end; initialization finalization UnhookTextOut; end. =================================================== unit PEStuff; interface uses Windows; type PImageDosHeader = ^TImageDosHeader; _IMAGE_DOS_HEADER = packed record { DOS .EXE header } e_magic: Word; { Magic number } e_cblp: Word; { Bytes on last page of file } e_cp: Word; { Pages in file } e_crlc: Word; { Relocations } e_cparhdr: Word; { Size of header in paragraphs } e_minalloc: Word; { Minimum extra paragraphs needed } e_maxalloc: Word; { Maximum extra paragraphs needed } e_ss: Word; { Initial (relative) SS value } e_sp: Word; { Initial SP value } e_csum: Word; { Checksum } e_ip: Word; { Initial IP value } e_cs: Word; { Initial (relative) CS value } e_lfarlc: Word; { File address of relocation table } e_ovno: Word; { Overlay number } e_res: array [0..3] of Word; { Reserved words } e_oemid: Word; { OEM identifier (for e_oeminfo) } e_oeminfo: Word; { OEM information; e_oemid specific} e_res2: array [0..9] of Word; { Reserved words } _lfanew: LongInt; { File address of new exe header } end; TImageDosHeader = _IMAGE_DOS_HEADER; PIMAGE_FILE_HEADER = ^IMAGE_FILE_HEADER; IMAGE_FILE_HEADER = packed record Machine : WORD; NumberOfSections : WORD; TimeDateStamp : DWORD; PointerToSymbolTable : DWORD; NumberOfSymbols : DWORD; SizeOfOptionalHeader : WORD; Characteristics : WORD; end; PIMAGE_DATA_DIRECTORY = ^IMAGE_DATA_DIRECTORY; IMAGE_DATA_DIRECTORY = packed record VirtualAddress : DWORD; Size : DWORD; end; PIMAGE_SECTION_HEADER = ^IMAGE_SECTION_HEADER; IMAGE_SECTION_HEADER = packed record Name : packed array [0..IMAGE_SIZEOF_SHORT_NAME-1] of Char; VirtualSize : DWORD; // or VirtualSize (union); VirtualAddress : DWORD; SizeOfRawData : DWORD; PointerToRawData : DWORD; PointerToRelocations : DWORD; PointerToLinenumbers : DWORD; NumberOfRelocations : WORD; NumberOfLinenumbers : WORD; Characteristics : DWORD; end; PIMAGE_OPTIONAL_HEADER = ^IMAGE_OPTIONAL_HEADER; IMAGE_OPTIONAL_HEADER = packed record { Standard fields. } Magic : WORD; MajorLinkerVersion : Byte; MinorLinkerVersion : Byte; SizeOfCode : DWORD; SizeOfInitializedData : DWORD; SizeOfUninitializedData : DWORD; AddressOfEntryPoint : DWORD; BaseOfCode : DWORD; BaseOfData : DWORD; { NT additional fields. } ImageBase : DWORD; SectionAlignment : DWORD; FileAlignment : DWORD; MajorOperatingSystemVersion : WORD; MinorOperatingSystemVersion : WORD; MajorImageVersion : WORD; MinorImageVersion : WORD; MajorSubsystemVersion : WORD; MinorSubsystemVersion : WORD; Reserved1 : DWORD; SizeOfImage : DWORD; SizeOfHeaders : DWORD; CheckSum : DWORD; Subsystem : WORD; DllCharacteristics : WORD; SizeOfStackReserve : DWORD; SizeOfStackCommit : DWORD; SizeOfHeapReserve : DWORD; SizeOfHeapCommit : DWORD; LoaderFlags : DWORD; NumberOfRvaAndSizes : DWORD; DataDirectory : packed array [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of IMAGE_DATA_DIRECTORY; Sections: packed array [0..9999] of IMAGE_SECTION_HEADER; end; PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS; IMAGE_NT_HEADERS = packed record Signature : DWORD; FileHeader : IMAGE_FILE_HEADER; OptionalHeader : IMAGE_OPTIONAL_HEADER; end; PImageNtHeaders = PIMAGE_NT_HEADERS; TImageNtHeaders = IMAGE_NT_HEADERS; { PIMAGE_IMPORT_DESCRIPTOR = ^IMAGE_IMPORT_DESCRIPTOR; IMAGE_IMPORT_DESCRIPTOR = packed record Characteristics: DWORD; // or original first thunk // 0 for terminating null import descriptor // RVA to original unbound IAT TimeDateStamp: DWORD; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) Name: DWORD; FirstThunk: DWORD; // PIMAGE_THUNK_DATA // RVA to IAT (if bound this IAT has actual addresses) ForwarderChain: DWORD; // -1 if no forwarders end; TImageImportDescriptor = IMAGE_IMPORT_DESCRIPTOR; PImageImportDescriptor = PIMAGE_IMPORT_DESCRIPTOR;} PIMAGE_IMPORT_BY_NAME = ^IMAGE_IMPORT_BY_NAME; IMAGE_IMPORT_BY_NAME = record Hint: Word; Name: Array[0..0] of Char; end; PIMAGE_THUNK_DATA = ^IMAGE_THUNK_DATA; IMAGE_THUNK_DATA = record Whatever: DWORD; end; PImage_Import_Entry = ^Image_Import_Entry; Image_Import_Entry = record Characteristics: DWORD; TimeDateStamp: DWORD; MajorVersion: Word; MinorVersion: Word; Name: DWORD; LookupTable: DWORD; end; const IMAGE_DOS_SIGNATURE = $5A4D; // MZ IMAGE_OS2_SIGNATURE = $454E; // NE IMAGE_OS2_SIGNATURE_LE = $454C; // LE IMAGE_VXD_SIGNATURE = $454C; // LE IMAGE_NT_SIGNATURE = $00004550; // PE00 implementation end. ================================================= Create a new project with one form, with two buttons. ================================================= unit PigLatinUnit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, forms, Dialogs, StdCtrls; type Tform1 = class(Tform) Button1: TButton; Button2 |
qoo1234
版主 發表:256 回覆:1167 積分:659 註冊:2003-02-24 發送簡訊給我 |
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
1. '這很難以看出這叫做 "心得"' ==> 請原諒我的無知 , 在此之前我根本
不知道要用此 HOOK 相關技術 , 只
是看一些資料後 , 才興起把它實作
看看 , '驗證' 看看是否可行 !
2. 'jackalan 長官的確太火爆' ==> 這也沒什麼 , 一個民主的社會本來就有
許多意見 , 看多聽多就沒什麼了 . 3. '而非蜻蜓點水式的說明' ==> 我原想把自己驗證的結果 '說出來' , 畢竟
網路上那些文章的想法與做法是有許多的
'不確定性' , 自己沒玩過怎知道行不行呢 ?
再者希望引起對這題目有興趣的同好 , 相互
討論 , 研究 .
4. "公益" ==> 我沒想過 , 只是一個單純的出發點而已 ! 5. '大家也別再為難 8866 長官了' ==> 沒這嚴重 , 只是大家各自表述自己的
意見罷了 . 6. '請站長把這篇討論鎖起來吧!' ==> 這點建議也不錯 , 只是我不認為以上
各位的發言是 '口水之爭' !!! >< >< > 發表人 -
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
|
jackalan
初階會員 發表:20 回覆:88 積分:36 註冊:2003-11-08 發送簡訊給我 |
真是抱歉,没想到我只是发表一下自己真实的想法就触犯了版规,并说了云云。我并无任何语言攻击,也许是我的语气太过强烈了,可能我就是有什么放不到心里的人,以后我会注意的。但有点我还是想说说,看到大家的回应后我更多的是感受到欣慰,我希望KTOP也像媒体一样(因为对我来说KTOP的确是学习DELPHI的最好媒体),每个人都能有言论自由,很多东西都是在争论中得到结果的。 qoo1234先生,不要动不动就拿版规来,相信在KTOP里的人这点觉悟还是有的,如果一有反面意见就拿版规来,我觉得应该学习的是您。按照您的意思是不是什么反面意见都不能提,只要有人发表文章就要双手赞成?否则就要版规处置,对于我刚说了不到两句就拿出版规来,我感到非常失望。 我的看法依然不变,8866您的这篇文章如果从标题看实在是太过实了,何为技術分析?别的不想多说了,总之关于"只是驗證網路上的那些文章是否 "可行" "实在荒谬。 至于“畢竟 1200 元對你一個已經有工作的人 , 實在也不是一種負擔不是嗎 ???”我没有意见,但请不要挂上“捐款”的名义,好像多名正言顺似的。1200不是多不多的问题,KTOP还是交流的地方吗?KTOP中好的文章和程序数不胜数,如果人人都挂个“捐款”才能得到原代码,那还有什么交流而言。是不是对公开要求“捐款”,也应该上个KTOP版规呢? 發表人 - jackalan 於 2004/04/25 21:37:36
|
liki
一般會員 發表:2 回覆:4 積分:6 註冊:2003-08-22 發送簡訊給我 |
Hook Win32 API 的應用研究之四:螢幕取詞 用過金山詞霸吧?用過的人一定對它的螢幕取詞功能印象很深刻,因為這種功能使翻譯程序更加簡便快捷,螢幕取詞是金山詞霸的核心技術之一。 大家有沒有想過這樣神奇的功能是如何實現的呢?經歷過DOS年代系統設計的人可能知道,螢幕上顯示的字元是存放在顯存裡的,每個坐標的字元對應顯存的一個特定的現存單元存儲的字元,直接操作顯存,就可以進行字元的顯示和讀取,若WINDOWS是這樣就好了,可惜事實上相去甚遠。那WINDOWS的字元是怎樣顯示的呢?WINDOWS是圖形界面,顯示的最小單位是像素(Pixel),上面的所有東西都是"畫"上去的,當然也包括了字元,也就沒有什麼字元顯存的概念了。沒有了直接操作顯存而獲得螢幕上字元內容的辦法了,那還有什麼方法呢? 讓我們來設身處地地想想看,假如我們要在自己的程式中顯示一個字串,我們會怎樣做呢?不要回答是MessageBox(),我們不是指的這種"顯示"方法,我指的是最低階的方法,也就是直接操作DC的方法,我想一般就是調用上面提到過的Win32 API函數TextOut()了,當然,還有類似的一些其它函數,例如:ExtTextOut()、DrawText()、DrawTextEx()等等。好了,找到點眉目了,我們來看看這些函數的參數能提供哪些信息,這裡只列出TextOut()函數的定義,其它的函數基本都包含這些參數,另外提供了更多的附加選項而已,請查閱MSDN相關文檔: BOOL TextOut(
HDC hdc, // 設備上下文引用
int nXStart, // 開始繪製字串的位置的x坐標
int nYStart, // 開始繪製字串的位置的y坐標
LPCTSTR lpString, // 指向字串的指標
int cbString // 指明要繪製多少個字元
); 我們看到,坐標和內容都有了,這不正是我們想要的信息嗎?只要Hook住這個函數,這些信息不都唾手可得了嗎?於是祭出Hook大法來做個實驗:先隨便用VC的嚮導開闢一個單文檔應用程式,在OnDraw()函數里調用TextOut()在某個位置隨便輸出一個字串(不論是調用pDC->TextOut(...)或者是::TextOut(...)都一樣,CDC類只不過把TextOut()封裝了一下而已),然後在OnInitialUpdate()裡設置Hook(用現成的庫),鉤住TextOut(),截獲TextOut之後,讓TextOut()輸出另外一個字串而不輸出原來的字串。還要記住在OnDestroy()裡解除Hook。最後編譯連接,測試程式。你會發現不僅是你調用TextOut()輸出的地方的字串被替換了,而且連才旦、對話框等等有字的地方也變了,在實驗成功之餘,是不是個意外的收穫?其實WINDOWS內部的大多數文字輸出也是調用了TextOut()函數來實現的。現在水落石出了,我們只要Hook住文字輸出函數,包括我上面提到的和沒有提到的函數,就能截獲螢幕上文字輸出的坐標和內容等等信息,只要我們一一作記錄,並加以分析轉換,跟鼠標的位置進行比較,我們就能得到螢幕上某個位置的文字內容是什麼了,要翻譯怎麼的,就看你的了,這就是螢幕取詞,雖然實際上實現的程序並不像說得那麼簡單。 出了詞霸的螢幕取詞,還有一些動態中文化、外掛中文平台之類的軟體,也是基於這種技術的,現在看來,它們是不是已經不再神秘了? 發表人 - liki 於 2004/04/26 01:33:46
|
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
1. 8866您的这篇文章如果从标题看实在是太过实了,何为技術分析 ??
Ans : 把 '自己' 的所見所聞的實驗結果與大家討論 , 並盼望能有更高竿
的建議改進自己的做法 ! 2. 别的不想多说了,总之关于"只是驗證網路上的那些文章是否 "可行" "实在
荒谬 !
Ans : 難道你沒聽過 '盡信書不如無書' 的說法嗎 ? 我實在對於你的用詞
感到極度的 '無奈' ! 3. 但请不要挂上“捐款”的名义,好像多名正言顺似的.
Ans : 捐不捐是每個人的自由 , 我也有我自己的判斷 ! 4. KTOP还是交流的地方吗 ?
Ans : 交流並不是只有 Source code 喔 ! 還包掛文件 , 心得 , 實驗結
果 , 心情故事 等 ... 5. 我會建議站長把此討論給刪掉 ! 避免一些激烈的言論影響讀者的心情 .
|
jackalan
初階會員 發表:20 回覆:88 積分:36 註冊:2003-11-08 發送簡訊給我 |
不要用什么“極度的 '無奈'”,“捐不捐是每個人的自由 , 我也有我自己的判斷 !”、“交流並不是只有 Source code 喔 ! 還包掛文件 , 心得 , 實驗結
果 , 心情故事”来回应问题,去读读你对所有发表人的评论,好像大家都错了似了。 “交流並不是只有 Source code 喔 ! 還包掛文件 , 心得 , 實驗結
果 , 心情故事”我很赞成,但还是请搞清楚什么叫“心得”什么叫“實驗結
果”,发个所谓的“實驗結果”就让人“捐款”就是你“捐不捐是每個人的自由 , 我也有我自己的判斷 !”的理由吗?如果这个就是帮助别人的话,相信想作帮人的人就太多了。 也强烈建议版主删除这篇文章,口水仗不想再打了,以后不对这类文章发表意见就是,什么人才能进步,相信是能接受意见的人。不知道是我肚量太小还是什么的,每个人的每个反面意见你都做了“批示”,就算我们错了吧,“實驗結果”继续、“捐款”继续。。。 至于谁会“影響讀者的心情”,大家会有公论。。。 發表人 -
|
shieh2700
高階會員 發表:0 回覆:127 積分:100 註冊:2002-06-13 發送簡訊給我 |
引言: 長官 沒有 SourceCode 的程式 , 如何"對有興趣的網友減少一些時間的浪費" 既然也是參考別人的文章 , 提供自己的 SourceCode 應該也不為過 >>< face="Verdana, Arial, Helvetica"> 長官: 由您先前的大作中得知, 您似乎是在開發 POS 系統 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=19269 您近期有提供或開放完整 POS 系統 Source Code 的計劃嗎? 我想大家應該會很期待的... 發表人 - shieh2700 於 2004/04/26 10:37:55 |
8866
中階會員 發表:27 回覆:147 積分:69 註冊:2002-10-14 發送簡訊給我 |
|
領航天使
站長 發表:12216 回覆:4186 積分:4084 註冊:2001-07-25 發送簡訊給我 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |