Delphi之東進模擬語音卡(D160A)可復用源碼
網友急需,先開源吧,文檔過幾天慢慢補。
作者:成曉旭
設計簡介:
1、 將卡、通道分別單獨進行設計與封裝。
2、 所有的外部操作接口都封裝在卡類這一類。
3、 在我的項目中,在卡類這一級還增加了適配器或者代理,分別實現了Adapter或Proxy模式;以盡可能地解耦卡設備的實現細節與具體應用業務之間的關系。因為,我們的系統中使用了幾家不同的卡設備,另一方面,這些卡設備,在不同的軟件系統中,又有不同的業務應用需求。
4、 當然,卡這一級,也可以實現一個統一的接口,這樣對外部可以表現出相對統一的行為,以方便業務層代碼的調用,比如說:在數據采集的應用中,統一的接口可以讓采集控制層不必依賴於具體的采集設備和通信方式,可以一致地實現數據收發,不管通信方式是RS232、RS485、TCP/IP、PSTN,還是別的方式或者通信設備。
5、 在通道設計中,核心的就是一個“狀態機模式”,通過輪巡通道狀態來管理硬件卡設備,並且,還自己設計了一個業務級的“業務狀態機”,來抽象業務方面需要關心的“業務狀態”,通過增加“業務狀態機”這樣一個中間層,以解耦業務狀態與設備狀態之間的依賴。(這一點,在我看到的所有卡廠商提供的各類Demo程序裏面都沒有這樣做,這也無形中誤導了很多的開發人員,我看到的所有應用軟件開發的源碼都是:設備細節、尤其是通道狀態,與業務邏輯代碼緊緊地耦合在一起,難解難分)。
6、 此設計的另一個亮點是:IoC模式的應用(2004年自己在設計此類時還不知道這個概念,全憑自己的經驗總結出這樣的設計)。對通道進入“呼入成功”、“呼出成功”等業務狀態的調用代碼從通道類是解耦出來:設計一個接口,在各個業務狀態的處理方法中,再調用接口方法,將具體的業務處理邏輯委托給實現此接口的對象。並且這個接口的實現是通過“依賴註入”實現IoC的。這樣設計,就達到了很好的可復用性和靈活性。
7、 當然,更好的實現可以采用AOP(面向方法編程)的思想或者實現技術,這樣可復用性更好,如此設計,在業務與卡方法的調用之間,耦合度將是最低的。
8、 目前的版本,沒有在代碼中體現接口的實現……
9、 類圖(以後補上):
10、卡類源碼:
//------------------------------------------------------------------------------//// 產品名稱: 成曉旭的個人軟件Delphi源碼庫// 產品版本: CXXSoft delphi code source lib 2.0// 模塊名稱: Delphi之東進模擬語音卡類// 模塊描述: // 單元文件: unDJCard160A.pas // 開發作者: 成曉旭// 備註: 任何人使用此文件時,請保留此段自述文件,謝謝!// 開發時間: 2004-08-03// 修改歷史: // 修改描述://------------------------------------------------------------------------------unit unDJCard160A;interfaceuses Windows, unDJTC08a32,unDJNewSig, unBaseDefine,unDJ160ADefine, unDJChanne160A;type TCXXCommCard160A = class(TObject) private ChannelNumber:Word; channelObject:array of TCXXDJChannel160A; OnCardChannelState:TTrunkStatusEvent; procedure Stop(); procedure ReleaseCommDevice(); function GetChannelObjectOrder(const aChannelID:Word):Word; public constructor Create(const trunkEvent:TTrunkStatusEvent); destructor Destroy(); override; function LoadCommDevice(const loadAll:boolean=false):boolean; function Startup():boolean; function GetAFreeChannel():Word; function GetChannelNumber():Word; function DialPhone(const aChannelID:Word;const DialPhoneNumber:PChar):boolean; function HangUp(const aChannelID:Word):boolean; end;implementation...{ TCXXCommCard160A }constructor TCXXCommCard160A.Create(const trunkEvent:TTrunkStatusEvent);begin ChannelNumber := 0; Self.OnCardChannelState := trunkEvent;end;destructor TCXXCommCard160A.Destroy;var Loop:Word;begin Stop(); if (Length(channelObject) > 0) and (channelNumber > 0) then begin for Loop := 0 to ChannelNumber - 1 do begin if Assigned(channelObject[Loop]) then begin channelObject[Loop].Free(); channelObject[Loop] := nil; end; end; end; ReleaseCommDevice();end;function TCXXCommCard160A.DialPhone(const aChannelID: Word; const DialPhoneNumber: PChar): boolean;var K:Word;begin Result := false; K := GetChannelObjectOrder(aChannelID); if (K <> ErrorTrunkNumber) and (Assigned(channelObject[K])) then begin Result := channelObject[K].DialPhone(DialPhoneNumber); end;end;procedure TCXXCommCard160A.ReleaseCommDevice();begin DisableCard(); FreeDrv();end;function TCXXCommCard160A.GetAFreeChannel(): Word;var Loop:Word;begin Result := ErrorTrunkNumber; for Loop := Low(channelObject) to High(channelObject) do begin if (channelObject[Loop].GetChannelType() = ctEmpty) then continue; if (channelObject[Loop].GetChannelStatus() = atsFree) then begin Result := channelObject[Loop].GetChannelID();break;
end; end;end;function TCXXCommCard160A.GetChannelNumber(): Word;begin Result := channelNumber;end;function TCXXCommCard160A.GetChannelObjectOrder( const aChannelID: Word): Word;var Loop:Word;begin Result := ErrorTrunkNumber; for Loop := Low(channelObject) to High(channelObject) do begin if (channelObject[Loop].GetChannelID = aChannelID) then begin Result := Loop;break;
end; end;end;function TCXXCommCard160A.HangUp(const aChannelID: Word): boolean;var K:Word;begin Result := false; K := GetChannelObjectOrder(aChannelID); if (K <> ErrorTrunkNumber) and (Assigned(channelObject[K])) then begin channelObject[K].ChannelHangUp(); Result := true; end;end;function TCXXCommCard160A.LoadCommDevice(const loadAll:boolean): boolean;const loadEmpty = true;var Loop,tempNumber:Word; isFlag:LongInt; function CheckLoadTrunk():boolean; begin Result := loadAll or ((NOT loadAll) and(TChannelType(CheckChType(Loop)) <> ctEmpty)); end;begin isFlag := LoadDRV(); Result := (isFlag=0); if NOT Result then Exit; tempNumber := CheckValidCh(); Result := EnableCard(tempNumber,1024*8)=0; if NOT Result then begin FreeDrv(); Exit; end; Result := Sig_Init()=1; if NOT Result then Exit; SetBusyPara(700); SetPackRate(PACK_64KBPS ); channelNumber := tempNumber; SetLength(channelObject,channelNumber); for Loop := 0 to channelNumber - 1 do begin if CheckLoadTrunk() then begin channelObject[Loop] := TCXXDJChannel160A.Create(OnCardChannelState); channelObject[Loop].CreateCommChannel(Loop); end; end;end;function TCXXCommCard160A.Startup(): boolean;var Loop:integer;begin for Loop := 0 to channelNumber - 1 do begin channelObject[Loop].Resume(); end; Result := true;end;procedure TCXXCommCard160A.Stop();var Loop:integer;begin for Loop := 0 to channelNumber - 1 do begin channelObject[Loop].Suspend(); channelObject[Loop].Terminate(); channelObject[Loop] := nil; end;end;end.11、 通道類源碼:
//------------------------------------------------------------------------------//// 產品名稱: 成曉旭的個人軟件Delphi源碼庫// 產品版本: CXXSoft delphi code source lib 2.0// 模塊名稱: Delphi之東進模擬語音卡通道類// 模塊描述: // 單元文件: unDJChanne160A.pas // 開發作者: 成曉旭// 備註: 任何人使用此文件時,請保留此段自述文件,謝謝!// 開發時間: 2004-08-03// 修改歷史: // 修改描述://------------------------------------------------------------------------------unit unDJChanne160A;interfaceuses Windows,Classes,SysUtils, unBaseDefine,unDJ160ADefine, unDJTC08a32,unDJNewSig;Type TCXXDJChannel160A = class(TThread) //TCXXDJChannel160A = class(TObject) private channelType:TChannelType; oldChannelState,channelState:TTrunkState; channelID:Word; phoneNumber:string; dtmfString:string; isConntectd:boolean; isDialOut:boolean; aTrunkState:TTrunkStatus; procedure InformTrunkStatus(const aMsgFlag: TLVOperateFlag); procedure ClearTrunkStatus(); function CheckSigHangup():boolean; function CheckCallIn():boolean; function SwitchOnCallIn():boolean; procedure ProcessCallInSuccess(); procedure ProcessDialSuccess(); procedure ProcessCheckDialSend(); protected procedure Execute(); override; public strMessage:string; OnChannelState:TTrunkStatusEvent; constructor Create(const trunkEvent:TTrunkStatusEvent); destructor Destroy();override; procedure CreateCommChannel(const aChennelID: Word); procedure ChannelProcessor(); function GetChannelID():Word; function GetChannelStatus():TTrunkState; function GetChannelType():TChannelType; function DialPhone(const DialPhoneNumber:PChar):boolean;overload; function DialPhone(const DialPhoneNumber:PChar;const PreDialNumber:PChar):boolean;overload; procedure ChannelHangUp(); function GetDialOut():boolean; end;implementation...{ TCXXDJChannel160A }procedure TCXXDJChannel160A.ChannelHangUp();begin isDialOut := false; StopSigCheck(channelID); HangUp(channelID); Sig_ResetCheck(channelID); StartSigCheck(channelID); InitDTMFBuf(channelID); ClearTrunkStatus(); InformTrunkStatus(lvofUpdate);end;procedure TCXXDJChannel160A.ChannelProcessor();var dState:Word;begin PUSH_PLAY(); FeedSigFunc(); CheckCallIn(); case channelState of atsFree: begin // end; atsCallIning: begin SwitchOnCallIn(); end; atsCallInSuccess: begin if CheckSigHangup() then Exit; ProcessCallInSuccess(); end; atsCheckSendDial: begin ProcessCheckDialSend(); end; atsDialing: begin dState := Sig_CheckDial(channelID); case dState of // S_NORESULT: S_CONNECT: begin channelState := atsDialSuccess; isConntectd := true; end; S_BUSY, S_NOBODY, S_NODIALSIG, S_NOSIGNAL: begin channelState := atsHangOff; end; end; strMessage := '撥號中...'; end; atsDialSuccess: begin if CheckSigHangup() then Exit; ProcessDialSuccess(); strMessage := '撥號成功'; end; atsHangOff: begin ChannelHangUp(); end; end; if (oldChannelState <> channelState) then begin oldChannelState := channelState; InformTrunkStatus(lvofUpdate); end;end;function TCXXDJChannel160A.CheckCallIn(): boolean;begin Result := RingDetect(channelID); if Result then begin OffHook(channelID); if isDialOut then channelState := atsDialSuccess else channelState := atsCallIning; end;end;function TCXXDJChannel160A.CheckSigHangup(): boolean;begin Result := false; if (Sig_CheckBusy(channelID)=1) then begin strMessage := '對方已掛機'; InformTrunkStatus(lvofUpdate); StopPlayFile(channelID); channelState := atsHangOff; Result := true; end;end;procedure TCXXDJChannel160A.ClearTrunkStatus();begin channelState := atsFree; oldChannelState := channelState; phoneNumber := ''; dtmfString := ''; strMessage := ''; isConntectd := false;end;constructor TCXXDJChannel160A.Create(const trunkEvent:TTrunkStatusEvent);begin Self.OnChannelState := trunkEvent; Self.FreeOnTerminate := true; inherited Create(true);end;destructor TCXXDJChannel160A.Destroy;begin Suspend(); ChannelHangUp(); //inherited Destroy();end;function TCXXDJChannel160A.DialPhone(const DialPhoneNumber:PChar; const PreDialNumber:PChar): boolean;begin isDialOut := true; phoneNumber := DialPhoneNumber; OffHook(channelID); InitDTMFBuf(channelID); Result := Sig_StartDial(channelID,DialPhoneNumber,PreDialNumber,0)=1; channelState := atsCheckSendDial;end;function TCXXDJChannel160A.DialPhone( const DialPhoneNumber: PChar): boolean;begin Result := DialPhone(DialPhoneNumber,'');end;procedure TCXXDJChannel160A.Execute;begin while NOT Terminated do begin Synchronize(ChannelProcessor); Sleep(10); end;end;function TCXXDJChannel160A.GetChannelID(): Word;begin Result := channelID;end;function TCXXDJChannel160A.GetChannelStatus(): TTrunkState;begin Result := channelState;end;procedure TCXXDJChannel160A.InformTrunkStatus(const aMsgFlag: TLVOperateFlag);begin aTrunkState.lvFlag := aMsgFlag; aTrunkState.TrunkID := IntToStr(channelID); aTrunkState.TrunkType := channelType; aTrunkState.TrunkTypeStr := ChannelTypeString[channelType]; aTrunkState.TrunkStep := channelState; aTrunkState.TrunkStepStr := TrunkStateString[channelState]; aTrunkState.TrunkPhone := phoneNumber; aTrunkState.TrunkData := dtmfString; OnChannelState(aTrunkState);end;procedure TCXXDJChannel160A.ProcessCallInSuccess();beginend;function TCXXDJChannel160A.SwitchOnCallIn(): boolean;begin OffHook(channelID); InitDTMFBuf(channelID); StartSigCheck(channelID); channelState := atsCallInSuccess; Result := true;end;procedure TCXXDJChannel160A.ProcessDialSuccess();beginend;procedure TCXXDJChannel160A.CreateCommChannel(const aChennelID: Word);begin channelID := aChennelID; channelType := TChannelType(CheckChType(channelID)); ClearTrunkStatus(); InformTrunkStatus(lvofAdd);end;function TCXXDJChannel160A.GetChannelType(): TChannelType;begin Result := channelType;end;function TCXXDJChannel160A.GetDialOut(): boolean;begin Result := isDialOut;end;procedure TCXXDJChannel160A.ProcessCheckDialSend();begin if(CheckSendEnd(channelID) = 1) then begin StartSigCheck(channelID); channelState := atsDialing; end;end;end.