2011年12月11日 星期日

如何在C#中使用C++ Class DLL

如何在C#中使用Unmanaged dll 文章中曾介紹C#中使用C++ 函式庫(DLL)的方法, 不過此方法僅適用於C-Style export 出來的函式, 不行用在類別(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++類別程式碼
  1. #ifdef CSHAPEAPI_EXPORTS  
  2.  #define  CSHAPE_API  __declspec(dllexport)  
  3. #else  
  4.  #define  CSHAPE_API  __declspec(dllimport)  
  5. #endif  
  6.   
  7.   
  8. class CSHAPE_API CShape  
  9. {  
  10.   
  11. public:  
  12.  CShape(){};  
  13.  virtual ~CShape(){};  
  14.   
  15. public:  
  16.  float GetRectangleArea(float w ,float h) { return w*h; };    
  17.  float GetTriangleArea(float b ,float h) { return 0.5*b*h;};  
  18. };  

另外設計一個C-Style function 介面, 包裝要使用的C++ 類別

CShapeBridge.h
  1. #include "CShapeAPI.h"  
  2. #ifdef __cplusplus  
  3. extern "C" {  
  4. #endif  
  5.   
  6. extern CSHAPE_API CShape* CreateInstance();  
  7. extern CSHAPE_API void DisposeInstance(CShape* pShapeInstance);  
  8.   
  9. extern CSHAPE_API float GetRectangleArea(CShape* pShapeInstance,  
  10.                                         float w ,float h);    
  11. extern CSHAPE_API float GetTriangleArea(CShape* pShapeInstance,  
  12.                                         float b ,float h);  
  13.   
  14.   
  15. #ifdef __cplusplus  
  16. }  
  17. #endif  

CShapeBridge.cpp
  1. #include "stdafx.h"  
  2. #include "CShapeBridge.h"  
  3.   
  4. extern "C"  CSHAPE_API CShape* CreateInstance()  
  5. {  
  6.  return new CShape();  
  7. }  
  8.   
  9. extern "C"  CSHAPE_API void DisposeInstance(CShape* pShapeInstance)  
  10. {  
  11.  if(pShapeInstance != NULL)  
  12.  {  
  13.   delete pShapeInstance;  
  14.   pShapeInstance = NULL;  
  15.  }  
  16. }  
  17.   
  18. extern "C"  CSHAPE_API float GetRectangleArea(CShape* pShapeInstance,   
  19.                                               float w ,float h)  
  20. {  
  21.   
  22.  return pShapeInstance->GetRectangleArea(w, h);  
  23. }  
  24.   
  25. extern "C"  CSHAPE_API float GetTriangleArea(CShape* pShapeInstance,   
  26.                                              float b ,float h)  
  27. {  
  28.  return pShapeInstance->GetTriangleArea(b, h);  
  29. }  

利用PInvoke機制呼叫C-Style function , 並釋放unmanaged object記憶體

  1. ShapeWrapper.cs  
  2. using System;  
  3. using System.Runtime.InteropServices;  
  4.   
  5. namespace ShapeTest  
  6. {  
  7.     public class ShapeWrapper : IDisposable  
  8.     {  
  9.         #region PInvokes  
  10.         [DllImport("CShapeAPI.dll")]  
  11.         private static extern IntPtr CreateInstance();  
  12.   
  13.         [DllImport("CShapeAPI.dll")]  
  14.         private static extern void DisposeInstance(IntPtr pShapeInstance);  
  15.   
  16.         [DllImport("CShapeAPI.dll")]  
  17.         private static extern float GetRectangleArea(IntPtr pShapeInstance,  
  18.                                                      float w, float h);    
  19.          
  20.         [DllImport("CShapeAPI.dll")]  
  21.         private static extern float GetTriangleArea(IntPtr pShapeInstance,   
  22.                                                     float b, float h);  
  23.         #endregion  
  24.  
  25.         #region Members  
  26.         // Variable to hold the C++ class's this pointer  
  27.         private IntPtr m_pNativeObject;    
  28.         #endregion Members  
  29.   
  30.   
  31.         public ShapeWrapper()  
  32.         {  
  33.             // We have to Create an instance of   
  34.             // this class through an exported function  
  35.             this.m_pNativeObject = CreateInstance();  
  36.         }  
  37.   
  38.         public void Dispose()  
  39.         {  
  40.             Dispose(true);  
  41.         }  
  42.   
  43.         protected virtual void Dispose(bool bDisposing)  
  44.         {  
  45.             if (this.m_pNativeObject != IntPtr.Zero)  
  46.             {  
  47.                 // Call the DLL Export to dispose this class  
  48.                 DisposeInstance(this.m_pNativeObject);  
  49.                 this.m_pNativeObject = IntPtr.Zero;  
  50.             }  
  51.   
  52.             if (bDisposing)  
  53.             {  
  54.                 // No need to call the finalizer since we've now cleaned  
  55.                 // up the unmanaged memory  
  56.                 GC.SuppressFinalize(this);  
  57.             }  
  58.         }  
  59.   
  60.         // This finalizer is called when Garbage collection occurs, but only if  
  61.         // the IDisposable.Dispose method wasn't already called.  
  62.         ~ShapeWrapper()  
  63.         {  
  64.             Dispose(false);  
  65.         }  
  66.  
  67.  
  68.         #region Wrapper methods  
  69.         public float GetRectangleArea(float w, float h)  
  70.         {  
  71.   
  72.             return GetRectangleArea(m_pNativeObject, w, h);  
  73.         }  
  74.   
  75.         public float GetTriangleArea(float b, float h)  
  76.         {  
  77.             return GetTriangleArea(m_pNativeObject, b, h);  
  78.         }  
  79.         #endregion  
  80.     }  
  81. }  

測試C#程式使用C++ Class DLL
  1. using System;  
  2. namespace ShapeTest  
  3. {  
  4.     class Program  
  5.     {  
  6.         static void Main(string[] args)  
  7.         {  
  8.             ShapeWrapper shape = new ShapeWrapper();  
  9.   
  10.             float w= 25.0f;  
  11.             float h= 10.0f;  
  12.             Console.WriteLine("Rectangle area = "+shape.GetRectangleArea(w, h));  
  13.   
  14.   
  15.             float b = 30.0f;  
  16.             float h1 = 20.0f;  
  17.             Console.WriteLine("Triangle area = "+ shape.GetTriangleArea(b, h1));  
  18.   
  19.         }  
  20.     }  
  21. }   


測試結果:
Rectangle area = 250
Triangle area = 300

另外, MSDN也有提供使用CLR的方法, 請參考包裝原生類別以便讓 C# 使用