EnChart.Core 1.0.0
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 (네이티브 래퍼)
참조
- SafeHandle 문서
- PInvoke 가이드
- Native 래퍼:
native/EnChart.Native/README.md
Showing the top 20 packages that depend on EnChart.Core.
| Packages | Downloads |
|---|---|
|
EnChart.Wpf
EnChart WPF Library
|
3 |
.NET 8.0
- System.Runtime.InteropServices (>= 4.3.0)