2010年11月24日 星期三

如何在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++類別程式碼
#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# 使用

參考文章:

1 則留言: