Интеграция C# с другими языками: от P/Invoke до Python и JavaScript

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему C# не живёт в вакууме, и как это работает

Когда я начинал работать с C# лет десять назад, мне казалось, что .NET — это самодостаточная вселенная. Библиотеки, фреймворки, базы данных — всё под рукой. Но потом пришла задача: нужно было использовать старую C++ библиотеку для обработки сигналов. И тут я понял — интеграция C# с другими языками это не роскошь, а необходимость для любого серьёзного проекта.

Сегодня C# — это мультиплатформенный язык с богатой экосистемой, но он не умеет делать всё. Иногда нужно запустить Python-скрипт для машинного обучения, иногда — дёрнуть нативную функцию из Rust для максимальной производительности, а иногда — встроить JavaScript-движок для динамической генерации UI. Хорошая новость: C# умеет дружить практически со всеми.

В этой статье я покажу реальные сценарии взаимодействия C# с другими языками с рабочим кодом, бенчмарками и подводными камнями. Поехали!

P/Invoke: классика жанра для C и C++

Platform Invocation Services (P/Invoke) — это встроенный механизм .NET для вызова функций из нативных DLL. Он появился ещё в первой версии .NET и остаётся самым быстрым способом интеграции с C/C++ кодом.

Базовый пример: вызов функции из C

Допустим, у нас есть нативная библиотека math_helper.dll с функцией:

// math_helper.h
extern "C" __declspec(dllexport) double calculate_power(double base, int exp);

В C# мы подключаем её так:

using System.Runtime.InteropServices;

public class NativeMath
{
    [DllImport("math_helper.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern double calculate_power(double base, int exp);
}

// Использование
double result = NativeMath.calculate_power(2.0, 10);
Console.WriteLine($"2^10 = {result}"); // 1024

Важные моменты при P/Invoke C#:

  • Всегда указывайте CallingConvention — чаще всего Cdecl или StdCall.
  • Для строк используйте MarshalAs(UnmanagedType.LPStr) или LPWStr.
  • Структуры передавайте по ссылке через ref или out.

P/Invoke даёт минимальные накладные расходы — буквально несколько наносекунд на вызов. Если вам нужно интегрировать C# с C++ для высоконагруженных вычислений — это ваш выбор.

C++/CLI: мост между мирами

C++/CLI — это расширение Visual C++, которое позволяет писать код, работающий и в управляемой (.NET), и в неуправляемой среде. Это не отдельный язык, а гибрид, который часто используют для создания обёрток над C++ библиотеками.

Пример обёртки для OpenCV

Допустим, у нас есть C++ функция, обрабатывающая изображение:

// NativeImageProcessor.h
#pragma once
public ref class ImageProcessor
{
public:
    static System::Drawing::Bitmap^ ApplyFilter(System::Drawing::Bitmap^ bmp);
};

Внутри мы вызываем нативный OpenCV, а наружу отдаём управляемый Bitmap. Из C# используем как обычную .NET библиотеку:

using ImageProcessorLib;

var img = new Bitmap("photo.jpg");
var processed = ImageProcessor.ApplyFilter(img);
processed.Save("filtered.jpg");

C++/CLI интеграция даёт полный контроль над памятью и позволяет избежать маршалинга. Минус — код становится платформозависимым и сложнее в отладке.

Python.NET: вызываем Python из C#

Python — король Data Science и машинного обучения. И если ваш проект на C# требует, скажем, запуска нейросети на PyTorch или обработки текста через spaCy, вызов Python из C# через Python.NET — это то, что нужно.

Установка и первый пример

Устанавливаем пакет:

dotnet add package Python.Runtime.NETStandard

Теперь код:

using Python.Runtime;

// Инициализация Python
Runtime.PythonDLL = @"C:\Python311\python311.dll"; // путь к вашей DLL
PythonEngine.Initialize();

using (Py.GIL()) // Global Interpreter Lock
{
    dynamic np = Py.Import("numpy");
    dynamic array = np.array(new double[] { 1.0, 2.0, 3.0 });
    dynamic mean = np.mean(array);
    Console.WriteLine($"Среднее: {mean}"); // 2.0
}

PythonEngine.Shutdown();

Важно: блокировка GIL обязательна, иначе получите краш. Python.NET — зрелая библиотека, но для продакшена используйте её в изолированных процессах или через микросервисы.

Когда это оправдано

  • Быстрый прототип с использованием Python-экосистемы.
  • Интеграция с моделями ML, которые не портируются на .NET.
  • Обработка естественного языка (NLP) через NLTK или spaCy.

Однако помните: каждый вызов Python из C# — это накладные расходы на маршалинг данных и GIL. Для высоконагруженных систем лучше организовать отдельный Python-сервис и общаться через gRPC или REST.

Edge.js: C# и JavaScript в одном процессе

Edge.js позволяет запускать JavaScript (Node.js) прямо из .NET приложения. Это круто, когда нужно использовать npm-пакеты для фронтенда или парсинга.

Пример: вызов Lodash из C#

using EdgeJs;

var func = Edge.Func(@"
    return function (data, callback) {
        var _ = require('lodash');
        var result = _.chunk(data, 2);
        callback(null, result);
    };
");

var result = await func(new object[] { 1, 2, 3, 4, 5, 6 });
Console.WriteLine(string.Join(", ", (object[])result)); // [1,2], [3,4], [5,6]

C# и JavaScript взаимодействие через Edge.js — это асинхронность из коробки. Библиотека запускает Node.js в фоновом процессе и общается с ним через IPC. Идеально для скриптов, где важна скорость разработки.

Минус: Edge.js не поддерживает последние версии Node.js (актуально на 2025 год — 18.x). Для продакшена рассмотрите Jering.Javascript.NodeJS — более современный форк.

Rust FFI: производительность и безопасность

Rust становится всё популярнее для системного программирования. C# Rust FFI позволяет получить скорость C++ с гарантиями безопасности памяти.

Создание Rust-библиотеки для C#

В Cargo.toml:

[lib]
crate-type = ["cdylib"]

Код на Rust:

#[no_mangle]
pub extern "C"                

Блоги

Рекомендаций по книгам