Análisis y reparación de dos BUGs en Delphi
Al usar Delphi 7 para el desarrollo de bases de datos de tres niveles, encontré dos pequeños problemas. A través de repetidas pruebas, finalmente descubrí los dos pequeños ERRORES en Delphi 7 y los solucioné (parece que hay los mismos ERRORES en Delphi 6), escribiendo. Este artículo comparte la alegría del éxito con todos. También soy nuevo en Delphi, por lo que debe haber muchas cosas incorrectas en el artículo.
BUG1. Los caracteres chinos se truncan al pasar parámetros:
Cómo reproducir ERROR:
SQL Server 2000 se utiliza en segundo plano y hay una tabla XsHeTong para realizar pruebas. Puede ajustarla según su situación real.
Primero cree un servidor de datos: cree un nuevo proyecto, cree un módulo de datos remoto, coloque un ADOConnection, ADODataSet y DataSetPRovider en él y realice las configuraciones correspondientes. Deje el ComamndText de ADODataSet en blanco y configure poAllowCommandText en su opción en True. Compile y ejecute.
Cree un programa cliente nuevamente: cree un nuevo proyecto, coloque una DCOMConnection en el formulario, conéctese al servidor de datos creado anteriormente, coloque un ClientDataSet, configure su conexión a DCOMConnection aquí y configure su ProviderName al servidor arriba El nombre del Proveedor de conjunto de datos. Finalmente, coloque DataSource y DBGrid y realice las configuraciones correspondientes para ver los resultados, y luego coloque un botón para probar.
Escriba un código similar al siguiente en OnClick de Button (aquí utilicé la tabla XsHeTong y sus dos campos HTH (char 15), GCMC (varchar 100), puede ajustarlo de acuerdo con su situación de prueba real):
con ClientDataSet1 hacer
comenzar
Cerca;
CommandText := 'Insertar en valores XsHeTong(HTH, GCMC)(:HTH,:GCMC)';
Parámetros[0].AsString := '12345';
Params[1].AsString := 'Caracteres chinos que se truncarán';
Ejecutar;
Cerca;
CommandText := 'Seleccionar * de XsHeTong';
Abierto;
fin;
Ejecute el programa, haga clic en el botón y verá que el registro se ha insertado. Desafortunadamente, el resultado no es correcto. "Los caracteres chinos que se truncarán" se convierten en "se truncarán", pero "12345" sin caracteres chinos se insertó correctamente. .
Análisis y reparación de ERRORES:
A modo de comparación, intenté utilizar directamente ADOConnection, ADOCommand y ADOTable para probar la arquitectura C/S. El resultado fue correcto y los caracteres chinos no se cortaron. Esto muestra que este ERROR solo aparece en la arquitectura de tres niveles.
Utilice SQL Server Profiler para sondear las declaraciones enviadas para ejecutarse en SQL Server y encontrar las siguientes diferencias entre la arquitectura de dos niveles y la arquitectura de tres niveles:
Arquitectura de dos niveles:
exec sp_executesql N'Insertar en valores XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Caracteres chinos que se truncarán '
Arquitectura de tres niveles:
exec sp_executesql N'Insertar en valores XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'se truncará
Obviamente, en la arquitectura de dos capas, la longitud del parámetro se pasa de acuerdo con la estructura de la biblioteca real. En la arquitectura de tres capas, la longitud del parámetro se pasa de acuerdo con la longitud de la cadena del parámetro real y la longitud real. La longitud de la cadena parece estar mal calculada. Un carácter chino se trata como si tuviera dos caracteres de longitud.
No hay más remedio que rastrear y depurar. Para depurar la biblioteca VCL de Delphi, debe seleccionar "Usar DCU de depuración" en las "Opciones del compilador" de las opciones del proyecto.
Primero rastree el programa cliente, luego ClientDataSet1.Execute, y luego pase por una serie de funciones como TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute, etc., hasta AppServer.AS_Execute (ProviderName, CommandText, Params, OwnerData); envía la solicitud al servidor. No hay anomalías. Parece que el problema está en el lado del servidor.
Después de rastrear el servidor y realizar repetidas pruebas, me concentré en la función TCustomADODataSet.PSSetCommandText. Después de un seguimiento repetido y detallado, el objetivo se volvió cada vez más preciso: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. Finalmente encontré la fuente del ERROR: la función VarDataSize Aquí está su código:
función VarDataSize (valor constante: OleVariant): entero;
comenzar
si VarIsNull (Valor) entonces
Resultado := -1
de lo contrario, si VarIsArray (Valor) entonces
Resultado := VarArrayHighBound(Valor, 1) + 1
de lo contrario, si TVarData(Value).VType = varOleStr entonces
comenzar
Resultado := Longitud(PWideString(@TVarData(Value).VOleStr)^ //La línea problemática
si Resultado = 0 entonces
Resultado := -1;
fin
demás
Resultado := TamañoDe(OleVariant);
fin;
Es en esta función que se calcula la longitud del parámetro real. Toma la dirección del valor en Valor y la utiliza como puntero WideString para encontrar la longitud de la cadena. Como resultado, la cadena "caracteres chinos". ser truncado" es La longitud se convierte en 7 en lugar de 14.
Una vez que se encuentra el problema, no es difícil resolverlo simplemente.
Resultado := Longitud(PWideString(@TVarData(Value).VOleStr)^ //La línea problemática
Cambiar a
Resultado := Longitud(PAnsiString(@TVarData(Value).VOleStr)^ //No hay problema
Eso es todo.
Pero esto hará que la longitud se duplique al encontrar la longitud de la cadena en inglés, por lo que también puedes cambiar esta línea a:
Resultado := Longitud(Valor);
De esta forma, se puede obtener la longitud correcta ya sea una cadena en chino, inglés o una mezcla de chino-inglés. Esta es una pregunta que todavía me desconcierta. ¿Por qué Borland da vueltas en círculo para encontrar la longitud del valor del parámetro mediante un puntero? Si alguien lo sabe por favor explíquemelo. ¡Muchas gracias!
Algunos amigos pueden tener preguntas: ¿por qué no ocurre este problema de truncamiento de cadenas cuando no se realiza a través de una arquitectura de tres niveles? La respuesta no es complicada. Al enviar comandos a SQL Server directamente a través de ADOCommand, determina la longitud del parámetro de acuerdo con la estructura de la tabla. Primero enviará un mensaje a SQL Server.
SET FMTONLY ON seleccione HTH,GCMC de XsHeTong SET FMTONLY OFF
para obtener la estructura de la tabla. En la arquitectura de tres niveles, aunque TCustomADODataSet también usa el objeto TADOCommand internamente para emitir comandos, no usa este valor como longitud del parámetro después de obtener la estructura de la tabla, sino que recalcula la longitud en función de los parámetros reales. un error.
BUG2. Problema con el campo de búsqueda de ClientDataSet:
Cómo reproducir ERROR:
Cree un nuevo proyecto y coloque dos ClientDataSets en él, a saber, cds1 y cds2. Sus fuentes de datos pueden ser arbitrarias. Entre ellos, cds1 es el conjunto de datos principal. Agregue un nuevo campo de búsqueda. Este campo de búsqueda se basa en un campo de caracteres. en cds1 value para encontrar el valor correspondiente en cds2.
En términos generales, es normal ejecutar el programa, pero una vez que el valor en el campo de búsqueda de cds1 aparece con una comilla simple "'" (puede modificar o agregar un registro, intente ingresar una comilla simple), se producirá un error inmediatamente. : Constante de cadena sin terminar.
Análisis y reparación de ERRORES:
La causa de este ERROR es mucho más obvia que el anterior. Debe deberse a que no se manejaron adecuadamente los efectos secundarios de las comillas simples.
De manera similar, rastreemos el código fuente de VCL:
Ejecute el programa y, cuando se produzca un error, abra la ventana Pila de llamadas (en el menú Ver->Depurar ventanas) para comprobar las llamadas a funciones. Algunas de las llamadas anteriores son obvias y no hay ningún problema. a Lookup para verificar la causa. La primera llamada a la función relacionada con Lookup es TField.CalcLookupValue. Establecemos un punto de interrupción en esta función, volvemos a ejecutar el programa y realizamos una depuración de un solo paso después de la interrupción.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
Después de varias llamadas a funciones anteriores, configuramos rápidamente el objetivo en el proceso LocateRecord. En este proceso, genera las condiciones de filtro correspondientes en función de la configuración del campo de búsqueda y luego agrega las condiciones de filtro correspondientes al conjunto de datos de destino. encontrado, y la falla radica en la generación de condiciones de filtro. Por ejemplo, debemos ir a cds2 según el valor del campo Cust (se supone que es 001) en cds1 y encontrar el valor del campo CustName correspondiente según el valor del campo CustID. La condición generada debe ser [CustID] = '001', pero si el valor de Cust es aa'bb, la condición generada se convertirá en [CustID] = 'aa'bb', lo que obviamente conduce a una constante de cadena sin terminar.
Por lo general, cuando solucionamos el problema de las comillas simples que aparecen entre comillas simples, solo necesitamos escribir dos comillas entre comillas. Lo mismo ocurre aquí, siempre que la condición generada sea [CustID] = 'aa''bb'. no habrá ningún error. Entonces puedes modificar el código fuente de esta manera:
Busque el siguiente código en el procedimiento LocateRecord:
ftString, ftFixedChar, ftWideString, ftGUID
si (i = Fields.Count - 1) y (loPartialKey en Opciones) entonces
ValStr := Formato('''%s*''',[VarToStr(Valor)]) más
ValStr := Formato('''%s''',[VarToStr(Valor)]);
Cambiar a:
ftString, ftFixedChar, ftWideString, ftGUID:
si (i = Fields.Count - 1) y (loPartialKey en Opciones) entonces
ValStr := Formato('''%s*''',[ StringReplace(VarToStr(Valor),'''','''''',[rfReplaceAll])])
demás
ValStr := Formato('''%s''',[ StringReplace(VarToStr(Valor),'''','''''',[rfReplaceAll])]);
Es decir, al generar la cadena de condición del filtro, cambie todas las comillas simples en el valor del filtro de la condición de una a dos.
Para garantizar la exactitud de esta modificación, verifiqué el proceso LocateRecord correspondiente en TCustomADODataSet (cuando use el campo de búsqueda en TADODataSet, no habrá errores debido a comillas simples, solo cuando use TCustomClientDataSet), su método de procesamiento es el mismo que TCustomClientDataSet es ligeramente diferente. Construye condiciones de filtro a través de la función GetFilterStr, pero en GetFilterStr maneja correctamente el problema de las comillas simples. Entonces, mirándolo de esta manera, el problema de no manejar correctamente las comillas simples en LocateRecord de TCustomClientDataSet es de hecho una omisión menor por parte de Borland.