在如何在C#中使用Unmanaged dll 文章中曾介紹C#中使用C++ 函式庫(DLL)的方法, 不過此方法僅適用於C-Style export 出來的函式, 不行用在類別(Class)中的成員函式.
但其實很多API都是設計成C++的類別, 因此本文將介紹如何在C# 使用C++ Class的函式.
但其實很多API都是設計成C++的類別, 因此本文將介紹如何在C# 使用C++ Class的函式.
其步驟如下:
- 設計一個C-Style function 介面, 透過指標存取物件的實體 – 由於C#中並沒有提供包裝C++ Class的方法, 所以我們必須將類別中public function重新包裝成C-Style function, 而一個物件的實體, 其實可以透過指標(*)作存取, C#中指標的使用也可透過IntPtr.
- 利用C# PInvoke機制呼叫C-Style function– 作法可參考如何在C#中使用Unmanaged dll
- 釋放unmanaged object記憶體 –由於C#的回收機制,不會自動回收unmanaged object記憶體, 所以我們必須透過Dispose/Finalize手動回收, 其作法可參考C#記憶體管理
範例如下:
原始C++類別程式碼
#ifdef CSHAPEAPI_EXPORTS #define CSHAPE_API __declspec(dllexport) #else #define CSHAPE_API __declspec(dllimport) #endif class CSHAPE_API CShape { public: CShape(){}; virtual ~CShape(){}; public: float GetRectangleArea(float w ,float h) { return w*h; }; float GetTriangleArea(float b ,float h) { return 0.5*b*h;}; };
另外設計一個C-Style function 介面, 包裝要使用的C++ 類別
CShapeBridge.h
#include "CShapeAPI.h" #ifdef __cplusplus extern "C" { #endif extern CSHAPE_API CShape* CreateInstance(); extern CSHAPE_API void DisposeInstance(CShape* pShapeInstance); extern CSHAPE_API float GetRectangleArea(CShape* pShapeInstance, float w ,float h); extern CSHAPE_API float GetTriangleArea(CShape* pShapeInstance, float b ,float h); #ifdef __cplusplus } #endif
CShapeBridge.cpp
#include "stdafx.h" #include "CShapeBridge.h" extern "C" CSHAPE_API CShape* CreateInstance() { return new CShape(); } extern "C" CSHAPE_API void DisposeInstance(CShape* pShapeInstance) { if(pShapeInstance != NULL) { delete pShapeInstance; pShapeInstance = NULL; } } extern "C" CSHAPE_API float GetRectangleArea(CShape* pShapeInstance, float w ,float h) { return pShapeInstance->GetRectangleArea(w, h); } extern "C" CSHAPE_API float GetTriangleArea(CShape* pShapeInstance, float b ,float h) { return pShapeInstance->GetTriangleArea(b, h); }
利用PInvoke機制呼叫C-Style function , 並釋放unmanaged object記憶體
ShapeWrapper.cs using System; using System.Runtime.InteropServices; namespace ShapeTest { public class ShapeWrapper : IDisposable { #region PInvokes [DllImport("CShapeAPI.dll")] private static extern IntPtr CreateInstance(); [DllImport("CShapeAPI.dll")] private static extern void DisposeInstance(IntPtr pShapeInstance); [DllImport("CShapeAPI.dll")] private static extern float GetRectangleArea(IntPtr pShapeInstance, float w, float h); [DllImport("CShapeAPI.dll")] private static extern float GetTriangleArea(IntPtr pShapeInstance, float b, float h); #endregion #region Members // Variable to hold the C++ class's this pointer private IntPtr m_pNativeObject; #endregion Members public ShapeWrapper() { // We have to Create an instance of // this class through an exported function this.m_pNativeObject = CreateInstance(); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool bDisposing) { if (this.m_pNativeObject != IntPtr.Zero) { // Call the DLL Export to dispose this class DisposeInstance(this.m_pNativeObject); this.m_pNativeObject = IntPtr.Zero; } if (bDisposing) { // No need to call the finalizer since we've now cleaned // up the unmanaged memory GC.SuppressFinalize(this); } } // This finalizer is called when Garbage collection occurs, but only if // the IDisposable.Dispose method wasn't already called. ~ShapeWrapper() { Dispose(false); } #region Wrapper methods public float GetRectangleArea(float w, float h) { return GetRectangleArea(m_pNativeObject, w, h); } public float GetTriangleArea(float b, float h) { return GetTriangleArea(m_pNativeObject, b, h); } #endregion } }
測試C#程式使用C++ Class DLL
using System; namespace ShapeTest { class Program { static void Main(string[] args) { ShapeWrapper shape = new ShapeWrapper(); float w= 25.0f; float h= 10.0f; Console.WriteLine("Rectangle area = "+shape.GetRectangleArea(w, h)); float b = 30.0f; float h1 = 20.0f; Console.WriteLine("Triangle area = "+ shape.GetTriangleArea(b, h1)); } } }
測試結果:
Rectangle area = 250
Triangle area = 300
另外, MSDN也有提供使用CLR的方法, 請參考包裝原生類別以便讓 C# 使用
參考文章: