線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:5423
推到 Plurk!
推到 Facebook!

Delphi 透過 DDE 讀取 YesWin 的即時資訊出現亂碼

答題得分者是:aftcast
bestlong
站務副站長


發表:125
回覆:734
積分:506
註冊:2002-10-19

發送簡訊給我
#1 引用回覆 回覆 發表時間:2009-03-20 14:07:00 IP:60.248.xxx.xxx 訂閱
看到網路上有在寫程式讀取下單軟體的即時資訊
想說來試一試結果資料是讀到了不過卻附上亂碼
畫面如下:




[code delphi]
procedure TForm1.FormCreate(Sender: TObject);
begin
ddeClientItem1.Lines.Clear;
ddeClientItem2.Lines.Clear;
ddeClientItem3.Lines.Clear;
ddeClientItem4.Lines.Clear;

ddeClientConv1.ConnectMode := ddeAutomatic;
ddeClientConv1.FormatChars := True;
ddeClientConv1.FormatChars := False;
ddeClientConv1.SetLink('YES','DQ');

DdeClientItemTime.DdeConv := ddeClientConv1;
DdeClientItemTime.DdeItem := 'Time';

ddeClientItem1.DdeConv := ddeClientConv1;
ddeClientItem1.DdeItem := '$TWT.Name';
ddeClientItem2.DdeConv := ddeClientConv1;
ddeClientItem2.DdeItem := '$TWT.Price';

ddeClientItem3.DdeConv := ddeClientConv1;
ddeClientItem3.DdeItem := 'TXF1.Name';
ddeClientItem4.DdeConv := ddeClientConv1;
ddeClientItem4.DdeItem := 'TXF1.Price';
end;

procedure TForm1.DdeClientItemTimeChange(Sender: TObject);
var i: integer;
begin
Label1.Caption := DDEClientItemTime.Text;
Label3.Caption := IntToStr(Length(DDEClientItemTime.Text));
Label8.Caption := '';
for i := 1 to Length(DDEClientItem2.Text) do
begin
Label8.Caption := Label8.Caption ' ' IntToStr(Ord(DDEClientItemTime.Text[i]));
end;
Label8.Caption := Trim(Label8.Caption)
end;

procedure TForm1.ddeClientItem1Change(Sender: TObject);
begin
Edit1.Text := DDEClientItem1.Text;
Label4.Caption := IntToStr(Length(DDEClientItem1.Text));
end;

procedure TForm1.ddeClientItem2Change(Sender: TObject);
var i: integer;
begin
Edit2.Text := ddeClientItem2.Text;
Label5.Caption := IntToStr(Length(DDEClientItem2.Text));

Edit5.Clear;
for i := 1 to Length(DDEClientItem2.Text) do
begin
Edit5.Text := Edit5.Text ' ' IntToStr(Ord(DDEClientItem2.Text[i]));
end;
Edit5.Text := Trim(Edit5.Text);
end;

procedure TForm1.ddeClientItem3Change(Sender: TObject);
begin
Edit3.Text := ddeClientItem3.Text;
Label6.Caption := IntToStr(Length(DDEClientItem3.Text));
end;

procedure TForm1.ddeClientItem4Change(Sender: TObject);
var i: integer;
begin
Edit4.Text := ddeClientItem4.Text;
Label7.Caption := IntToStr(Length(DDEClientItem4.Text));
Edit6.Clear;
for i := 1 to Length(DDEClientItem4.Text) do
begin
Edit6.Text := Edit6.Text ' ' IntToStr(Ord(DDEClientItem4.Text[i]));
end;
end;

[/code]

不知道站友是否有處理的經驗,能開釋一下嗎?
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
編輯記錄
bestlong 重新編輯於 2009-03-20 14:12:34, 註解 無‧
GrandRURU
站務副站長


發表:235
回覆:1655
積分:1753
註冊:2005-06-21

發送簡訊給我
#2 引用回覆 回覆 發表時間:2010-11-30 13:33:27 IP:111.249.xxx.xxx 訂閱
我用BCB6來實作,也是出現相同的內容,哈!
會不會又是UNICODE的問題呢?

不過那些怪怪字好像也是用不到,直接濾掉應該就可以了吧?

