MFC dll實現(xiàn)C++/CLI dll組件,C#調用的全過程詳解(附完整源碼)
模塊化組件化實現(xiàn)獨立的功能模塊是軟件設計的良好習慣,一般用實現(xiàn)為DLL。普通的DLL對外提供接口是采用導出函數(shù)接口,如果接口數(shù)量不大,只是50個以內,這種方式很適合;如果對外接口有上百個,導出函數(shù)接口就完全破壞了軟件模塊化分層設計的理念,使用接口非常麻煩,此情形采用C++/CLI導出類方式實現(xiàn)比較適合,即核心實現(xiàn)先C++ DLL,然后C++/CLI直接調用C++ DLL導出類,對外第三方工程提供CLI類接口。浮云E繪圖以一個最簡單的繪圖模塊為示例,詳細介紹此方法的實現(xiàn)過程。
一、C++ DLL實現(xiàn)
本文只是為了介紹調用C++ dll導出類實現(xiàn)C++/CLI dll的完整過程,示例程序盡量簡單。先用C++實現(xiàn)一個繪圖組件dll。
C++ dll繪圖主鍵設計構思
1. 繪圖畫布CFyView:CFyView繼承自CWnd,是繪圖畫布窗口,并響應鼠標事件。
2. 繪圖數(shù)據(jù)容器CChart:管理所有業(yè)務數(shù)據(jù),(如需支持控件內滾軸,容器是虛擬畫布)。
3. 曲線CCurve:曲線數(shù)據(jù)和曲線繪制。
C++ dll程序開發(fā)過程
1. 新建工程
選擇C++ Windows 的?MFC動態(tài)鏈接庫? --> 項目取名FyMfcDll,解決方案取名FyDemo --> Dll類型選 使用共享MFC DLL的常規(guī) DLL
2. 創(chuàng)建繪圖窗口類CFyView
新建類CFyView,繼承于CWnd --> 添加窗口屬性變量int m_crBackColor,重載窗口消息OnPaint、OnLButtonDblClk。
class CFyView : public CWnd
{
public:
CFyView();
virtual ~CFyView();
bool Create(HWND hParentWnd);
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
public:
int m_crBackColor;
HWND m_hParentWnd;
CChart m_chart;
};
先創(chuàng)建好繪圖窗口,方便調試繪圖功能。接著往下實現(xiàn)繪圖相關功能,等繪圖功能實現(xiàn)后,再在CFyView里聲明繪圖相關對象,在OnPaint完成繪圖呈現(xiàn)。
3. 創(chuàng)建數(shù)據(jù)管理器類CChart
數(shù)據(jù)管理類主要定義了曲線Curve對象集合、曲線標題、標題顯示位置屬性,以及添加、刪除曲線,設置標題位置、繪圖Draw等函數(shù)。
//作者:浮云E繪圖,專業(yè)付費定制各類CAD/Viso等繪圖編輯器、曲線控件等軟件
//QQ:316868127
class CChart
{
public:
CChart();
virtual ~CChart();
void AddCurve(CCurve* curve);
void RemoveCurve(CCurve* curve);
void ClearCurves();
void Draw(CDC* dc);
void SetTitlePos(int x, int y);
void GetTitlePos(int& x, int& y);
CString GetTitle();
public:
CString m_sTitle;
CPtrArray m_curves;
int m_iTitleX;
int m_iTitleY;
};
實際商業(yè)項目中,數(shù)據(jù)管理容器管理著大量的業(yè)務對象,就曲線控件而言,比如曲線網(wǎng)格、坐標軸、圖例等等數(shù)據(jù)。
4. 創(chuàng)建曲線類CCurve
曲線類主要定義了數(shù)據(jù)點集合CPoint數(shù)組、曲線名稱、曲線線條寬度、顏色、線型等屬性,主要方法是添加、清空點,以及畫點連線函數(shù)Draw。
class CCurve
{
public:
CCurve(CString name);
virtual ~CCurve();
virtual bool AddPoint(CPoint* point);
virtual void ClearPoints();
virtual void Draw(CDC* dc);
public:
CString m_sName;
int MAX_POINT_COUNT = 100;
CPoint* m_pts = new CPoint[100]; //實際項目,此處創(chuàng)建的數(shù)組個數(shù)由外部程序傳入
int m_nPointCount;
int m_nLineStyle;
int m_nLineWidth;
int m_crLineColor;
};
以上完成了C++ dll示例定義,完整的解決方案(包含4個工程)源碼,在文本底部提供下載鏈接。在些C++ dll時,為了方便快捷的測試,可以先寫一個C#測試工程,直接通過導出函數(shù)方式,測試C++ dll的核心流程。具體實現(xiàn)方式可參考?
C++ dll直接導出函數(shù)測試接口
extern "C" __declspec(dllexport) CFyView * NewFyChart()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return new CFyView();
}
extern "C" __declspec(dllexport) void DeleteFyChart(CFyView* fyView)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
delete fyView;
}
extern "C" __declspec(dllexport) bool CreateFyView(CFyView *fyView, HWND hParentWnd)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return fyView->Create(hParentWnd);
}
extern "C" __declspec(dllexport) void LoadFyVDate(CFyView * fyView)
{
CChart* chart = &(fyView->m_chart);
int offx = 50;
CCurve* curve1 = new CCurve(_T("curve 001"));
for (int i = 0; i < 20; i++)
{
curve1->AddPoint(new CPoint(offx, rand()%50));
offx += 10;
}
curve1->m_crLineColor = 0x00FF00;
curve1->m_nLineWidth = 2;
chart->AddCurve(curve1);
CCurve* curve2 = new CCurve(_T("curve 001"));
for (int i = 0; i < 40; i++)
{
curve2->AddPoint(new CPoint(offx, 100+rand() % 50));
offx += 10;
}
curve2->m_crLineColor = 0x0;
curve2->m_nLineWidth = 1;
chart->AddCurve(curve2);
fyView->RedrawWindow();
}
extern "C" __declspec(dllexport) void SetFyVBackColor(CFyView * fyView, int color)
{
fyView->m_crBackColor = color;
fyView->RedrawWindow();
}
extern "C" __declspec(dllexport) int GetFyVBackColor(CFyView * fyView)
{
return fyView->m_crBackColor;
}
C#工程直接調用C++ dll測試實例
? ? ? ? private const string LTDLL_NAME = "FyMfcDll.dll";
[DllImport(LTDLL_NAME, EntryPoint = "NewFyChart", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr NewFyChart();
[DllImport(LTDLL_NAME, EntryPoint = "DeleteFyChart", CallingConvention = CallingConvention.Cdecl)]
public static extern void DeleteFyChart(IntPtr chart);
[DllImport(LTDLL_NAME, EntryPoint = "CreateFyView", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CreateFyView(IntPtr fyView, IntPtr hParentWnd);
[DllImport(LTDLL_NAME, EntryPoint = "LoadFyVDate", CallingConvention = CallingConvention.Cdecl)]
public static extern void LoadFyVDate(IntPtr chart);
[DllImport(LTDLL_NAME, EntryPoint = "SetFyVBackColor", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetFyVBackColor(IntPtr chart, int color);
[DllImport(LTDLL_NAME, EntryPoint = "GetFyVBackColor", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetFyVBackColor(IntPtr chart);
IntPtr m_fyChart;
public Form1()
? ? ? ? {
? ? ? ? ? ? InitializeComponent();
? ? ? ? }
private void Form1_Load(object sender, EventArgs e)
{
m_fyChart = NewFyChart();
CreateFyView(m_fyChart, this.panel1.Handle);
LoadFyVDate(m_fyChart);
}
private void btnChangeBk_Click(object sender, EventArgs e)
? ? ? ? {
int bkclr = GetFyVBackColor(m_fyChart);
SetFyVBackColor(m_fyChart, 0xADD8E6);
? ? ? ? }
二、C++/CLI dll、 C++ dll、C#之數(shù)據(jù)類型的對應關系
?3種開發(fā)語言常用的數(shù)據(jù)類型對應關系如下表所示:

需要特別說明的是:
1>需要函數(shù)形參返回int值時,C++定義為int&,CLI定義為int%,C#定義為ref int。如GetChartSize(int& w, int& h) --> GetChartSize(int% w, int% h) --> GetChartSize(ref int w, ref int h);
2>需要顏色定義時,C++不支持Color只能用int,CLI支持Color,C#支持Color,因為int和Color類型的內存不一樣,需要中間層CLI把int 與 Color相互轉換(DateTime類型也是如此)。
3>CLI類加^,等同于C#的類,都是托管對象。
那么C++對象是怎么與C#對應上的呢?他們無法直接對應,得通過CLI層轉換。得在CLI層同時維護C++類指針與CLI類引用,并且讓他們關聯(lián)上。對C#使用CCurve^托管對象,對內維護C++類CCurve*指向的非托管內存。具體請看下一節(jié)CLI工程源碼。
4> 屬性的實現(xiàn)。C++使用setXXX和GetXXX函數(shù),CLI使用property即set和get方法,C#直接訪問變量。
三、C++/CLI DLL實現(xiàn)
C++/CLI dll程序開發(fā)過程
1. 新建C++/CLI工程
選擇 CLR類庫(.NET Framework) --> 項目取名 FyClr
2. 添加FView類,作為C++窗口類CFyView的對應
// FView.h文件
using namespace System;
namespace fy_dll
{
public ref class FView : public IDisposable
{
public:
FView();
virtual ~FView();
};
}
// FView.cpp文件
#include "pch.h"
#include "FView.h"
namespace fy_dll
{
FView::FView()
{}
FView::~FView()
{}
}
1> FView是托管類,帶ref關鍵字;
2>FView類繼承于IDisposable,作為C++資源對象(窗口)的對應類,請繼承于IDisposable
3>請定義一個namespace 命名空間,在C#工程好引用。
C++對象與C#對應
? ? //FView.h
? ? #include "../FyMfcDll/CFyView.h"
? ? public ref class FView : public IDisposable
? ? {
public:
FView();
property Color BackColor {
Color get();
void set(Color value);
}
private:
CFyView* m_cFyView;
? ? ......
? ? };
? ? //-----------------------------------------------
? ? //FView.cpp
? ? FView::FView()
{
m_cFyView = new CFyView();
}
Color FView::BackColor::get()
{
return UInt2Color(m_cFyView->m_crBackColor);
}
void FView::BackColor::set(Color value)
{
m_cFyView->m_crBackColor = Color2UInt(value);
}
是在ref class FView類里定義了一個C++對象指針,維系這與C++ dll的關系。
C++/CLI dll封裝C++ dll過程和編譯選項
1> 回到C++工程,把需要導出的類定義加關鍵詞
class ?CFyView : public CWnd ?--> 改成
class __declspec(dllexport) ?CFyView : public CWnd
2>CLI工程編譯選項設置
????????a> 項目屬性 ->? 配置屬性 -> 高級 ->??MFC的使用,設為"在共享DLL中使用MFC";
????????b> 項目屬性 ->? 配置屬性 -> 高級 ->??字符集,設為"使用多字節(jié)字符集;(如果文字亂碼)
? ? ? ? b> 項目屬性 ->? 配置屬性 -> 調試 -> 命令,可設為調用此dll的應用程序EXE;(非常重要,方便調試代碼)
? ? ? ? c> 項目屬性 ->? 配置屬性 -> 調試 ->?調試器類型,設為"混合(.NET Framework)";(方便調試代碼)
????????d> 項目屬性 ->? 鏈接器?-> 常規(guī)?->?附加庫目錄,設為"$(SolutionDir)$(Configuration)\";
? ? ? ? e> 項目屬性 ->? 鏈接器?-> 輸入 ->?附加依賴項,設為"$FyMfcDll.lib";(因為本項目CLI工程調用了C++ FyMfcdll工程dll)
? ? ? ? ?f> 項目屬性 ->? C/C++ -> 代碼生成 ->?結構成員對齊,設為"4字節(jié)";(如果C++工程與CLI工程對通一結構體內存數(shù)據(jù)錯了,兩個工程需設置相同的結構成員對齊方式)
? ? ? ? ?g> 項目屬性 ->? 配置屬性 -> 高級 -> 公共語言運行時支持,設為"公用語言運行時支持(/clr)";(C++工程與CLI工程都要設置)

3. CLI工程,繼續(xù)新建托管類FChart對應C++工程的CChart類,新建托管類FCurve對應C++工程導出類CCurve。
C++ 工程類如CChart定義改成class __declspec(dllexport) ?CChart? -->?CLI工程FChart類新增有必要對外提供接口訪問的屬性和成員函數(shù)(實現(xiàn)是調用C++指針對象執(zhí)行)。工程源碼底部下載。
四、第三方調用C++/CLI DLL示例
1. 新建項目,選 C# Windows 桌面? --> Windows 窗體應用(.NET Framework) --> 取名“TestClrDllDemo”
2. 引用上文開發(fā)的Clr組件。1>添加代碼 using yf_dll? --> 2> 點擊“引用”,右鍵“添加引用”,瀏覽clr組件生成目錄,選擇fyClr.dll。
3. C#工程直接調用CLI DLL里的各種類。

?解決方案可以運行,包含4個工程:(
)https://download.csdn.net/download/fyhhack/879915601. MFC dll工程 FyMfcDll,C++ MFC實現(xiàn)核心業(yè)務和繪圖。
2. C++/CLI dll工程 fyClr ,封裝 MFC dll各功能,以導出類方式對外提供接口,直接C#訪問。
3. C# Winform測試MFC dll工程 TestMfcdllDemo
4. C# Winform測試C++/CLI dll工程 TestClrDllDemo