EnChart.Core 1.0.1

EnChart.Core - .NET 8 PInvoke Wrapper Library

C++ EnChart API를 .NET에서 사용하기 위한 PInvoke 래퍼 라이브러리입니다.

구조

src/EnChart.Core/
├── Native/
│   ├── Handle/                    # SafeHandle 구현
│   │   ├── IEnChartSafeHandle.cs      # IEnChart 핸들
│   │   ├── IEncSettingsSafeHandle.cs  # IEncSettings 핸들
│   │   └── ...
│   ├── Wrapper/                   # 기능 래퍼
│   │   ├── IEnChartWrapper.cs         # IEnChart 기능
│   │   ├── IEncSettingsWrapper.cs     # IEncSettings 기능
│   │   └── ...
│   └── NativeLibraryLoader.cs     # 크로스플랫폼 DLL 로더
├── EnChartInstance.cs             # 고수준 인스턴스 관리
├── BitmapEventArgs.cs             # 이벤트 데이터
└── EnChart.Core.csproj

설계 패턴: SafeHandle + Wrapper

1. SafeHandle (생성/소멸만 담당)

목적: 네이티브 리소스의 안전한 생명주기 관리

책임:

  • 네이티브 인스턴스 생성 (Create 메서드)
  • 네이티브 인스턴스 소멸 (ReleaseHandle 오버라이드)
  • GC 통합 (SafeHandleZeroOrMinusOneIsInvalid 상속)

예제:

public class IEnChartSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [DllImport("EnChart.Native")]
    private static extern IntPtr IEnChart_Create(...);

    [DllImport("EnChart.Native")]
    private static extern void IEnChart_Destroy(IntPtr handle);

    public static IEnChartSafeHandle Create(...)
    {
        var handle = new IEnChartSafeHandle();
        IntPtr ptr = IEnChart_Create(...);
        handle.SetHandle(ptr);
        return handle;
    }

    protected override bool ReleaseHandle()
    {
        if (!IsInvalid)
        {
            IEnChart_Destroy(handle);
        }
        return true;
    }
}

2. Wrapper (기능 메서드 담당)

목적: 네이티브 기능을 .NET 스타일로 노출

책임:

  • SafeHandle을 사용한 PInvoke 호출
  • 에러 처리 및 예외 변환
  • 이벤트 및 콜백 관리
  • 매개변수 마샬링

예제:

public class IEnChartWrapper : IDisposable
{
    private IEnChartSafeHandle _handle;

    [DllImport("EnChart.Native")]
    private static extern short IEnChart_ZoomIn(IEnChartSafeHandle handle, short f_sync);

    public IEnChartWrapper(string configFile, uint width, uint height)
    {
        _handle = IEnChartSafeHandle.Create(configFile, width, height);
        // Setup...
    }

    public void ZoomIn(bool sync = false)
    {
        short result = IEnChart_ZoomIn(_handle, (short)(sync ? 1 : 0));
        if (result < 0)
        {
            throw new InvalidOperationException($"Failed to zoom in. Error: {result}");
        }
    }

    public void Dispose()
    {
        _handle?.Dispose();
    }
}

현재 구현된 래퍼

IEnChart (메인 차트 인터페이스)

SafeHandle: IEnChartSafeHandle

  • Create(configFile, width, height) - 인스턴스 생성
  • ReleaseHandle() - Shutdown + Destroy

Wrapper: IEnChartWrapper

  • 이벤트: BitmapUpdated, ProgressUpdated
  • 메서드: ZoomIn/Out, Move*, SetAutoUpdate, Update, Render
  • 서브 인터페이스: GetSettings(), GetLayerMgrHandle()

IEncSettings (설정 인터페이스)

SafeHandle: IEncSettingsSafeHandle

  • 주의: IEnChart가 소유하므로 ownsHandle = false
  • 별도 Destroy 불필요

Wrapper: IEncSettingsWrapper

  • SetWindow / GetWindow - 윈도우 크기
  • SetLonLat / GetLonLat - 경위도
  • SetScale / GetScale - 축척

사용 예제

기본 사용법

using EnChart.Core.Native.Wrapper;

// 1. 인스턴스 생성
var chart = new IEnChartWrapper("chart.cfg", 1024, 768);

// 2. 이벤트 구독
chart.BitmapUpdated += (sender, e) =>
{
    Console.WriteLine($"Frame: {e.Width}x{e.Height}, {e.Data.Length} bytes");
};

// 3. 자동 렌더링 설정
chart.SetAutoUpdate(100); // 100ms 주기

// 4. 줌/이동
chart.ZoomIn();
chart.MoveGeo(126.9780, 37.5665); // 서울

// 5. Settings 사용
var settings = chart.GetSettings();
settings.SetScale(50000);
var (lon, lat) = settings.GetLonLat();

