Atualmente, estou desenvolvendo uma aplicação ASP.NET que, por motivos de legado, precisa executar alguns scripts Perl. Para isso, escrevi uma pequena biblioteca C++ que utiliza a API Perl embarcada. Essa biblioteca possui um único ponto de entrada (C) que permite que o código C# execute um script enquanto passa argumentos de linha de comando e um ambiente. Esse ambiente permite que o código C# imite uma chamada CGI para o script Perl.
Agora estou vendo algo estranho. O aplicativo C# permite a execução simultânea de (um número máximo de) scripts Perl. Para cada chamada, um ambiente diferente é criado e este é passado para minha biblioteca C++, que então o passa para a perl_parse
função. O que noto então é que o ambiente real visto pelo script Perl não corresponde ao ambiente real passado, mas parece ser uma cópia antiga. Então, minha pergunta é: estou esquecendo de algo? Existe uma maneira especial de executar vários interpretadores Perl embarcados simultaneamente? Além disso, quando limito o número de threads simultâneas a 1, o problema persiste.
As partes relevantes da biblioteca C++ são as seguintes:
extern "C" __declspec(dllexport) BOOL ExecutePerlScript(PCSTR* environmentVariables,
PCSTR path)
{
BOOL result(FALSE);
// Create the Perl interpreter
PerlInterpreter* my_perl(perl_alloc());
if (NULL != my_perl)
{
PERL_SET_CONTEXT(my_perl);
PL_perl_destruct_level = 1;
perl_construct(my_perl);
PL_origalen = 1;
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
// Initialize the Perl interpreter
result = (perl_parse(my_perl,
XsInit,
NR_DEFAULT_ARGUMENTS,
DEFAULT_ARGUMENTS,
const_cast<char**>(environmentVariables)) == 0) ? TRUE : FALSE;
// Run the interpreter
if (result)
{
result = (perl_run(my_perl) == 0) ? TRUE : FALSE;
}
if (result)
{
result = LoadFile(path,
my_perl);
}
if (result)
{
// Execute the Perl script
eval_pv("eval \"$" SCRIPT_TO_EVALUATE_VARIABLE_NAME "; 1\" or do { $" SCRIPT_EXECUTION_ERROR_VARIABLE_NAME " = $@; }",
TRUE);
}
// Destruct the interpreter
PL_perl_destruct_level = 1;
perl_destruct(my_perl);
perl_free(my_perl);
}
return result;
}
extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
BOOL result(FALSE);
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
if (0 == g_initCount)
{
PERL_SYS_INIT3(0,
NULL,
NULL);
}
g_initCount++;
result = TRUE;
break;
case DLL_PROCESS_DETACH:
if (g_initCount > 0)
{
g_initCount--;
if (0 == g_initCount)
{
PERL_SYS_TERM();
}
}
result = TRUE;
break;
}
return result;
}
O formato environmentVariables
do snippet acima é uma matriz de char*
onde cada elemento está no formato <variable name>=<variable value>
e o último elemento da matriz é NULL
.
O script Perl que executo é o seguinte:
use strict;
use CGI qw/:standard/;
print "---- ENVIRONMENT ----\n";
for my $env (sort keys %ENV)
{
print "$env = $ENV{$env}\n";
}
print "\n";
Por exemplo, uma das execuções (em um loop) passa o seguinte ambiente para a função C++:
- AUTH_TYPE =
- CONTENT_LENGTH = 47
- CONTENT_TYPE = application/x-www-form-urlencoded
- GATEWAY_INTERFACE = CGI/1.1
- PATH_INFO = /test.pl
- PATH_TRANSLATED = E:\Perl\PerlTestApplication\test.pl
- QUERY_STRING = lang=nl
- REMOTE_ADDR = 1.2.3.4
- REMOTE_HOST = remote.host
- REMOTE_USER =
- REQUEST_METHOD = POST
- SCRIPT_NAME = /test.pl
- SERVER_NAME = example.domain
- SERVER_PORT = 443
- SERVER_PROTOCOL = HTTP/1.1
- SERVER_SOFTWARE = Microsoft-IIS/10.0
e então o script imprime o seguinte ambiente:
---- ENVIRONMENT ----
AUTH_TYPE =
CONTENT_LENGTH = 45
CONTENT_TYPE = application/x-www-form-urlencoded
GATEWAY_INTERFACE = CGI/1.1
PATH_INFO = /test.pl
PATH_TRANSLATED = E:\Perl\PerlTestApplication\test.pl
QUERY_STRING = lang=nl
REMOTE_ADDR = 1.2.3.4
REMOTE_HOST = remote.host
REMOTE_USER =
REQUEST_METHOD = POST
SCRIPT_NAME = /test.pl
SERVER_NAME = example.domain
SERVER_PORT = 443
SERVER_PROTOCOL = HTTP/1.1
SERVER_SOFTWARE = Microsoft-IIS/10.0
Como pode ser visto, o valor da CONTENT_LENGTH
variável é diferente e, no ambiente Perl, é o mesmo de um ambiente passado anteriormente para o script. Portanto, de alguma forma, o ambiente que passo para a nova instância do interpretador Perl não é limpo e outro ambiente ainda é usado. Eu já uso o PERL_SET_CONTEXT
para definir o contexto na thread atual logo após a construção, mas isso simplesmente não parece ser suficiente.
Eu tentei isso em uma instalação Active Perl do Perl 5.24 e em uma instalação Strawberry Perl do Perl 5.30, mas ambas dão o mesmo resultado errôneo.
O que estou fazendo errado?
No código-fonte do Perl, especificamente
perl_parse
oenv
parâmetro não é usado. Então, imagino que esteja reutilizando o ambiente de processos atual? Para contornar isso, você pode tentar algo assim:Aqui, obtemos o
%ENV
hash, analisamos oenvironmentVariables
array e os inserimos no hash, testando UTF8, etc. Em seguida, executamos o script normalmente. Observação : Isso pressupõe que eleenvironmentVariables
termine comnullptr
, então ajuste conforme necessário.Outra opção é armazenar essas variáveis em outro
HV
, por exemplo,main::CGI_ENV
se você não quiser sobrescrever valores existentes no ambiente atual. Nesse caso, você simplesmente faria$CGI_ENV{key}
.