Delphi之东进模拟语音卡(D160A)可复用源码
作者:成晓旭
Blog:http://blog.youkuaiyun.com/cxxsoft
(声明:欢迎转载,请保证文章的完整性)
设计简介:
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;

interface
uses
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;

interface

uses
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();
begin

end;

function TCXXDJChannel160A.SwitchOnCallIn(): boolean;
begin
OffHook(channelID);
InitDTMFBuf(channelID);
StartSigCheck(channelID);
channelState :
=
atsCallInSuccess;
Result :
=
true
;
end;

procedure TCXXDJChannel160A.ProcessDialSuccess();
begin

end;

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.
Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1108211