Análise e reparo de dois BUGs em Delphi
Ao usar o Delphi 7 para desenvolvimento de banco de dados de três camadas, encontrei dois pequenos problemas. Através de testes repetidos, finalmente descobri os dois pequenos BUGs no Delphi 7 e os corrigi (parece que existem os mesmos BUGs no Delphi 6), escrevendo. Este artigo compartilha a alegria do sucesso com todos. Também sou novo no Delphi, então deve haver muitas coisas erradas no artigo. Corrija-me.
BUG1. Os caracteres chineses são truncados ao passar parâmetros:
Como reproduzir BUG:
O SQL Server 2000 é usado em segundo plano e há uma tabela XsHeTong para teste. Você pode ajustá-la de acordo com sua situação real.
Primeiro crie um servidor de dados: crie um novo projeto, crie um módulo de dados remoto, coloque um ADOConnection, ADODataSet e DataSetPRovider nele e faça as configurações correspondentes. Deixe o ComamndText do ADODataSet em branco e defina o poAllowCommandText em sua opção como True. Compile e execute.
Crie um programa cliente novamente: crie um novo projeto, coloque um DCOMConnection no formulário, conecte-se ao servidor de dados criado anteriormente, coloque um ClientDataSet, defina sua conexão com o DCOMConnection aqui e defina seu ProviderName para o servidor acima do nome do DataSetProvider. Por fim, coloque o DataSource e o DBGrid e faça as configurações correspondentes para visualizar os resultados, e em seguida coloque um Button para teste.
Escreva um código semelhante ao seguinte no OnClick do Button (aqui usei a tabela XsHeTong e seus dois campos HTH (char 15), GCMC (varchar 100), você pode ajustá-lo de acordo com sua situação real de teste):
com ClientDataSet1 faça
começar
Fechar;
CommandText := 'Inserir em valores XsHeTong(HTH, GCMC)(:HTH,:GCMC)';
Params[0].AsString := '12345';
Params[1].AsString := 'Caracteres chineses que serão truncados';
Executar;
Fechar;
CommandText := 'Selecione * de XsHeTong';
Abrir;
fim;
Execute o programa, clique no botão e veja que o registro foi inserido Infelizmente, o resultado não está correto "Os caracteres chineses que serão truncados" tornam-se "serão truncados", mas "12345" sem caracteres chineses é inserido corretamente. .
Análise e reparo de BUG:
Para efeito de comparação, tentei usar diretamente ADOConnection, ADOCommand e ADOTable para testar a arquitetura C/S. O resultado estava correto e os caracteres chineses não seriam cortados. Isto mostra que este BUG só aparece na arquitetura de três camadas.
Use o SQL Server Profiler para investigar as instruções enviadas para execução no SQL Server e encontrar as seguintes diferenças entre a arquitetura de duas camadas e a arquitetura de três camadas:
Arquitetura de duas camadas:
exec sp_executesql N'Inserir em valores XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Caracteres chineses que serão truncados '
Arquitetura de três camadas:
exec sp_executesql N'Inserir em valores XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'será truncado
Obviamente, na arquitetura de duas camadas, o comprimento do parâmetro é passado de acordo com a estrutura real da biblioteca. Na arquitetura de três camadas, o comprimento do parâmetro é passado de acordo com o comprimento da string do parâmetro real e o comprimento real. o comprimento da string parece ter sido calculado incorretamente. Um caractere chinês é tratado como dois caracteres de comprimento.
Não há escolha a não ser rastrear e depurar. Para depurar a biblioteca VCL do Delphi, você precisa selecionar "Usar DCUs de depuração" nas "Opções do compilador" das opções do projeto.
Primeiro rastreie o programa cliente, depois ClientDataSet1.Execute e, em seguida, passe por uma série de funções como TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute, etc., até AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); envia a solicitação ao servidor. Não há anormalidades. Parece que o problema está no lado do servidor.
Após rastrear o servidor e testes repetidos, concentrei-me na função TCustomADODataSet.PSSetCommandText. Após rastreamento repetido e detalhado, o alvo tornou-se cada vez mais preciso: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. Finalmente encontrei a fonte do BUG: a função VarDataSize Aqui está o seu código:
função VarDataSize (const Valor: OleVariant): Inteiro;
começar
se VarIsNull(Valor) então
Resultado:= -1
senão se VarIsArray(Valor) então
Resultado: = VarArrayHighBound (Valor, 1) + 1
senão se TVarData(Value).VType = varOleStr então
começar
Resultado := Length(PWideString(@TVarData(Value).VOleStr)^); //A linha problemática
se Resultado = 0 então
Resultado:= -1;
fim
outro
Resultado:= SizeOf(OleVariant);
fim;
É nesta função que o comprimento do parâmetro real é calculado. Ela pega o endereço do valor em Value e o usa como um ponteiro WideString para encontrar o comprimento da string. ser truncado" é O comprimento passa a ser 7 em vez de 14.
Uma vez encontrado o problema, não é difícil resolvê-lo.
Resultado := Length(PWideString(@TVarData(Value).VOleStr)^); //A linha problemática
Mudar para
Resultado := Length(PAnsiString(@TVarData(Value).VOleStr)^);
É isso.
Mas isso fará com que o comprimento seja duplicado ao encontrar o comprimento da string em inglês, então você também pode alterar esta linha para:
Resultado := Comprimento(Valor);
Desta forma, o comprimento correto pode ser obtido, seja uma string chinesa, inglesa ou mista chinês-inglês. Esta é uma questão que ainda me intriga. Por que a Borland anda em círculo para encontrar o comprimento do valor do parâmetro através de um ponteiro? Se alguém souber, por favor me explique. Muito obrigado!
Alguns amigos podem ter dúvidas, por que esse problema de truncamento de strings não ocorre quando não é feito através de uma arquitetura de três camadas? A resposta não é complicada. Ao enviar comandos ao SQL Server diretamente através do ADOCommand, ele determina o comprimento do parâmetro de acordo com a estrutura da tabela. Ele primeiro enviará uma mensagem ao SQL Server
DEFINIR FMTONLY ON selecione HTH, GCMC de XsHeTong DEFINIR FMTONLY OFF
para obter a estrutura da tabela. Na arquitetura de três camadas, embora TCustomADODataSet também use o objeto TADOCommand internamente para emitir comandos, ele não usa esse valor como comprimento do parâmetro após obter a estrutura da tabela, mas recalcula o comprimento com base nos parâmetros reais. um erro.
BUG2. Problema com o campo Lookup do ClientDataSet:
Como reproduzir BUG:
Crie um novo projeto e coloque dois ClientDataSets nele, ou seja, cds1 e cds2. Suas fontes de dados podem ser arbitrárias. Entre eles, cds1 é o conjunto de dados principal. no valor cds1 para encontrar o valor correspondente em cds2.
De modo geral, é normal executar o programa, mas assim que o valor no campo Lookup de cds1 aparecer com aspas simples "'" (você pode modificar ou adicionar um registro, tente inserir aspas simples), ocorrerá um erro imediatamente : Constante de string não terminada.
Análise e reparo de BUG:
A causa deste BUG é muito mais óbvia que a anterior. Deve ser causada pela falha em lidar adequadamente com os efeitos colaterais das aspas simples.
Da mesma forma, vamos rastrear o código-fonte da VCL:
Execute o programa e, quando ocorrer um erro, abra a janela Call Stack (no menu View->Debug Windows) para verificar as chamadas de função. Algumas das chamadas anteriores são óbvias e não há problema. para Lookup para verificar a causa. A primeira chamada de função relacionada a Lookup é TField.CalcLookupValue Definimos um ponto de interrupção nesta função, executamos novamente o programa e executamos a depuração em uma única etapa após a interrupção.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
Após várias chamadas de função acima, definimos rapidamente o destino no processo LocateRecord. Nesse processo, ele gera as condições de filtro correspondentes com base nas configurações do campo de pesquisa e, em seguida, adiciona as condições de filtro correspondentes ao conjunto de dados de destino. encontrado, e a falha está na geração de condições de filtro. Por exemplo, precisamos ir para cds2 com base no valor do campo Cust (assumido como 001) em cds1 e encontrar o valor do campo CustName correspondente com base no valor do campo CustID. A condição gerada deve ser [CustID] = '001', mas se o valor de Cust for aa'bb, a condição gerada se tornará [CustID] = 'aa'bb', o que obviamente leva a uma constante String inacabada.
Normalmente, quando resolvemos o problema de aspas simples aparecerem entre aspas simples, precisamos apenas escrever duas aspas entre aspas. O mesmo é verdade aqui, desde que a condição gerada seja [CustID] = 'aa''bb'. não haverá erro. Então você pode modificar o código-fonte assim:
Encontre o seguinte código no procedimento LocateRecord:
ftString, ftFixedChar, ftWideString, ftGUID
if (i = Fields.Count - 1) e (loPartialKey em Opções) então
ValStr := Format('''%s*''',[VarToStr(Valor)]) senão
ValStr := Formato('''%s''',[VarToStr(Valor)]);
Alterar para:
ftString, ftFixedChar, ftWideString, ftGUID:
if (i = Fields.Count - 1) e (loPartialKey em Opções) então
ValStr := Formato('''%s*''',[ StringReplace(VarToStr(Valor),'''','''''',[rfReplaceAll])])
outro
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
Ou seja, ao gerar a sequência de condições do filtro, altere todas as aspas simples no valor do filtro da condição de um para dois.
Para garantir a exatidão desta modificação, verifiquei o processo LocateRecord correspondente em TCustomADODataSet (ao utilizar o campo Lookup em TADODataSet não haverá erros por aspas simples, somente ao utilizar TCustomClientDataSet), seu método de processamento é o mesmo que TCustomClientDataSet é um pouco diferente. Ele constrói condições de filtro por meio da função GetFilterStr, mas em GetFilterStr trata corretamente o problema de aspas simples. Então, olhando desta forma, o problema de não lidar corretamente com aspas simples em LocateRecord de TCustomClientDataSet é de fato uma pequena omissão da Borland.