// 6. 정리
chart.Dispose();

고수준 API (EnChartInstance)

using EnChart.Core;

// EnChartInstance는 IEnChartWrapper를 감싸서 더 편리한 API 제공
var instance = new EnChartInstance("chart.cfg", 1024, 768);

instance.BitmapUpdated += (s, e) => { /* ... */ };
instance.SetRenderingCycle(100);

// Settings 프로퍼티로 직접 접근
var (lon, lat) = instance.Settings.GetLonLat();
instance.Settings.SetScale(50000);

instance.Dispose();

새로운 래퍼 추가 방법

1. Native wrapper 먼저 구현

native/EnChart.Native/ixxx_wrapper.h/cpp 생성 (Native README 참조)

2. SafeHandle 구현

// Native/Handle/IXxxSafeHandle.cs
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;

namespace EnChart.Core.Native.Handle;

public class IXxxSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private const string DllName = "EnChart.Native";

    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr IXxx_Create(...);

    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
    private static extern void IXxx_Destroy(IntPtr handle);

    public IXxxSafeHandle() : base(true) // true = owns handle
    {
    }

    public static IXxxSafeHandle Create(...)
    {
        var handle = new IXxxSafeHandle();
        IntPtr ptr = IXxx_Create(...);
        handle.SetHandle(ptr);

        if (handle.IsInvalid)
        {
            throw new InvalidOperationException("Failed to create IXxx");
        }

        return handle;
    }

    protected override bool ReleaseHandle()
    {
        if (!IsInvalid)
        {
            IXxx_Destroy(handle);
        }
        return true;
    }
}

3. Wrapper 구현

// Native/Wrapper/IXxxWrapper.cs
using EnChart.Core.Native.Handle;
using System.Runtime.InteropServices;

namespace EnChart.Core.Native.Wrapper;

public class IXxxWrapper : IDisposable
{
    private const string DllName = "EnChart.Native";
    private IXxxSafeHandle _handle;
    private bool _disposed = false;

    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
    private static extern short IXxx_SomeMethod(IXxxSafeHandle handle, int param);

    public IXxxWrapper(...)
    {
        _handle = IXxxSafeHandle.Create(...);
    }

    public void SomeMethod(int param)
    {
        ThrowIfDisposed();
        short result = IXxx_SomeMethod(_handle, param);
        if (result < 0)
        {
            throw new InvalidOperationException($"Failed. Error: {result}");
        }
    }

    private void ThrowIfDisposed()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(IXxxWrapper));
    }

    public void Dispose()
    {
        if (_disposed) return;
        _handle?.Dispose();
        _disposed = true;
        GC.SuppressFinalize(this);
    }
}

주의사항

1. SafeHandle vs IntPtr

SafeHandle 사용 시기:

  • ✅ 인스턴스를 생성하고 소유하는 경우 (IEnChart)
  • ✅ GC가 자동으로 정리해야 하는 경우
  • using 문으로 관리할 경우

IntPtr 사용 시기:

  • ✅ 다른 객체가 소유하는 서브 인터페이스 (IEncSettings)
  • ✅ 일시적인 핸들 전달

2. 서브 인터페이스 핸들

IEnChart가 소유하는 서브 인터페이스(Settings, LayerMgr)는:

  • ownsHandle = false로 SafeHandle 생성
  • ReleaseHandle()에서 아무것도 하지 않음
  • IEnChart가 소멸될 때 함께 해제됨
public class IEncSettingsSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public IEncSettingsSafeHandle() : base(false) // false!
    {
    }

    protected override bool ReleaseHandle()
    {
        // Do nothing - IEnChart owns this
        return true;
    }
}

3. 콜백 및 GCHandle

콜백 델리게이트는 반드시 GCHandle로 고정:

private GCHandle _callbackHandle;

private void SetupCallback()
{
    MyCallback callback = OnCallback;
    _callbackHandle = GCHandle.Alloc(callback); // GC로부터 보호

    NativeMethod(_handle, callback);
}

public void Dispose()
{
    if (_callbackHandle.IsAllocated)
    {
        _callbackHandle.Free();
    }
}

4. 스레드 안전성

네이티브 콜백은 별도 스레드에서 호출될 수 있으므로:

private void OnCallback(...)
{
    try
    {
        // 예외를 네이티브로 전파하지 않도록 try-catch
        MyEvent?.Invoke(this, ...);
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine($"Callback error: {ex}");
    }
}

빌드

dotnet build EnChart.Core.csproj

의존성

  • .NET 8
  • EnChart.Native.dll (네이티브 래퍼)

참조

Showing the top 20 packages that depend on EnChart.Core.

Packages Downloads
EnChart.Wpf
EnChart WPF Library
3

Version Downloads Last updated
1.0.1 4 06/04/2026
1.0.0 4 05/27/2026