Access Violation

sexta-feira, janeiro 20, 2006

O que esta errado com este código (parte II) : Resposta
Se você executar o programa em questão verá que o resultado impresso sera:

N: 1 J: 7
N: 2 J: 6
N: 3 J: 5
N: 4 J: 4

Certamente esse não era o resultado esperado!

Olhando mais a fundo você observará que foram impressos itens consecutivos do vetor, no entanto a linha 38 (função printIt()) avança o índice de dois em dois (ou seja, os itens deveriam ser impressos intercalados)!

Para entender melhor o problema vamos analiasar a função "printIt()" (muito simples por sinal).

Na linha 38 temos um loop de 0 até o n. A cada passo o índice (i) utilizado para acessar o array é incrementado de dois, ou seja, o primeiro elemento acessado é o de índice 0 (zero), seguido pelos elementos de índice 2, 4 e assim sucessivamente.

Este acesso se dá através da chamada do método print() do objeto que se encontra no índice corrente (0, 2, 4, etc) na linha 40:

a[i].print();

O problema esta na forma como o compilador gera código para resolver a expressão a[i]. Esta expressão é equivalente a *(a + i) que nada mais é que aritmética de ponteiros. Bom, a especificação da linguagem determina que esta aritmética deve ser realizada baseada no tamanho do tipo em questão (neste caso o tipo da variável a), ou seja, sendo p um ponteiro igual a 1000, o endereço (p+2) é calculado como 1000 + 2 * sizeof(*p).

Se p for um ponteiro para char então p+2 será efetivamente:


1000 + 2 * sizeof(char) => 1000 + 2 * 1 == 1002


Contudo se p for um ponteiro para int então p+2 sera:


1000 + 2 * sizeof(int) => 1000 + 2 * 4 => 1008 (isto em 32 bits).


Veja o exemplo abaixo:

int v[4];
int *p = v; // p == &v[0];
int *p1 = p + 1; // p1 == &v[1];

char *pc = (char*) p;
char *p1c = (char*) p1;

printf("Diferença: %d", p1 - p);

Agora que você já relembrou como é realizada aritimética de ponteiros, voltemos à nossa expressão:

a[i].print();

Como dissemos esta expressão é equivalente à:

base *p = &a[i];
p->print();

que é equivalente à:

base *p = (a + i);
p->print();

Já percebeu? Não?

Imagine que o vetor a[] possua a seguinte configuração na memória:

a[0] (1000)
a[1] (1016)
a[2] (1032)
... ...
a[n]

No diagrama acima observamos que o primeiro elemento de a[], ou seja, a[0] se encontra no endereço 1000. E a[1]? qual seu endereço?

Baseado no diagrama acima responderíamos: 1016 é claro!

O problema é que o compildor não viu este diagrama; então para calcular este endereço o mesmo irá empregar aritmética de ponteiros com as informações que possui em mãos, ou seja, o endereço inicial de a e seu tipo um vetor
de objetos do tipo base
:

&a[i] == (a + i * sizeof(base))

Observe bem a expressão acima!

O compilador esta considerando o tamanho de "base" para calcular o deslocamento do elemento de indice "i" a partir do início do vetor, quando na realidade temos um vetor de objetos "derived", ou seja, para funcionar deveríamos tomar sizeof(derived) e não sizeof(base).

Assim, &a[1] será (assumindo que o primeiro elemento do vetor "a" se encontra no endereço 1000):

&a[1] => (1000 + 1 * sizeof(base)) => (1000 + 1 * 8) => 1008


Opss!! Não deveria ser 1016?

Então para a função "printIt()" o vetor 'a' tem a seguinte configuração:

Visão printIt()
---------------
a[0] (1000)
a[1] (1008)
a[2] (1016)
a[3] (1024)
a[4] (1032)
a[5] (1040)
a[6] (1048)
... ...
a[n]

Observe que isto ocorre porque sizeof(base) * 2 == sizeof(derived). Isto não é uma coicidência! Eu escrevi as classes para obter este resultado.

Na vida real, um erro como este (utilizar um vetor de objetos de forma polimorfica) normalmente causaria exceções de acesso a memória (access violations).

Quer fazer um teste? É bem simples, basta modificar o código de forma que o índice 'i' seja incrementado de 1 (um) ao invés de 2 (dois). Isto fará com que o programa tente acessar &a[1] que na realidade não é o endereço do segundo elemento do vetor mas sim um endereço no meio do primeiro objeto do vetor.

Como resolver isto? Simples! Nunca utilize vetores de objetos de forma polimorfica! Utilize um vetor de ponteiros para objetos!

Espero ter contribuido!

Adriano

[+/-] mostrar/esconder este post

quinta-feira, janeiro 19, 2006

Executando métodos de um objeto JScript a partir de um componente COM
Outro dia recebi um email de um amigo perguntando como ele poderia chamar um método de um objeto JScript a partir de um componente COM, ou seja, ele queria fazer um call back para o script que havia executado o método em questão.

No primeiro momento não entendi direito.. achei que não pudesse ser feito; em casa, tomando banho (como sempre ocorre) me veio a solução (ou melhor, eu entendi o problema :). Vamos ao que interessa, o exemplo.

  1. A primeira coisa que temos que entender é que o JScript (da Microsoft) é implementado utilizando-se a Script Engine que nada mais é que um punhado de componentes COM (ok estou simplificando um pouco as coisas). Assim sendo é natural que objetos em JScript suportem o modelo do COM.

  2. Ótimo, o próximo ponto que devemos ter em mente é que sendo uma linguagem de script também é natural que estes objetos suportem a interface IDispatch.

  3. Com isto em mente podemos escrever nosso código JScript que deve
    1. Implementar uma classe
    2. Instanciar um componente COM
    3. Passar, de alguma forma, uma referância do objeto JScript para o componente COM
