Mã được quản lý và mã không được quản lý trong .NET

1. Tổng quan về mã được quản lý và mã không được quản lý

1.1. Common Language Runtime (CLR)

CLR là môi trường thực thi của nền tảng .NET, chịu trách nhiệm quản lý việc thực thi chương trình trong thời gian chạy. Các chức năng chính của CLR bao gồm: quản lý bộ nhớ tự động, xác thực bảo mật mã, thực thi mã, và thu gom rác (garbage collection).

1.2. Phân biệt mã được quản lý và mã không được quản lý

Mã được quản lý (managed code) là mã nguồn được thực thi dưới sự giám sát của CLR, nơi bộ thu gom rác (GC) sẽ tự động giải phóng tài nguyên. Ngược lại, mã không được quản lý (unmanaged code) bao gồm các mã gốc như code hệ điều hành, các đối tượng Socket, Stream trong C#, và không thể được giải phóng hoàn toàn bởi GC của CLR. Trong thực tế, các chức năng không được quản lý thường được bao bọc (wrap) bởi các lớp .NET. Chẳng hạn, khi thao tác với tệp tin, người dùng sử dụng lớp System.IO.File thay vì gọi trực tiếp hàm CreateFile của hệ điều hành. Một đặc điểm quan trọng của CLR là tính an toàn bộ nhớ - chương trình chỉ truy cập vào vùng nhớ đã được cấp phát, do đó loại bỏ hoàn toàn các con trỏ treo (dangling pointer).

Luồng xử lý mã được quản lý:

Mã nguồn được viết trong Visual Studio (không chỉ C# mà còn VB.NET, J#, F#...) sẽ được biên dịch thành ngôn ngữ trung gian CIL (Common Intermediate Language, còn gọi là IL). Khi ứng dụng chạy, CLR thực hiện quá trình nạp và phân tích cấu trúc assembly. Khi một phương thức được gọi, CLR biên dịch CIL thành mã máy phù hợp với máy tính hiện tại và lưu vào bộ nhớ cache để sử dụng cho các lần gọi tiếp theo.

Luồng xử lý mã không được quản lý:

Mã không được quản lý chạy bên ngoài môi trường CLR, được biên dịch trực tiếp thành mã máy và thực thi bởi hệ điều hành. Các tác vụ như quản lý bộ nhớ, kiểm tra kiểu dữ liệu, và hỗ trợ bảo mật phải được xử lý thủ công bởi lập trình viên thông qua các Windows API.

So sánh giữa mã được quản lý và mã không được quản lý:

  • Mã được quản lý chạy trên CLR; mã không được quản lý chạy trực tiếp trên máy.
  • Mã được quản lý độc lập với nền tảng và ngôn ngữ, đảm bảo khả năng tương thích xuyên nền tảng; mã không được quản lý phụ thuộc vào hệ điều hành và ngôn ngữ biên dịch.
  • Mã được quản lý được hưởng lợi từ các dịch vụ của CLR (bảo mật, thu gom rác tự động), mang lại độ an toàn và tin cậy cao hơn; mã không được quản lý yêu cầu xử lý thủ công các vấn đề này và dễ gây rò rỉ bộ nhớ, con trỏ không hợp lệ.

2. Sử dụng mã không được quản lý trong C#

Assembly là đơn vị chức năng hoạt động dưới sự kiểm soát của CLR, thường tồn tại dưới dạng file .dll hoặc .exe. Assembly được tạo từ mã được quản lý có thể được thêm vào dự án thông qua tính năng Add Reference trong Visual Studio. Tuy nhiên, các file DLL được tạo từ mã không được quản lý (như C++) không thể được tham chiếu trực tiếp mà cần sử dụng các phương thức như DllImport. Thuộc tính DllImport cho phép gọi các hàm từ DLL bên ngoài:
[AttributeUsage(AttributeTargets.Method)]  
public class DllImportAttribute: System.Attribute  
{   
public DllImportAttribute(string dllName) {…} // Tham số chính là tên DLL   

public CallingConvention CallingConvention; // Quy ước gọi hàm  
public CharSet CharSet; // Bộ ký tự được sử dụng  
public string EntryPoint; // Tên điểm vào của hàm  
public bool ExactSpelling; // Yêu cầu khớp chính xác với tên hàm  
public bool PreserveSig; // Giữ nguyên hay chuyển đổi chữ ký phương thức  
public bool SetLastError; // Lưu trữ giá trị trả về từ FindLastError  
public string Value { get {…} }
}
Thứ tự tìm kiếm DLL khi chạy ứng dụng:
  1. Thư mục chứa file exe của ứng dụng
  2. Thư mục System32
  3. Các thư mục trong biến môi trường PATH
  4. Đường dẫn tùy chỉnh, ví dụ: DllImport(@"C:\\MyApp\\Bin\\Native.dll")

2.1. Sử dụng thư viện động C/C++

Định nghĩa hàm trong C/C++ DLL:
// Tệp tiêu đề và triển khai
extern "C" __declspec(dllexport) int Calculate(int x, int y)
{
    return x * y + 10;
}
Gọi hàm từ C#:
using System;
using System.Runtime.InteropServices;

class Application
{
    [DllImport("calculator.dll", EntryPoint = "Calculate")]
    private static extern int Compute(int x, int y);

    static void Main(string[] args)
    {
        int result = Compute(7, 4);
        Console.WriteLine("Computed value: " + result); // Kết quả: 38
    }
}

2.2. Gọi Win32 API

Windows cung cấp nhiều API cấp thấp có thể được sử dụng trong C#:
using System;
using System.Runtime.InteropServices;

class Utility
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string moduleName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);

    public const uint MB_OK = 0x00000000;
    public const uint MB_ICONINFORMATION = 0x00000040;

    static void Main()
    {
        IntPtr kernelModule = GetModuleHandle("kernel32.dll");
        MessageBox(IntPtr.Zero, "Xin chao!", "Thong bao", MB_OK | MB_ICONINFORMATION);
        FreeLibrary(kernelModule);
    }
}

