Статьи

Интеграция Node.js с C # DLL

Недавно мне пришлось интегрировать серверное приложение на базе Node.js с C # DLL. Наше программное обеспечение (веб-приложение) предоставляет возможность осуществлять платежи через POS-терминал. Эта последняя управляема через выделенную DLL, которая предоставляет такие интерфейсы, как ExecutePayment (операция, сумма) и так далее. Как я уже упоминал, существует сервер Node.js, который каким-то образом предоставляет функциональность POS (и некоторых других) в качестве REST API. (У выбора для использования Node.js были конкретные причины, которые я бы не хотел сейчас описывать).

Когда вы начинаете с такого начинания, тогда есть разные возможности. Одним из них является использование Edge.js, который позволяет встраивать, ссылаться и вызывать объекты .Net CLR из приложений, основанных на Node.js. Что-то вроде этого:

var hello = require('edge').func({
    assemblyFile: 'My.Edge.Samples.dll',
    typeName: 'Samples.FooBar.MyType',
    methodName: 'MyMethod' // Func<object,Task<object>>
}});

hello('Node.js', function (error, result) { ... });

Edge — очень интересный проект и имеет большой потенциал. На самом деле, я просто попробовал это быстро с простой DLL, и это сработало сразу. Однако при использовании его из моего Node-приложения в node-webkit это не сработало. Я еще не уверен, было ли это связано с node-webkit или самой POS DLL (потому что это может быть COM-доступ и т.д.). Однако, если вам нужны простые интеграции, это может сработать для вас.

Вызов процесса

Второй вариант , который пришел на мой взгляд , заключается в разработке DLL в качестве самодостаточного процесса и вызвать его с помощью Node.js в процессе апи . Оказывается, это довольно просто. Просто подготовьте свое приложение C #, чтобы прочитать его аргументы вызова st, вы можете сделать что-то вроде ..

IntegrationConsole.exe ExecutePayment 1 100

..в «ExecutePayment» с операцией № 1 и суммой 1 €. Консольное приложение C # должно передавать свои возвращаемые значения в STDOUT (вы можете использовать JSON для создания более структурированного формата протокола обмена информацией).
Получив это, вы можете просто выполнить процесс из Node.js и прочитать соответствующий STDOUT:

var process = require('child_process');
...
process.exec(execCmd,
    function (error, stdout, stderr) {
        var result = stdout;

        ...

        writeToResponse(stdout);
    });

execCmd содержит инструкции, необходимые для запуска EXE с необходимыми аргументами вызова.

При таком подходе вы выполняете процесс, он выполняет свою работу, возвращает ответ и завершается. Однако, если по какой-либо причине вам необходимо поддерживать работу процесса для более продолжительного, более интерактивного взаимодействия между двумя компонентами, вы можете обмениваться данными через STDIN / STDOUT процесса.
Ваше консольное приложение C # запускается и прослушивает STDIN.

static void Main(string[] args)
{
    ...
    string line;
    do
    {
        line = Console.ReadLine();
        try
        {
            // do something meaningful with the input
            // write to STDOUT to respond to the caller
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    } while (line != null);
}

На стороне Node.js вы не исполняете свой процесс, а вместо этого порождаете дочерний процесс.

var spawn = require('child_process').spawn;
...
var posProc = spawn('IntegrationConsole.exe', ['ExecutePayment', 1, 100]);

Чтобы получить ответы, вы просто регистрируетесь в STDOUT процесса …

posProc.stdout.once('data', function (data) {
        // write it back on the response object
        writeToResponse(data);
    });

… и вы также можете слушать, когда процесс умирает, чтобы в конечном итоге выполнить некоторую очистку.

posProc.on('exit', function (code) {
        ...
    });

Запись в STDIN процесса также проста:

posProc.stdin.setEncoding ='utf-8';
posProc.stdin.write('...');

Таким образом, вы получаете более интерактивное «взаимодействие с состоянием», когда вы отправляете в EXE-команду команду, которая отвечает (STDOUT) и на основании ответа вы снова реагируете и отправляете какую-то другую команду (STDIN).

Внедрение этого в шаблон запроса / ответа

Чтобы представить все как REST API (на узле), вам нужно обратить внимание на регистрацию обработчиков событий в STDOUT. Предположим, вы делаете что-то вроде

app.post('/someEndpoint',function(req, res){
    posProc = spawn('IntegrationConsole.exe',['ExecutePayment',1,100]);...
    posProc.stdout.on('data',function(data){// return the result of this execution// on the response});}),

app.post('/someOtherEndpoint',function(req, res){...
    posProc.stdout.on('data',function(data){// return the result of this execution// on the response});// write to the stdin of the before created child process
    posProc.stdin.setEncoding ='utf-8';
    posProc.stdin.write('...');});

Я исключил правильную обработку крайних случаев, например, что происходит, если ваш процесс умер раньше и т. Д., Но ключевой момент здесь заключается в том, что вы не можете зарегистрировать свои события с помощью on (..), так как в противном случае вы получите несколько обработчиков событий данных на стандартный выход Таким образом, вы можете либо зарегистрировать и отменить регистрацию события, используя синтаксис removeListener (‘имя события’, обратный вызов), либо использовать более удобный механизм однократной регистрации (как я уже делал в моих примерах в начале статьи):

posProc.stdout.once('data',function(data){// write it back on the response object
            writeToResponse(data);});