MYSQL的简单封装,学习基于托管的C++开发
又折腾了半天,弄明白了一些事情,一言难尽。
1、 背景。
最近在写一点尝试性的代码,需要做一个简单的原型来验证我的思路是否正确。这个原型由数据库(mysql)和前台界面构成。
Mysql的表/测试数据已经建好。
前台界面我不小心用了C++,鬼使神差的选择了CLR界面。(昨天折腾了半天总算搞明白CLR的指针^,调通了VC调用mysql。中间还有解决字符集的问题。)
今天本来想用回C#算了。但是略有点不甘心,遂重新写一遍看看托管的C++代码有啥不一样,于是重写昨天的调用mysql的代码,尝试进行封装一下。
2、 遇到的问题。
传统的sql调用来说,一般是bind输出变量,执行sql语句,使用输出变量。
例如,我设想中的调用是这样的
String^ myStringResult1;
Int myIntResult1;
Double myDoubleResult1;
Mysql_bind_var(1,StringType,myStringResult1);
Mysql_bind_var(2,IntType,&myIntResult1);
Mysql_bind_var(3,DoubleType,&myDoubleResult1);
Mysql_select(3,” SELECT ‘hello,this is string’,12,12.345 “);
//接下来myStringResult1就等于’hello,this is string’,
// myIntResult1就等于12,myDoubleResult1就等于12.345
恩,很自然的,封装一个mysql的操作类
public ref class tsql{
ConnectDB(xxx)
DisConnectDB(xxx)
BindVar(xxx);
Select(xxx)…
}
实现BindVar的时候,最简单的就是做个数组,把绑定的变量登记进去,然后再Select实现的时候,予以实例化和赋值。
非托管代码实现时,很简单
void *m_pArray[MAX_VARS];
void mysql_bind_var(int a,int b,void *pv)
{
M_pArray[a]=pv;
}
void mysql_select(int a,char *s)
{
*(int*)(m_pArray[0])=12;
*(double*)(m_pArray[1])=12.34;
Strcpy((char*)(m_pArray[2]),”helloxxx”);
}
哦也,就是bind的时候记录一个地址,select的时候直接把取得的结果放进去好了。
那么用托管代码能否实现呢?折腾了半天的结果是不能。
这个问题归纳起来,就是说托管的变量,能否在除堆栈传参以外的地方被实例化,也即不能有指向托管指针的指针。(托管变量可以由托管指针初始化,托管指针不能被记录再其生命周期之外)
即:
例1:正常gcnew实例化
void Test001()
{
String^ foo;
foo = gcnew String(“hello”); // 正确!foo=“hello”,正常实例化
}
例2:在函数调用中被实例化
void Test002_sub(String ^% pString)
{
pString =gcnew String(“hello”);
}
void Test002()
{
String^foo;
Test002_sub(foo); // 正确!foo=“hello”,在堆栈传参中被实例化。
}
例3:在除堆栈传参以外的地方被实例化。
void Test003_setPointer(String ^% pString,RecordVar^%pRecord)
{
// 在pRecord中记录下pString的地址
}
void Test003_InitPointer(RecordVar ^% pRecord)
{
// pRecord的pString初始化并赋值。
}
void Test003()
{
String^foo;
Test003_setPointer(foo,record); // 没有办法做一个record能够记录 foo地址
Test003_InitPointer(record); // 然后直接用record来初始化foo
//满怀希望的认为foo此时可用
}
我尝试了各种方法,包括System::Object打包,interior_ptr等等,都不行。
后来忽然想明白了,就是因为这个不能保证安全,当然被托管代码所不容许。
3、 原因:
如果允许记录托管指针的指针,那么就意味着允许托管指针有别名。当托管指针失效的时候,托管指针的指针应当如何自处呢?
如果允许指向托管指针的指针,那么则该托管指针不应当是栈上对象,而应该是托管堆中的对象。不然的话,指向托管指针的指针指向的内容将是可能被迫是非法内容(当程序运行到该托管指针的作用域范围外时,该托管指针将失效,指向该指针的指针也要失效)。
这解释了为什么不能有指向托管指针的指针,也解释了为什么interior_ptr只能在堆栈上。
因为interior_ptr生命周期应当与其程序执行的作用域相等,为确保它的生命周期与其程序执行的作用域相等,它也不能被赋值给别的变量。
换一句话说,就是托管模式下,一个变量不能被可能不在其生命周期之外的地方所改变。(太拗口了,就算我一开始明白这个也会被绕进去)
4、 托管模式的代码:
出于安全考虑,禁止了托管指针的指针。
So,这样最简单的,就是不要让调用者的托管变量离开“调用者”的视野。
改成了这样的模式:
OutParamo1,o2,o3;
Int iResult1;
String^sResult1;
Double dResult1;
Sql_select(“select xx ,xx ,xx from xxx where xxx “, o1,o2,o3);
iResult1 = o1;
sResult1= o2;
dResult1 =o3;
sql_select(String^ sqlStmt,…array<System::Object^>^pOutParams)
{
OutParam ^po;
for(i=0;i<pOutParams->Length;i++)
{
po= (OutParam^)pOutParams[i];
if (i == 1)
po->result= gcnew String(“hello”);
if (i == 2)
po->result= gcnew int(12);
if (i == 3)
po->result= gcnew double(12.34);
}
}
public ref class OutParam
{
public: OutParam(){};
operator int(){
return (int)result;
}
operator System::String^(){
return (System::String^)result;
}
operator double(){
return (double)result;
}
System::Object ^result;
}