2.3. Tích hợp với COM Component

COM (Component Object Model) là tiêu chuẩn giao diện nhị phân hướng đối tượng. Định nghĩa interface và lớp triển khai trong C++:
#include <windows.h>

// Định nghĩa interface COM cơ bản
class IProcessor : public IUnknown
{
public:
    virtual HRESULT __stdcall ProcessData(int* input, int* output) = 0;
};

// Triển khai component
class DataProcessor : public IProcessor
{
private:
    ULONG refCount;

public:
    DataProcessor() : refCount(0) {}

    HRESULT __stdcall ProcessData(int* input, int* output)
    {
        *output = (*input) * 2 + 5;
        return S_OK;
    }

    // Triển khai IUnknown
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject)
    {
        if (riid == IID_IUnknown || riid == IID_IProcessor)
        {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }

    ULONG __stdcall AddRef() { return ++refCount; }
    ULONG __stdcall Release() { return --refCount; }
};
Sử dụng COM từ C#:
using System;
using System.Runtime.InteropServices;

class ComClient
{
    [ComImport]
    [Guid("12345678-1234-1234-1234-123456789ABC")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IProcessor
    {
        void ProcessData(ref int input, out int output);
    }

    static void Main()
    {
        Type comType = Type.GetTypeFromCLSID(new Guid("12345678-1234-1234-1234-123456789ABC"));
        dynamic processor = Activator.CreateInstance(comType);

        int inputValue = 10;
        processor.ProcessData(ref inputValue, out int result);
        Console.WriteLine("Output: " + result); // Kết quả: 25
    }
}

Thẻ: .NET CLR C# interop P/Invoke

Đăng vào ngày 23 tháng 6 lúc 06:51