-
Notifications
You must be signed in to change notification settings - Fork 0
/
cap-estrutura.tex
406 lines (363 loc) · 21.8 KB
/
cap-estrutura.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
\chapter{Estrutura do Projeto}
\label{cap:estrutura}
\setcounter{defcnt}{0}
No capítulo anterior revisamos e apresentamos conceitos visando responder a
pergunta ``Como integrar \CXX{} com linguagens de \script{}?''. Agora que
temos essas ferramentas a nosso dispor, vamos modelar uma solução. Para isso
precisamos entender, em termos práticos, o que nosso sistema deve ser capaz
de fazer, e então projetar uma estrutura que satisfaça esses requisitos.
Um usuário do nosso sistema estará tipicamente desenvolvendo uma aplicação
em \CXX{} que de alguma forma precisa ser capaz de interagir com \lang{Lua}
e \lang{Python}, possivelmente ambas ao mesmo tempo. Como vimos na seção
\ref{cap:conceitos:maquina}, essa interação pode ser estabelecida de maneira
bastante direta entre elas se a máquina virtual da linguagem de \script{} em
questão tiver sido programada na linguagem compilada de interesse. Para evitar
repetitividade e simplificar o texto, diremos, do ponto de vista das
máquinas virtuais envolvidas, que:
\definicao{
\vspace{-1.5em}
\begin{enumerate}
\item A \textbf{linguagem nativa} é a linguagem na qual a máquina
virtual foi implementada.
\item A \textbf{linguagem virtual} é a linguagem que a máquina virtual
processa para executar suas simulações.
\end{enumerate}
}
Ou seja, \CXX{} será a nossa linguagem nativa de interesse, enquanto que
as linguagens virtuais serão \lang{Lua} e \lang{Python}, mas também poderiam
ser quaisquer outras linguagens cujas máquinas virtuais estejam programadas em
\C{} ou \CXX{}.
\section{Visão Geral}
\label{cap:estrutura:geral}
Para auxiliar a modelagem da estrutura, vamos dizer que tanto a aplicação
quantos os \script{s} estão divididos em \textbf{módulos}. Eles serão os
objetos que representam o conjunto de elementos provenientes de uma ou de
outra linguagem que podem ser acessados e manipulados. Por exemplo, se a
aplicação em questão for um jogo de ação no qual o jogador enfrenta inimigos
virtuais, o desenvolvedor poderia usar \script{s} para implementar a
inteligência artifical desse inimigos, para que fosse fácil ajustá-las sem
ter que recompilar o jogo. Desse modo, cada um desses \script{s} seria um
módulo que a aplicação precisaria carregar e usar durante sua
execução\footnote{Note como isso coincide com o conceito de ``módulo''
usado pelas APIs das linguagens de \script{}.}. Eles, por sua vez,
precisariam interagir com módulos da aplicação para que as inteligências
artificiais conseguissem manipular seus personagens dentro do mundo virtual
do jogo. Se elas quisessem saber o quão próximo o herói está, elas
precisariam usar as funções do módulo de controle de posicionamento dos
avatares. Se elas quisessem criar um projétil para atacar o jogador, elas
precisariam acessar o módulo que contenha a classe\footnotemark{} que
representa o projétil desejado.
\footnotetext{Classes são estruturas presentes em linguagens de programação
\emph{orientadas a objetos}. Seu papel é definir os dados e os
comportamentos que um conjunto de objetos deve ter.
}
\begin{figure}[ht]
\centering
\caption{}
\begin{subfigure}{.8\textwidth}
\begin{center}
\includegraphics[width=.8\textwidth]{overview-simple.png}
\vspace{1em}
\textit{
Esquematização bem simplificada do funcionamento sistema.
}
\end{center}
\end{subfigure}
\label{fig:overview-simple}
\end{figure}
Dessa forma, uma primeira ilustração bem simplificada do funcionamento do
nosso sistema é a representada na Figura \ref{fig:overview-simple}. O
sistema Ouroboros será responsável por fornecer acesso aos módulos entre a
aplicação e as máquinas virtuais. Dessa forma, estabelecemos duas
grandes funcionalidades no sistema: providenciar à aplicação a possibilidade
de manipular os módulos das máquinas virtuais, e deixar os módulos da
aplicação à disposição dos \script{s} . Esses processos são conhecidos como
\textbf{incorporação}\footnote{Do inglês, ``\textit{embedding}''.} e
\textbf{exportação}, respectivamente.
\definicao{
\vspace{-1.5em}
\begin{enumerate}
\item \textbf{Incorporação} é quando a aplicação programada na linguagem
nativa pode acessar e manipular os elementos dos módulos da
máquina virtual.
\item \textbf{Exportação} é quando módulos da aplicação na linguagem
nativa são registrados na máquina virtual de modo que os módulos
desta possam usar funcionalidades daquela.
\end{enumerate}
}
Nesse ponto vale à pena destacar um aspecto que ficou de lado até agora.
Como o subtítulo desse trabalho indica, o propósito do sistema é não só
integrar as ditas linguagens, como fazê-lo de maneira \textit{automatizada}.
Até porque as APIs das máquinas virtuais por sí sós já possuem seus próprios
mecanismos de fornecer incorporação e exportação, indicando que nosso
projeto seria redundante, não fosse o fato que eles são trabalhosos e
específicos demais de usar. A intenção é que o usuário do nosso sistema não
tenha que se preocupar com os detalhes de cada máquina virtual quando
estiver desenvolvendo a aplicação dele e seja capaz de escrever normalmente
os \script{s} que trabalhem com ela --- isso é, podendo acessar módulos
externos através dos mecanismos usuais que a linguagem virtual fornece.
Logo, nosso sistema consistirá majoritariamente de uma biblioteca \CXX{}
que disponibiliza incorporação e exportação automatizada de e para \script{s}.
Faremos isso aproveitando as possibilidades de abstração que a orientação a
objetos em \CXX{} fornece para encapsular os comportamentos desejados das
máquinas virtuais. As duas próximas
sessões explicarão como projetamos as partes dessa biblioteca que atenderão
cada um desses requisitos.
\section{API unificada para incorporação de \script{s}}
\label{cap:estrutura:opa}
Como vimos na seção \ref{cap:conceitos:apis}, as máquinas virtuais com que
estamos trabalhando possuem uma API própria através da qual podemos
manipular seu conteúdo. Para evitar que o usuário acesse elas diretamente
(e tenha que lidar com os mecanismos delas ele mesmo), podemos encapsulá-las
por trás de uma camada que constitua uma nova API intermediária. Ela
unificará o acesso às funcionalidades de todas as máquinas virtuais compatíveis.
Mais especificamente, ela fornecerá os seguintes serviços:
\begin{enumerate}
\item Carregar um módulo a partir de um \script{};
\item Executar rotinas implementadas na linguagem virtual;
\item Acessar objetos definidos na linguagem virtual;
\item Converter valores entre tipos da linguagem nativa e tipos da
linguagem virtual;
\end{enumerate}
Idealmente, quando o usuário requisitar um desses serviços, ele não precisa
saber qual máquina virtual realmente vai atendê-lo. Em termos práticos, isso
significa que quando ele evocar a rotina que representa o serviço desejado,
queremos que na verdade a rotina executada seja aquela que corresponda à
máquina virtual apropriada ``sem que ele perceba'', como na figura
\ref{fig:api-unificada}. Ou seja, precisamos de funções com nomes e
assinaturas\footnote{A
\emph{assinatura} de uma rotina ou função especifica que tipos de
parâmetros ela recebe e que tipos de valores ela devolve a quem a
evocou.
} fixos simbolizando a operação almejada, porém
sobrecarregadas com implementações circunstancialmente diferentes. Um jeito
de obter esse efeito em \CXX{} é através da herança de classes, que é o
método que adotaremos. Basicamente, sempre que formos implementar uma
funcionalidade que dependa do funcionamento das máquinas virtuais,
definiremos uma classe abstrata\footnote{Em
linguagens orientadas a objetos, \emph{classes abstratas} são classes
que não especificam a implementação de todas as operações dos objetos
pertencentes ao conjunto que ela representa, determinando apenas a
interface dessas operações. Outras classes deverão herdar dela para
de fato implementar cada operação, e os objetos envolvidos pertencerão
sempre a uma dessas classes filhas, mas poderão ser tratados como objetos
da classe mãe também. Por outro lado, um objeto nunca pode pertencer
\emph{apenas} a uma classe abstrata, pois senão ele teria comportamentos
indefinidos.
} com as assinaturas desejadas e herdaremos
ela em classes específicas para cada linguagem de \script{}, que
implementarão o que de fato acontece quando as funções da classe original
são chamadas. Quando objetos dessas classes forem instanciados, os
mecanismos de \CXX{} garantirão que a implementação usada corresponderá à
classe verdadeira deles (relativa a alguma máquina virtual), por mais que a
aplicação do usuário só conheça a classe abstrata mãe.
\begin{figure}[ht]
\centering
\caption{}
\begin{subfigure}{.8\textwidth}
\begin{center}
\includegraphics[width=.5\textwidth]{api-unificada.png}
\vspace{1em}
\textit{
Nesse exemplo, a aplicação do usuário pede para carregar um certo
módulo \script{}. A API unificada deve discernir qual máquina virtual
é a apropriada para tal (no caso, a máquina C) e delegar a tarefa, sem
que o usuário precise saber.
}
\end{center}
\end{subfigure}
\label{fig:api-unificada}
\end{figure}
Vamos, então, basear-nos nos serviços listados acima para escolher a combinação de
classes que a nossa API intermediária deverá oferecer. Uma maneira de fornecer acesso a
objetos virtuais, como especifica o item 3, é encapsular seu comportamento
em uma classe própria. Dessa forma, quando instâncias dessa classe fossem
manipuladas pela aplicação do usuário, elas internamente executariam as
operações necessárias na máquina virtual para que o efeito desejado
ocorresse. E com isso ganhamos o item 4 de graça, pois basta colocar
nessa classe métodos que convertam o valor interno do objeto virtual para
\CXX{} e vice-versa. Por exemplo, o usuário poderia usar uma instância dessa
classe para referir-se a uma cadeia de caracteres definida em um \script{}
para convertê-la para o tipo \lang{string} em \CXX{}. O item 2 também
é facilmente satisfeito pois, levando em consideração que nas linguagens de
\script{} com que estamos trabalhando funções são tipos de primeira
classe\footnotemark{}, um objeto virtual também seria capaz de conter uma
delas de forma que ao ser evocado desencadearia a rotina correspondente
dentro da máquina virtual. A figura \ref{fig:api-vobjs} exemplifica essas
ideias.
\footnotetext{Quando funções são tipos de \emph{primeira classe}, elas podem ser
tratadas como se fossem um valor, o que permite que elas possam ser
armazenadas em variáveis, passadas como argumento para outras funções
e até mesmo devolvidas como resultado de outras funções também.
}
Fica faltando apenas o item 1, com o qual podemos seguir o mesmo raciocínio
que usamos com funções: podemos tratar módulos como objetos virtuais. Assim,
quando o usuário pede para carregar um \script{}, ele recebe uma instância
de objeto virtual que abstrai o módulo em si. Isso exige que a classe
tenha operações de acesso a membros dos objetos, para a aplicação poder
alcançar os objetos virtuais que estão ``dentro'' do módulo. Para completar,
a técnica de sobrecarga via herança nos permite criar implementações diferentes
dessa classe para representar objetos virtuais de cada máquina e mesmo
assim exigir que o usuário use apenas a classe base delas com as assinaturas de
todas essas operações que vimos.
\begin{figure}[ht]
\centering
\caption{}
\begin{subfigure}{.8\textwidth}
\begin{center}
\includegraphics[width=.7\textwidth]{api-vobjs.png}
\vspace{1em}
\textit{
Cada instância da classe que representará objetos virtuais encapsula
um valor dentro de alguma máquina virtual. Note que mais de uma
instância pode ser responsável por um mesmo valor.
}
\end{center}
\end{subfigure}
\label{fig:api-vobjs}
\end{figure}
Por uma questão de projeto de código, são necessárias ainda mais duas
classes. Afinal, quando o usuário quiser carregar um módulo para obter seu
respectivo objeto virtual, que parte da nossa API ele deverá usar? A classe
dos objetos virtuais não pode ser instanciada sozinha (até porque ela é
abstrata). Normalmente, quem tem as informações necessárias para fazê-lo são
as próprias máquinas virtuais. Então, uma solução seria definir uma classe
para as máquinas virtuais também, novamente usando herança para abstrair o
funcionamento delas ao mesmo tempo deixando que assinaturas relevantes fiquem
expostas. A terceira classe seria justamente para gerenciar as instâncias de
máquinas virtuais. As relações entre essas três classes e o usuário estão
representadas na figura \ref{fig:api-classes}.
Chamamos a parte do nosso sistema que corresponde a essa API unificada de
\emph{\textbf{Ouroboros Project API}} ou simplesmente \textbf{OPA}.
\begin{figure}[ht]
\centering
\caption{}
\begin{subfigure}{.8\textwidth}
\begin{center}
\includegraphics[width=\textwidth]{api-classes.png}
\vspace{1em}
\textit{
Isso não chega a ser uma diagrama UML, apenas uma representação
simbólica das classes. A especificação mais detalhada delas será
exposta no capítulo \ref{cap:atividades}. Mas emprestamos a notação
de herança.
}
\end{center}
\end{subfigure}
\label{fig:api-classes}
\end{figure}
\section{Gerador de \textit{wrappers} para exportação de interfaces nativas}
\label{cap:estrutura:opwig}
Para entender melhor como fazer a exportação, precisamos saber o que
exportar. No caso, como as aplicações dos usuários estarão escritas em \C{}
ou \CXX{}, os elementos que gostaríamos de exportar são:
\begin{enumerate}
\item Variáveis globais.
\item Funções globais.
\item Tipos novos definidos na aplicação (usando \lang{typedef}, classes
ou equivalentes)
\item Outras informações, como \textit{namespaces}\footnotemark{}.
\end{enumerate}
\footnotetext{\textit{Namespaces} (do inglês, ``espaços de nomes'') são estruturas
em \CXX{} que separam os elementos de uma aplicação em espaços distintos,
facilitando a organização e expressividade do código, assim como
evitando possíveis conflitos de nomes iguais usados em contextos
diversos.
}
No terceiro item, o ideal é que todas as operações inerentes ao tipo
também sejam exportadas. Isso englobaria tanto operações aritméticas básicas
quanto os métodos de classes, por exemplo. O usuário deverá ser capaz de
criar objetos desses tipos em seus \script{s} e usá-los da maneira mais fiel
possível ao modo como eles seriam usados no código nativo da aplicação.
O que explicamos nas sessões \ref{cap:conceitos:apis:lua} e
\ref{cap:conceitos:apis:python} nos fornece algumas ferramentas úteis,
apesar de trabalhosas, para exportar esses elementos que listamos.
Basicamente, podemos usar os mecanismos de registro de módulos nas máquinas
virtuais para expor os módulos reais da aplicação\footnotemark{}. Aproveitando
o exemplo que demos na seção \ref{cap:estrutura:geral}, poderia haver um
módulo na aplicação (no caso, um jogo) que cuidasse do posicionamento de
avatares, e nós gostaríamos que \script{s} incorporados pudessem acessar
as funcionalidades desse módulo. Todas as variáveis, funções e tipos relacionados
teriam que ser colocados no módulo virtual através das funções de
inicialização, que precisam ser feitas usando o protocolo esperado pelas
APIs.
\footnotetext{Para não confundir a qual tipo de módulo estaremos nos referindo,
chamaremos os módulos que registramos nas máquinas virtuais de
\emph{módulos virtuais}, enquanto que os da aplicação serão simplesmente
\emph{módulos}.
}
Isso compõe um método de exportação que funciona, mas traz dificuldades. A
maior delas é que \C{} e \CXX{} não oferecem um meio de definir funções
novas em tempo de execução, impedindo nossa biblioteca de registrar módulos
virtuais durante o processo de suas rotinas, pois as funções de inicialização já precisam
estar criadas quando a aplicação do usuário for ser compilada. A outra
grande dificuldade é que é igualmente impossível de analisar o código da
aplicação durante a execução dela mesma dadas as linguagens compiladas que
adotamos. Normalmente, se um desenvolvedor quisesse exportar seu código
nativo para uma linguagem de \script{}, ele simplesmente escreveria as
funções de inicialização e manualmente colocaria cada elemento do código
dele nos módulos exportados. Mas o que estamos tentando projetar aqui visa
justamente \emph{automatizar} a integração entre aplicação e \script{s}, então
simplesmente pedir para o usuário fazer esse trabalho sujo para nós está
fora de questão. Assim sendo, não temos como saber quais variáveis, funções
e tipos pertencem a um módulo e, mesmo que soubéssemos, não temos como
fornecer as funções de inicialização necessárias quando a aplicação do
usuário for executada e esse serviço for requerido da nossa biblioteca.
Um jeito de contornar esses problemas é \emph{não} tentar fazer a exportação
durante a execução da aplicação, mas sim antes de ela ser compilada, de
maneira que tenhamos acesso ao seu código fonte. A partir dele somos
capazes de reconhecer as declarações que queremos importar, e podemos
escrever o código adicional das funções de inicialização a tempo
de elas serem compiladas junto com o resto da aplicação. Isso seria feito
por um outro programa --- que obviamente precisa estar compilado antes da
aplicação, uma vez que ele \emph{interfere} com o código dela --- que chamamos
de \emph{gerador de código}. Na prática, as únicas partes do código da
aplicação que ele precisa analisar são os cabeçalhos, que é onde estarão as
declarações. Uma visão geral desse procedimento está ilustrada na figura
\ref{fig:gerador-funcionamento}.
No entanto, ainda sobram algumas outras complicações porque, em geral,
elementos nativos não podem ser exportados diretamente. Isso fica bastante
claro quando olhamos para o modo como funções nativas são passadas para as
máquinas virtuais: elas precisam ter uma assinatura específica, que ainda
por cima varia de máquina para máquina. E fica ainda mais complicado quando
pensamos em classes, pois em \lang{Lua}, por exemplo, não há orientação a
objetos implementada por padrão --- embora seja possível construir mecanismos
na linguagem que funcionem como classes, que é o que precisamos fazer.
\begin{figure}[ht]
\centering
\caption{}
\begin{subfigure}{.8\textwidth}
\begin{center}
\includegraphics[width=\textwidth]{gerador-funcionamento.png}
\vspace{1em}
\textit{
Representação de como o gerador conribui na exportação.
Em \CXX{}, arquivos de cabeçalho usam a extensão \lang{.h} enquanto
que arquivos de implementação usam \lang{.cxx}, embora haja outras
conveções.
}
\end{center}
\end{subfigure}
\label{fig:gerador-funcionamento}
\end{figure}
Então, para cada tipo de elemento nativo que estamos interessados
(variáveis, funções, tipos, etc), precisamos de uma maneira de adaptá-los
para o formato que as máquinas virtuais esperam. Isso é possível através do
uso de \textit{wrappers} (do inglês, ``embrulhos''), que nesse contexto são
porções de código que encapsulam as atividades nativas da aplicação para
expô-los adequadamente aos protocolos que as APIs das máquinas virtuais
exigem para exportação. Assim, o que realmente estaremos inserindo nos
módulos virtuais serão esses \textit{wrappers}, e não os elementos
originais. Entraremos em mais detalhes sobre como fazer esses \textit{wrappers} no
capítulo \ref{cap:atividades}. Por enquanto, o importante é entender que
essa parte do nosso sistema envolverá um gerador de código que produzirá
os \textit{wrappers} que ele julgar necessário dado o código fonte da
aplicação do usuário, e os inserirá em módulos virtuais de maneira a
efetivamente exportar as funcionalidades nativas para as máquinas virtuais.
Isso tudo significa que no final das contas nosso sistema não será apenas
uma biblioteca, pois ele também terá executáveis fazendo o papel de
geradores, um para cada linguagem de \script{}. O que fizemos para evitar
dividir nosso código em muitas partes foi limitar esses programas auxiliares a
simplesmente evocarem algumas rotinas da nossa biblioteca que de fato farão a análise e
a geração de código. Chamamos essa parte do nosso sistema de \emph{Ouroboros
Project Wrapper and Interface Generator} (OPWIG).