===================引 用 bestlong 文 章===================
...43...
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#3 引用回覆 回覆 發表時間:2010-11-30 16:28:40 IP:210.64.xxx.xxx 訂閱
照我的經驗與看法:

結構體(delphi叫record?)裡的一些char[ ]之類的資料在轉入ansistring時,沒用使用AnsiString(char*,n) 這個方法建構,而使用一般的AnsiString(char*), 就會導致有"垃圾"。 應該不是送來的資料本身有怪字,而是指到自己程式裡的額外的記憶體。所以才有會 tedit? 什麼之類的…還有規則的 垂垂… 應該都是指到不該指的位置造成。

是在server端的處理時可能就轉不正確造成。
------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
mephise
高階會員


發表:4
回覆:149
積分:205
註冊:2004-02-09

發送簡訊給我
#4 引用回覆 回覆 發表時間:2011-01-06 08:50:35 IP:60.250.xxx.xxx 訂閱
應該不是 Unicode 的問題, 因為畫面顯示一半是正常, 一半是亂碼
如果傳送過來的資料是 Unicode, 而你沒有用 UTF8Decode 函數來處理 (我猜您是用D7或是更早的版本來寫的)
那麼整行都會是亂碼, 不會一半對, 一半不對
所以猜測是 Server 端送來的訊息有問題, 直接切掉就好

------
Mephise Chen
前興德工程師
GrandRURU
站務副站長


發表:235
回覆:1655
積分:1753
註冊:2005-06-21

發送簡訊給我
#5 引用回覆 回覆 發表時間:2014-05-23 15:50:53 IP:59.120.xxx.xxx 訂閱
過了快四年才找到解……

底下是我使用 Delphi 2009 測出來不會有亂碼的程式片斷:

var myAnsiPChar: PAnsiChar;
begin
myAnsiPChar := DdeClientConv1.RequestData($TWT.Name);
Caption := string(myAnsiPChar);


或許 DdeClientItem 有什麼不為人知的 Bug 吧。

希望這回覆不會太晚...Orz
編輯記錄
GrandRURU 重新編輯於 2014-05-23 16:28:27, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#6 引用回覆 回覆 發表時間:2014-05-24 23:02:03 IP:114.42.xxx.xxx 訂閱

以下是放在我部落格裡的備份
http://aftcast.pixnet.net/blog/post/32351223

