Access Violation

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