Como criar um programa C ++ para permitir a importação de funções em tempo de execução?


10

hoje, eu gostaria de fazer uma pergunta sobre os recursos do C ++ para realizar uma arquitetura de software específica.

Obviamente, usei a pesquisa, mas não encontrei nenhuma resposta diretamente vinculada.

Basicamente, meu objetivo é criar um programa que permita ao usuário modelar e simular sistemas físicos compostos arbitrariamente, por exemplo, um carro. Suponho que tenha uma biblioteca de modelos físicos (funções dentro de classes). Cada função pode ter algumas entradas e retornar algumas saídas, dependendo da descrição física subjacente, por exemplo, um modelo de motor de combustão, um modelo de arrasto aerodinâmico, um modelo de roda, etc.

Agora, a idéia é fornecer ao usuário uma estrutura que lhe permita compor quaisquer funções de acordo com suas necessidades, ou seja, mapear qualquer comportamento físico. A estrutura deve fornecer funcionalidades para conectar as saídas e entradas de diferentes funções. Portanto, a estrutura fornece uma classe de contêiner. Eu o chamo de COMPONENTE, capaz de armazenar um ou vários objetos de modelo (FUNCTION). Esses contêineres também podem conter outros componentes (cf. padrão composto), bem como as conexões (CONNECTOR) entre os parâmetros de função. Além disso, a classe de componente fornece algumas funcionalidades numéricas gerais, como o solucionador de matemática e assim por diante.

A composição das funções deve ser feita durante o tempo de execução. Na primeira instância, o usuário deve poder configurar uma composição importando um XML que define a estrutura da composição. Mais tarde, pode-se pensar em adicionar uma GUI.

Para oferecer uma melhor compreensão aqui, é um exemplo muito simplificado:

<COMPONENT name="Main">
  <COMPONENT name="A">
    <FUNCTION name="A1" path="lib/functionA1" />
  </COMPONENT>
  <COMPONENT name="B">
    <FUNCTION name="B1" path="lib/functionB1" />
    <FUNCTION name="B2" path="lib/functionB2" />
  </COMPONENT>
  <CONNECTIONS>
    <CONNECTOR source="A1" target="B1" />
    <CONNECTOR source="B1" target="B2" />
  </CONNECTIONS>        
</COMPONENT>

Não é necessário aprofundar os recursos da estrutura, porque meu problema é muito mais geral. Quando o código / programa da estrutura é compilado, a descrição do problema físico, bem como as funções definidas pelo usuário, não são conhecidas. Quando o usuário seleciona (via XML ou mais tarde por meio de uma GUI) uma função, a estrutura deve ler as informações da função, ou seja, deve obter as informações dos parâmetros de entrada e saída, a fim de oferecer ao usuário a opção de interconectar as funções.

Conheço os princípios da reflexão e sei que o C ++ não fornece esse recurso. No entanto, tenho certeza de que o conceito de "construção de objetos durante o tempo de execução" é muitas vezes necessário. Como devo configurar minha arquitetura de software em C ++ para alcançar meu objetivo? C ++ é o idioma certo? O que eu negligencio?

Desde já, obrigado!

Cheers, Oliver


C ++ possui ponteiros de função e objetos de função. Todas as funções são compiladas no executável ou estão em bibliotecas dinâmicas (em qual plataforma)?
Caleth