RuRu再度燃起追其真正的原因,經三小時以上的研究,終於解開迷題了!!!! 果與我最開始的猜測有相關性 (之前的回覆)
原因:
TDDEClientItem的Text屬性在某些情形下會得到亂碼加在資料的後面。原因追到VCL源碼去後發現,Text的值與下面的函式有關:
procedure TDdeCliItem.StoreData(DdeDat: HDDEData);
var
Len: Longint;
Data: string;
I: Integer;
begin
if DdeDat = 0 then
begin
RefreshData;
Exit;
end;
Data := PChar(AccessData(DdeDat, @Len)); //這裡就是問題的所在!
if Data <> '' then
begin
FCtrl.Lines.Text := Data;
ReleaseData(DdeDat);
if FCliConv.FormatChars = False then
begin
for I := 1 to Length(Data) do
if (Data[I] > #0) and (Data[I] < ' ') then Data[I] := ' ';
FCtrl.Lines.Text := Data;
end;
end;
DataChange;
end;
問題在Data := PChar(AccessData(DdeDat, @Len)); 這行上。其中,AccessData這個函式又叫了Win32API的DdeAccessData(DdeDat, pDataLen); 這個api回傳的是一個指向Byte型別的指標。經強轉為PChar (指向Char)的指標後,再透過內delphi的內建的AnsiString建構式把PChar指的字串變成AnsiString。至此,感覺好像沒錯… 但深入的思考,若傳回的指標的結尾null 值放在不正確的位置上,就會造成附加一些亂碼出現。然而,有可能null值的位置錯了嗎? 因為值的來源是dde server上傳來的,問題追入dde server 相關的api上。經了解,它與win32api裡的DdeCreateDataHandle 這個函式有關。查msdn得:
HDDEDATA WINAPI DdeCreateDataHandle(
_In_ DWORD idInst,
_In_opt_ LPBYTE pSrc,
_In_ DWORD cb,
_In_ DWORD cbOff,
_In_opt_ HSZ hszItem,
_In_ UINT wFmt,
_In_ UINT afCmd
);
其中,第三個參數,是關鍵,它描述出要填入內容的長度為何。仔細再看第三參數的說明:
The amount of memory, in bytes, to copy from the buffer pointed to by pSrc. (include the terminating NULL, if the data is a string).
哇重點來了啦! 有沒有,當server把資料傳入時,若是傳字串,長度要「自己加1」因為要含null這個結尾。
於是假設寫server的那個人,忘了這件事… 我覺得這種事常常會發生的。那麼就會造成null值沒被放入記憶區塊。舉例來說,若要放入 ABC這三個字在memory上。假設該memory上目前的值是一堆亂的東西,比如說長這樣:
31 32 33 34 35 36 37 38 00 31 32,(十六進位),若當它字串來看是 '12345678'
當把ABC放入 (其實是copy上去)那這區塊會變成
甲) 41 42 43 34 35 36 37 38 00 31 32 (當字串來看是ABC45678,若指定3的長度)
或是
乙) 41 42 43 00 35 36 37 38 00 31 32 (當字來看就是ABC,若指定4的長度,因null值有進去啦)
到目前為止,似乎命運操在寫server 程式人的手上,但沒那麼悲觀。再回首看上面講的DdeAccessData(DdeDat, pDataLen);這個api。第二個參數是那道光! 它指示出資料本身是多長,它呼應了DdeCreateDataHandle的第三個參數!! 假使,以剛的例子來說,server的實作是把ABC用長度3來copy進記憶區塊中,那麼這個pDataLen就會是3。有這個值就有救了!
跳離一下上面的論述。因朋友提到他用TDdeClientConv.RequestData的方法取值就正確! 於是我追入源碼看:
function TDdeClientConv.RequestData(const Item: string): PChar;
var
hData: HDDEData;
ddeRslt: LongInt;
hItem: HSZ;
pData: Pointer;
Len: Integer;
begin
Result := nil;
if (FConv = 0) or FWaitStat then Exit;
hItem := DdeCreateStringHandle(ddeMgr.DdeInstId, PChar(Item), CP_WINANSI);
if hItem <> 0 then
begin
hData := DdeClientTransaction(nil, 0, FConv, hItem, FDdeFmt,
XTYP_REQUEST, 10000, @ddeRslt);
DdeFreeStringHandle(ddeMgr.DdeInstId, hItem);
if hData <> 0 then
try
pData := DdeAccessData(hData, @Len); // 從這裡開始看
if pData <> nil then
try
Result := StrAlloc(Len 1); // 哇,有聰明,會用len 1
Move(pData^, Result^, len); // data is binary, may contain nulls
Result[len] := #0; // 自己在最後面塞 null 值,讚啦!
finally
DdeUnaccessData(hData);
end;
finally
DdeFreeDataHandle(hData); // 到這裡結束關鍵
end;
end;
end;
是不是? 更加驗證了我開始的推論!! 同樣都是在取dde server送來的資料,竟不同的人員(絕對是不同的borland工程師,寫法就是不同)用不同的寫法。RequestData方法的實作是很嚴僅的!! 它考慮了server 的實作人員可能會忘了把null的長度也算進去啦! 但TDdeCliItem.StoreData的實作人員就不再乎,他認為那是寫server的人員自己該注意的? 事實上,若你DDE server是用delphi寫的,嘿~~ borland的人員是有加null的長度的! 類似如下:
hszCmd := DdeCreateDataHandle(ddeMgr.DdeInstId, Cmd, StrLen(Cmd) 1,
0, 0, FDdeFmt, 0);
所以,用delphi寫的DDE server來驗本題目,是完全不會錯! 這可能也是實作TDdeCliItem.StoreData函式的人員測式的時候總不會發現問題,於是就這樣不嚴僅下去…
解法:
了解以上的來龍去脈後,解就不難,我修正如下:
procedure TDdeCliItem.StoreData(DdeDat: HDDEData);
var
Len: Longint;
Data: string;
I: Integer;
begin
if DdeDat = 0 then
begin
RefreshData;
Exit;
end;
Data := PChar(AccessData(DdeDat, @Len)); //這裡就是問題的所在!
if Data <> '' then
begin
SetLength(Data, Len); //加入這行應該就可以搞定! 我沒delphi可測。
FCtrl.Lines.Text := Data;
至於改好後,要怎麼來變更delphi的library ? 這又是另一個題目…我認為應該是將ddeman.pas檔照上面改好後,在delphi裡,用install component的功能,選ddeman.pas檔,然後compile,然後會出錯,但沒關係,會得到ddeman.dcu,然後把這個檔去蓋delphi安裝目錄下的那個。請自行試看看。因我沒有delphi,我都是用c builder,故無法驗證後再保證沒錯啦! 請見諒!
結論:
不知這樣的結果算不算是bug? 假使寫server的人,用vc 寫,也注意到要含null一起copy到記憶體區塊,那就不會出事。但,最佳的解法是依DdeAccessData(hData, @Len); 中的len來決定一個正確的字串,而不是依null值的所在位置。(因null值可能是在正確字串後的任何一個地方出現的)。我會找時間去Embarcadero report這個bug?,希望在未來的版本得到修正!

