关键字:VC++, SQL Server,UDP,扩展存储过程
作者:邓杨均
EMail: dyj057@gmail.com
当数据库更新的时候,如果插入,删除,修改了数据记录,我们的应用程序需要知道这
些记录,就需要使用触发器,触发器再调用扩展存储过程,扩展存储过程再把数据使用
UDP的通讯方式,发送到应用程序,这样应用程序就会收到数据库更新的通知。
1. 创建扩展存储过程的工程
启动Visual Studio 2003 -> 新建Visual C++项目 -> 扩展存储过程DLL
2.在生成的向导中,输入你要创建的扩展存储过程的名称,这个很重要,一开始就要认真填写,
否则工程创建完成后,就不好修改了.在我们例子里面就输入xp_door_web_site
3.读取存储过程的参数,然后形成数据报文,再发送出去,该示例存储过程有4个参数.有varchar,int等数据类型.
先来看看主要的处理流程:
RETCODE __declspec(dllexport) xp_door_web_site(SRV_PROC *srvproc)
{
XPLib lib(srvproc); //Create tool class
try
{
DWSEvent newEvent; // Create a new event
//Check paramerter's number
if (srv_rpcparams(srvproc) != 4)
{
// Send usage message to client and return error
lib.printUsage("xp_door_web_site <UserId>,<Url>,<Message>,<type>");
return (XP_ERROR);
}
lib.readVarcharPara(1,&newEvent.m_strUserId[0], (int)newEvent.m_strUserId.capacity(), false,"UserID");
lib.readVarcharPara(2,&newEvent.m_strUrl[0], (int)newEvent.m_strUrl.capacity(), false,"Url");
lib.readVarcharPara(3,&newEvent.m_strMsg[0], (int)newEvent.m_strMsg.capacity(), false,"Msg");
lib.readIntPara(4,newEvent.m_nType, false,"Type");
newEvent.SetConfigFile("C:\\XPdoorwebsite.config.ini");
newEvent.Cast();
}
catch(CExceptionEx & me)
{
lib.printError( me.what() );
return XP_ERROR;
}
catch(...)
{
lib.printError("Unkowned exception occured");
return XP_ERROR;
}
return XP_NOERROR ;
}
代码都很容易理解,也很简单,就不多言
4. XPLib类库是我自己写的,是编写扩展存储过程主要的可复用的类,它的职能是读取存储过程的参数,和打印错误和使用信息.
XPlib.h的声明如下:
#pragma once
#include <string>
using namespace std;
namespace IBMS
{
namespace Data
{
class XPLib
{
public:
XPLib(SRV_PROC *pSrvProc);
~XPLib(void);
static const int MAX_SERVER_ERROR = 20000;
static const int XP_SEND_TO_IBMS_ERROR = MAX_SERVER_ERROR + 1;
public:
void printUsage (const char * usage);
void printError (const char * errroMsg);
void readIntPara( int nParaIndex, int &iValue, bool bAllowNull = true, string paraName="");
void readVarcharPara(int nParaIndex, char * szValue, int nMaxLength=-1, bool bAllowNull = true,string paraName="");
private:
SRV_PROC * m_pSrvProc;
};
}
}
XPlib.cpp的实现如下:
#include "StdAfx.h"
#include ".\xplib.h"
#include <cassert>
#include <sstream>
using namespace std;
#include "moreexception.h"
using namespace IBMS::StdLib;
namespace IBMS
{
namespace Data
{
/****************************************************************************
*Class:
XPLib
*Function:
XPLib(SRV_PROC *pSrvProc)
*Purpose:
construct the class
*Parameter:
[in]pSrvProc
*Return:
****************************************************************************/
XPLib::XPLib(SRV_PROC *pSrvProc)
:m_pSrvProc(pSrvProc)
{
assert(m_pSrvProc!=NULL);
}
/****************************************************************************
*Class:
XPLib
*Function:
~XPLib(void)
*Purpose:
destroy the class
*Parameter:
*Return:
****************************************************************************/
XPLib::~XPLib(void)
{
}
/****************************************************************************
*Class:
XPLib
*Function:
void printError (SRV_PROC *pSrvProc, CHAR* szErrorMsg)
*Purpose:
send error message to Client
*Parameter:
[in]errroMsg
error message
*Return:
****************************************************************************/
void XPLib::printError (const char * errroMsg)
{
srv_sendmsg(m_pSrvProc, SRV_MSG_ERROR, XP_SEND_TO_IBMS_ERROR, SRV_INFO, 1,
NULL, 0, (DBUSMALLINT) __LINE__,
const_cast<char *>(errroMsg),
SRV_NULLTERM);
srv_senddone(m_pSrvProc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
}
/****************************************************************************
*Class:
XPLib
*Function:
void printUsage (SRV_PROC *pSrvProc)
*Purpose:
send XP usage info to client
*Parameter:
[in]usage
usage of the extended procedure
*Return:
****************************************************************************/
void XPLib::printUsage (const char * usage)
{
srv_sendmsg(m_pSrvProc, SRV_MSG_ERROR, XP_SEND_TO_IBMS_ERROR, SRV_INFO, 1,
NULL, 0, (DBUSMALLINT) __LINE__,
const_cast<char *>(usage),
SRV_NULLTERM);
srv_senddone(m_pSrvProc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
}
/****************************************************************************
*Class:
XPLib
*Function:
void XPLib::readIntPara(int nParaIndex, int & iValue,bool bAllowNull)
*Purpose:
Read int type parameter
*Parameter:
[in]paraIndex
the Index of the parameter
[out]value
value of the parameter
[in]bAllowNull
wether the parameter is allowed null
Default:true
[in]string paraName
Default:empty string
*Return:
none
****************************************************************************/
void XPLib::readIntPara( int nParaIndex, int &iValue, bool bAllowNull , string paraName/*=""*/)
{
if( paraName.empty())
{
paraName = "参数" + nParaIndex;
}
if( nParaIndex < 1 )
{
THROW_EX( paraName + "的参数应该是从1开始的整数" );
}
// Make sure parameter is a INPUT parameter
if (srv_paramstatus(m_pSrvProc, nParaIndex) == -1)
{
THROW_EX( paraName + "应该是一个输入类型的参数" );
}
BOOL bfNULL; // Whether parameter is null
BYTE pbType[100]={0}; // Type of Parameter
ULONG ulMaxLen = 256; // Max Length of Parameter
ULONG ulActualLen; // Actual Length of Paramter
if ( srv_paraminfo(m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen, NULL, &bfNULL) != SUCCEED )
{
THROW_EX( "读取" + paraName +"失败" );
}
//Check datatype
if(pbType[0] != 0x26) //INT Type
{
stringstream ss;
ss << paraName <<" 不是一个int的数据类型。类型值为:"
<< static_cast<int>(pbType[0])<<endl;
THROW_EX(ss.str());
}
//Check data length must =< 9
if( ulActualLen > 9 )
{
stringstream ss;
ss << paraName <<" 数据类型太长了,对于一个整形数据来说。";
THROW_EX(ss.str());
}
//Read value
if(srv_paraminfo( m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen,
reinterpret_cast<BYTE *>( &iValue ), &bfNULL) != SUCCEED )
{
stringstream ss;
ss << paraName << "读取数据值失败。";
THROW_EX(ss.str());
}
}
/****************************************************************************
*Class:
XPLib
*Function:
void XPLib::readVarcharPara(int nParaIndex, CHAR* szValue, bool bAllowNull)
*Purpose:
Read Varchar type parameter
*Parameter:
[in]paraIndex
the Index of the parameter
[out]szValue
value of the parameter
[in]nStringLength
maxLength of the value string
Default:-1
if it's -1, not check it
[in]bAllowNull
wether the parameter is allowed null
Default:true
[in]paraName
name of parameter
Default:empty string
*Return:
****************************************************************************/
void XPLib::readVarcharPara(int nParaIndex, char * szValue, int nMaxLength /*=-1*/ ,
bool bAllowNull /*= true*/,string paraName/*=""*/)
{
if(paraName.empty())
{
paraName = "参数" + nParaIndex;
}
assert( nParaIndex > 0 );
// Make sure parameter is a INPUT parameter
if (srv_paramstatus(m_pSrvProc, nParaIndex) == -1)
{
THROW_EX( paraName + "应该是一个输入类型的参数" );
}
BOOL bfNULL; // Whether parameter is null
BYTE pbType[100]={0}; // Type of Parameter
ULONG ulMaxLen = 256; // Max Length of Parameter
ULONG ulActualLen; // Actual Length of Paramter
if ( srv_paraminfo(m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen, NULL, &bfNULL) != SUCCEED )
{
THROW_EX( "读取" + paraName +"失败" );
}
//check Length
if( ulActualLen > (UINT) nMaxLength )
{
stringstream ss;
ss << paraName << "长度超过该字段允许的最大长度" << nMaxLength ;
THROW_EX(ss.str());
}
//Check null allowed
if( !bAllowNull && bfNULL)
{
stringstream ss;
ss << paraName << "不能为空" ;
THROW_EX(ss.str());
}
//Check datatype
if( pbType[0] != 167 ) //Varchar data type
{
stringstream ss;
ss << paraName << "不是varchar数据类型。类型值为:"
<< static_cast<int>(pbType[0])<<endl;
THROW_EX(ss.str());
}
//Read value
if(srv_paraminfo( m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen,
reinterpret_cast<BYTE *>(szValue), &bfNULL) != SUCCEED )
{
stringstream ss;
ss << "读取" << paraName <<"失败";
THROW_EX(ss.str());
}
}
}
}
5.生成UDP报文,发送到应用程序
这里的任务就很简单了,读取配置文件的信息"C:\\XPdoorwebsite.config.ini",然后根
据配置文件中的IP地址和端口发送应用程序,注意在写UDP通讯程序时,不要再加入Sock
et API DLL,因为SQL Server中已经引用了该DLL,更不要释放该DLL,那样会导致SQL
Server的数据库停止.而且要特别的小心,不能发生程序错误,因为扩展存储过程在SQ
L Server的进程空间执行,所以要捕获所有异常.就像我们做的
catch(...)
{
lib.printError("Unkowned exception occured");
return XP_ERROR;
}
下面给出UDP初始化和发送数据的代码:
void RowInsertEvent::CreateUdpSocket()
{
if(( m_socket = socket(AF_INET, SOCK_DGRAM, 0)) == SOCKET_ERROR)
{
THROW_BY_ERROR_CODE(GetLastError())
m_socket=NULL;
}
}
void RowInsertEvent::UnicastSend(const char *text, const char *ip, int port)
{
struct hostent *host;
SOCKADDR_IN addr;
int bytesSent = 0;
if( m_socket == NULL)
{
CreateUdpSocket();
}
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned short) port);
addr.sin_addr.s_addr = inet_addr(ip);
if(addr.sin_addr.s_addr == INADDR_NONE)
{
host = NULL;
host = gethostbyname(ip);
if(host == NULL)
{
THROW_BY_ERROR_CODE( WSAGetLastError() );
}
memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length);
}
if((bytesSent = sendto( m_socket, text, static_cast<int>(strlen(text)), 0, (struct sockaddr*) &addr,
sizeof(addr))) == SOCKET_ERROR)
{
THROW_BY_ERROR_CODE( WSAGetLastError() );
}
}
6.当这一切都完成了, 就是编译该工程,发布的时候,一定要选择使用在静态库中使用MFC
然后复制该动态连接库 XPDoorWebSite.dll 复制到 SQL Server 的\Binn 目录中或者System32目录下
从 Visual Studio 数据项目添加新的扩展存储过程,
方法是使用 SQL Server 企业管理器,或者执行如下
use master
go
SQL 命令:
sp_addextendedproc 'xp_door_web_site', 'XPDoorWebSite.dll'
7.测试该存储过程:
use master
go
xp_door_web_site 'dyj','http://dyj.com.cn','Hello',1
记住一定用单引号,不能使用双引号
如果一些正常,提示出'命令成功完成'
8.构建配置文件,在本例子中配置文件为C:\\XPdoorwebsite.config.ini,里面包含了通
讯的配置一个简单的示例:
[Client]
IP=192.9.207.145
Port=10006
9.在其他的事务性质的数据库中创建触发器,在触发器中,使用该存储过程.
在本例中:
在lkgov的数据库上运行SQL语句:
use lkgov
go
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'msg_insert_trigger' AND type = 'TR')
DROP TRIGGER msg_insert_trigger
go
create trigger msg_insert_trigger on msg
for insert
as
begin
declare @type int
declare @userid varchar(12)
declare @msg varchar(500)
declare @url varchar(500)
select
@userid = userid,
@msg= msg,
@url =url,
@type = type
from inserted
exec master.dbo.xp_door_web_site @userid,@url,@msg,@type
end
这样每次在表msg中插入数据的时汗存储过程都会调用xp_door_web_site
总的说来过程是比较繁琐,但也没有什么高深的东西,熟练掌握后,就能很容易的使用了.
期待在SQL Server 2005使用C# 等托管代码写扩展的存储过程..
^0^ Good Luck
2005-6-20