11
A questão é muito ampla no sentido de que normalmente requer um diploma universitário em engenharia elétrica / [automação de projeto eletrônico (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) ou engenharia mecânica / projeto auxiliado por computador (CAD) . Comparativamente, chamar a biblioteca dinâmica C / C ++ é muito fácil, consulte Convenções de chamada C para x86 . Pode ser necessário manipular a pilha (via ponteiro da pilha da CPU) e os valores do registro da CPU.
Rwong #

11
O carregamento dinâmico de funções não é suportado pela linguagem C ++. Você terá que olhar para algo específico da plataforma. Por exemplo, um compilador C ++ no Windows deve suportar DLLs do Windows, que suportam uma forma de reflexão.
Simon B

No C ++, é realmente difícil chamar uma função cuja assinatura (tipos de argumento e retorno) não é conhecida no momento da compilação. Para fazer isso, você precisa saber como as chamadas de função funcionam no nível de montagem da plataforma escolhida.
Bart van Ingen Schenau 1/11

2
A maneira que eu resolveria isso é compilar o código c ++ que cria um intérprete para qualquer idioma que suporte um comando eval. Bang problema resolvido usando c ++. : P Pense por que isso não é bom o suficiente e atualize a pergunta. Ajuda quando os requisitos reais são claros.
Candied_orange #

Respostas:


13

No C ++ padrão puro, você não pode "permitir a importação de funções em tempo de execução"; de acordo com o padrão, o conjunto de funções C ++ é conhecido estaticamente no momento da construção (na prática, no tempo do link) desde que fixado na união de todas as unidades de tradução que compõem seu programa.

Na prática, na maioria das vezes (excluindo sistemas incorporados), seu programa C ++ é executado acima de algum sistema operacional . Leia Sistemas operacionais: três peças fáceis para obter uma boa visão geral.

Vários sistemas operacionais modernos permitem o carregamento dinâmico de plugins . O POSIX especifica notavelmente dlopen& dlsym. O Windows tem algo diferente LoadLibrary(e um modelo de vinculação inferior; você precisa anotar explicitamente as funções envolvidas, fornecidas ou usadas pelos plug-ins). No Linux, você pode praticamente dlopenum monte de plugins (veja meu manydl.cprograma , com paciência suficiente, pode gerar e carregar quase um milhão de plugins). Portanto, o seu XML pode impulsionar o carregamento de plug-ins. Sua descrição de múltiplos componentes / múltiplos conectores me lembra os sinais e slots Qt (que requer um mocpré - processador ; você também pode precisar de algo assim).

A maioria das implementações de C ++ usa nomes diferentes . Por isso, é melhor declarar como extern "C"as funções relacionadas aos plugins (e definidas neles e acessadas pelo dlsymprograma principal). Leia o C ++ dlopen mini HowTo (pelo menos para Linux).

BTW, Qt e POCO são estruturas C ++ que fornecem uma abordagem portátil e de nível superior aos plugins. E a libffi permite chamar funções cuja assinatura é conhecida apenas em tempo de execução.

Outra possibilidade é incorporar algum intérprete, como Lua ou Guile , em seu programa (ou escrever seu próprio, como o Emacs). Esta é uma forte decisão de projeto arquitetônico. Você pode ler Lisp In Small Pieces e Pragmatics da linguagem de programação para obter mais informações.

Existem variantes ou combinações dessas abordagens. Você pode usar alguma biblioteca de compilação JIT (como libgccjit ou asmjit). Você pode gerar em tempo de execução algum código C e C ++ em um arquivo temporário, compilá-lo como um plug-in temporário e carregar dinamicamente esse plug-in (usei essa abordagem no GCC MELT ).

Em todas essas abordagens, o gerenciamento de memória é uma preocupação significativa (é uma propriedade de "programa inteiro" e o que realmente é o "envelope" do seu programa está "mudando"). Você precisará de pelo menos alguma cultura sobre a coleta de lixo . Leia o manual do GC para obter a terminologia. Em muitos casos ( referências circulares arbitrárias em que ponteiros fracos não são previsíveis), o esquema de contagem de referência caro aos ponteiros inteligentes em C ++ pode não ser suficiente. Veja também isso .

Leia também sobre atualização dinâmica de software .

Observe que algumas linguagens de programação, principalmente Common Lisp (e Smalltalk ), são mais amigáveis ​​à idéia de funções de importação em tempo de execução. O SBCL é uma implementação de software livre do Common Lisp e compila o código da máquina a cada interação REPL (e pode até mesmo coletar o código da máquina com lixo, além de salvar um arquivo de imagem principal inteiro, que pode ser facilmente reiniciado mais tarde).


3

Claramente, você está tentando criar seu próprio estilo de software Simulink ou LabVIEW, mas com um componente XML profano.

Na sua forma mais básica, você está procurando uma estrutura de dados orientada a gráficos. Seus modelos físicos são constituídos por nós (você os chama de componentes) e arestas (conectores em sua nomeação).

Não há mecanismo imposto por linguagem para fazer isso, nem mesmo com reflexão; portanto, você precisará criar uma API e qualquer componente que queira executar terá que implementar várias funções e respeitar as regras estabelecidas por sua API.

Cada componente precisará implementar um conjunto de funções para executar ações como:

  • Obter o nome do componente ou outros detalhes sobre ele
  • Obtenha o número de quantas entradas ou saídas o componente expõe
  • Interrogar um componente sobre uma entrada específica nossa saída
  • Conecte entradas e saídas juntas
  • e outros

E isso é apenas para configurar seu gráfico. Você precisará de funções adicionais definidas para organizar como seu modelo é realmente executado. Cada função terá um nome específico e todos os componentes devem ter essas funções. Qualquer coisa específica de um componente deve ser alcançada por meio dessa API, de maneira idêntica de componente para componente.

Seu programa não deve estar tentando chamar essas 'funções definidas pelo usuário'. Em vez disso, deve chamar uma função de 'computação' de propósito geral ou algo parecido em cada componente, e o próprio componente cuida de chamar essa função e transformar sua entrada em sua saída. As conexões de entrada e saída são as abstrações para essa função, é a única coisa que o programa deve ver.

Em resumo, pouco disso é realmente específico para C ++, mas você precisará implementar um tipo de informação do tipo tempo de execução, adaptada ao seu domínio de problema específico. Com cada função definida pela API, você saberá quais nomes de função chamar em tempo de execução e os tipos de dados de cada uma dessas chamadas e apenas usará o carregamento regular de bibliotecas dinâmicas antigas para fazer isso. Isso virá com uma quantidade razoável de clichê, mas isso é apenas parte da vida.

O único aspecto específico de C ++ que você deve ter em mente é o melhor para que sua API seja uma API C, para que você possa usar compiladores diferentes para módulos diferentes, se os usuários estiverem fornecendo seus próprios módulos.

O DirectShow é uma API que faz tudo o que descrevi e pode ser um bom exemplo.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.