後記:
yeswin這程式的人員,肯定就是我說的沒注意到長度要包含null,才會造成這樣的情形。假使他有注意,那應該也不會出現亂碼附在後面了!

===================引 用 GrandRURU 文 章===================

或許 DdeClientItem 有什麼不為人知的 Bug 吧。

------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2014-05-24 23:04:28, 註解 無‧
aftcast 重新編輯於 2014-05-24 23:13:47, 註解 無‧
herbert2
尊榮會員


發表:58
回覆:632
積分:878
註冊:2004-04-16

發送簡訊給我
#7 引用回覆 回覆 發表時間:2014-05-25 01:59:27 IP:202.39.xxx.xxx 訂閱

===================引 用 aftcast 文 章===================
....
RuRu再度燃起追其真正的原因,經三小時以上的研究,終於解開迷題了!!!! 果與我最開始的猜測有相關性 (之前的回覆)
....
結論:
不知這樣的結果算不算是bug?

後記:
yeswin這程式的人員,肯定就是我說的沒注意到長度要包含null,才會造成這樣的情形。假使他有注意,那應該也不會出現亂碼附在後面了!


如果 Embarcadero 的團隊有人肯用心 debug,新加坡來的業務經理就不會這麼難為了!

這些深入核心的動作,還是蕭大俠功夫深。
我只能簡單的 trace 一下 if else,但就看到 TCustomGrid 配合 Runtime Themes 新功能時,
有部分變數用錯了,還有些 TWincontrol VCL 元件應該也有類似 bug;
若是自創新元件,還勉強可應付局部修改與擴增,但那麼底層的 bug,真不知要從何改起呀!

網路找到的一些討論:

http://codeverge.com/embarcadero.delphi.non-tech/vcl-bug-affects-many-vcl-componen/1073429
VCL bug affects many VCL components 2009/12/17
GrandRURU
站務副站長


發表:235
回覆:1655
積分:1753
註冊:2005-06-21

發送簡訊給我
#8 引用回覆 回覆 發表時間:2014-05-25 06:54:41 IP:1.163.xxx.xxx 訂閱
蕭大俠是正解無誤!實在太強大!
===

因為 TDdeCliItem 放在 implementation

建議還是直接 Copy Add Edit DdeMan.pas 到有使用 Dde 元件的專案裡

這樣專案會優先使用專案裡同名的檔案


等哪一天 EMBT 心情好,在某一版 XXXE 修了這個 Bug 後,再刪掉吧


提外話:
目前 富邦e01 也會有這樣的問題,可能是券商的工程師跳槽過去的吧 XD

話說, Debug 的時間快和開發時間比多了,換了新版情況也好不到哪裡去,
所以,按上述的推理,我很認真的懷疑現在在 EMBT 的 RD,或許是 M$ 派過去的 SPY....

===================引 用 aftcast 文 章===================

以下是放在我部落格裡的備份
http://aftcast.pixnet.net/blog/post/32351223

...43...
編輯記錄
GrandRURU 重新編輯於 2014-05-25 06:55:42, 註解 無‧
GrandRURU 重新編輯於 2014-05-25 06:59:02, 註解 無‧
系統時間:2017-12-14 16:04:48
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!