Primeiro vamos apresentar o código JScript.
0: function MinhaClasse_Teste1(i)
1: {
2: this.Valor = i * 2;
3: }
4:
5: function MinhaClasse_Teste2()
6: {
7: return this.Valor;
8: }
9:
10: function MinhaClasse()
11: {
12: this.Teste1 = MinhaClasse_Teste1;
13: this.Teste2 = MinhaClasse_Teste2;
14: this.Valor = 1;
15: }
16:
17: var obj;
18: obj = new MinhaClasse();
19:
20: obj.Teste1(10);
21: WScript.Echo("Valor: " +
22: obj.Teste2() +
23: " [Antes de ter o callback executado]");
24:
25: var t = new ActiveXObject("TestScript.ScriptCallBack");
26: t.Callback = obj;
27: t.DoSomeAction(40);
28:
29: WScript.Echo("Valor: " +
30: obj.Teste2() +
31: " [Depois de executar o objeto COM]");

Nas linhas 10-15 declaramos uma classe (a função MinhaClasse() é tratada como o construtor da classe); dentro do construtor adicionamos os métodos/propriedades à classe (através de atribuição).

Pronto! Nossa classe já esta definida! A mesma possui dois métodos (Teste1() e Teste2()) e uma propriedade (Valor).

Nas linhas 17-23 executamos alguns métodos da classe apenas a título de teste.

Na linha 25 instanciamos o objeto COM que iremos utilizar.

Na linha 26 informamos a este objeto qual o objeto deve ser utilizado para realizar o callback (note que o mesmo deve possuir conhecimento prévio da estrutura da classe, ou seja, o objeto COM deve saber o nome do método e número de parâmetros do método a ser executado como callback).

Finalmente na linha 27 executamos o método do objeto COM que irá (em algum momento) executar o método do objeto JScript. O método DoSomeAction() irá chamar a função Teste1() da classe MinhaClasse() (JScript).

Neste ponto finalizamos a parte do script. Agora vamos abordar o componente COM:
0: #include "stdafx.h"
1: #include "TestScript.h"
2: #include "TestScriptImpl.h"
3:
4: STDMETHODIMP CScriptCallBack::put_Callback(IDispatch* rhs)
5: {
6: if (obj)
7: {
8: obj.Release();
9: }
10:
11: if (rhs)
12: {
13: obj = rhs;
14: }
15:
16: return S_OK;
17: }
18:
19: STDMETHODIMP CScriptCallBack::DoSomeAction(INT i)
20: {
21: HRESULT hr = E_UNEXPECTED;
22: if (obj)
23: {
24: CComVariant param(i);
25: hr = obj.Invoke1(OLESTR("Teste1"), &param);
26: }
27:
28: return hr;
29: }

Este é um componente típico, sem maiores complicadores. O ponto importante no mesmo é a propriedade Callback; a mesma espera um ponteiro para uma interface do tipo IDispatch que implemente um método com a seguinte assinatura:

Teste1(int n)

Quando o método DoSomeAction() é executado o mesmo chama o método Teste1() do objeto passado para a propriedade Callback.

Observe que para o componente COM não importa se o objeto apontado pela interface IDispatch em questão é ou não um objeto JScript; tudo que é necessário é que este objeto implemente a interface IDispatch e possua o método Teste1() com uma assinatura compatível.

Aproveitando a oportunidade, você consegue ver um problema em potencial neste código? Então fique atento... este será o assunto de um post futuro na série: "Oque esta errado com este código - parte N" :)

Have fun!



[+/-] mostrar/esconder este post

terça-feira, janeiro 17, 2006

O que esta errado com este código (parte II)
Este é um pouco mais difícil de entender apenas olhando o código. Para facilitar adianto que o programa deveria imprimir o seguinte:
- N: 1 J: 7
- N: 3 J: 5
- N: 5 J: 3
- N: 7 J: 1

Compile o programa e teste :) (tanto o VC6 quanto o VC7 geram o mesmo resultado)


0: #include <iostream>
1:
2: class base
3: {
4: public:
5: base(int i) : n(i)
6: {
7: }
8:
9: virtual void print()
10: {
11: std::cout << "N: " << n << " ";
12: }
13:
14: private:
15: int n;
16: };
17:
18: class derived : public base
19: {
20: public:
21: derived(int x, int y) : base(x), j(y), t(0)
22: {
23: }
24:
25: virtual void print()
26: {
27: base::print();
28: std::cout << "J: " << j << std::endl;
29: }
30:
31: private:
32: int j;
33: int t;
34: };
35:
36: void printIt(base a[], int n)
37: {
38: for (int i=0; i < n; i += 2)
39: {
40: a[i].print();
41: }
42: }
43:
44: void main()
45: {
46: derived d[] = {
47: derived(1,7),
48: derived(2,6),
49: derived(3,5),
50: derived(4,4),
51: derived(5,3),
52: derived(6,2),
53: derived(7,1)
54: };
55:
56:
57: printIt(d, sizeof(d) / sizeof(d[0]));
58: }

[+/-] mostrar/esconder este post