顯示具有 DLL 標籤的文章。 顯示所有文章
顯示具有 DLL 標籤的文章。 顯示所有文章

2010年12月9日 星期四

DLL的製作和使用

在程式開發上, 常會將自己寫好的共用函式或共用類別編譯成DLL, 這樣除了可以省記憶體空間外, 也可達到模組化方便讓其他程式共用, 這邊寫了一篇文章來介紹DLL建立的流程,主要分成兩個重點:

1.    DLL建立的步驟
  • 如何建立DLL檔(以C3DMath.dll為例)
  • 如何引用別人的lib檔和h檔(以MainDLLTest.exe為例)

2.    編譯環境的設定
VC中可以利用一些巨集指令來設定編譯環境,其優點有:
  • 方便管理檔案
  • 省去人工複製的時間(可以少掉複製一些h檔、 dll檔和lib檔的時間)
  • 將來可用在Auto build機制上


DLL建立的步驟-

1.    建立C3DMaths DLL專案:
開啟VC點選File -->New-->Project-->Win32 Console Application,然後在Name的欄位輸入專案的名稱


 2.    點選Next

3.    選擇DLL,然後按Finish


4.    產生一個標頭檔(C3DMath.h), 並加入下面這段巨集指令, 如下所示:
#ifdef  API_BUILD_DLL
 #define C3DMATHS_CLASS __declspec(dllexport)
#else
 #define C3DMATHS_CLASS __declspec(dllimport)
#endif

上面這段巨集的意思是, 如果定義了API_BUILD_DLL 編譯時就把字串 C3DMATHS_CLASS換成__declspec(dllexport), 否則, 就換成 __declspec(dllimport), 因為C3DMath.h 通常會用在兩個地方:
(1)產生 DLL的專案: 需要匯出 DLL 的資料、函式、類別或類別成員函式,因此要使用__declspec(dllexport)
(2)使用 DLL 的專案(例如exe檔): 此時需要匯入DLL 的資料、函式、類別或類別成員函式, 所以要用 __declspec(dllimport)

5.    在Preprocessor Definitions中定義巨集(API_BUILD_DLL),其作用是讓上面的巨集指令知道此DLL專案要使用__declspec(dllexport)

6.    在class的title上加上巨集的定義(C3DMATHS_CLASS), 表示將此class匯出

class  C3DMATHS_CLASS  C3DVector
{

public:
    C3DVector(void);
    ~C3DVector(void);

    C3DVector(float X, float Y, float Z);
    void Set(float X, float Y, float Z);
    float Length(void);
    void Normalize(void);
}

7.    按下Rebuild Sloution 鍵,即可產生C3DMaths.dllC3DMaths.lib

APP使用DLL的步驟
程式中使用DLL需要有.h檔, .lib檔和.dl檔, 其中.h檔和.lib檔必須加入到使用DLL的專案中, 而.dll檔則需要放在應用程式所在的路徑(通常和.exe檔同一路徑), 步驟如下:

1.    建立一隻Console的程式(MainDLLTest.exe)
2.    將C3DMath專案所產生的h檔、lib檔,複製到MainDLLTest的專案底下
3.    引入標頭檔(C3DMath.h)
       #include ".\C3DMaths\C3DMath.h"
4.    連結lib檔
       點選Property Page-->Linker-->Input-->Additional Dependencies,然後輸入lib檔的路徑


5. 複製.dll檔(C3DMaths.dll)到應用程式執行路徑下(MainDLLTest.exe), 即可執行程式來測試DLL的功能



編譯環境的設定
VC中可以利用一些巨集指令來設定你的編譯環境,不但可方便管理專案,也節省很多人工的時間,尤其在開發大型專案時,自動build的流程就變得很重要。下面介紹的方法,可用來自動產生使用DLL時,所要引用的檔案和資料夾,如此一來可省去人工複製的麻煩和時間。

首先看一下VC項目設置中可以使用的巨集,常用的有:

 1.    設定dll檔的輸出路徑
General -->Output Directory -->  $(PlatformName)\$(ConfigurationName)

2.    設定lib檔的輸出路徑
Linker->Advance->ImportLibrary -->\$(PlatformName)\$(ConfigurationName)/$(TargetName).lib

3.    利用Post Build Event複製檔案
這個功能大多是當你在開發 DLL 專案,編譯完成後,直接將編譯結果複製到會使用的專案目錄中。這樣就不用, 每次還要開檔案總管進行複製了。輸入的指令如下所示

產生專案目錄
mkdir  ".\$(ProjectName)" 

產生Debug或Release目錄
mkdir  ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製lib檔
COPY".\$(PlatformName)\$(ConfigurationName)\$(TargetName).lib"   
       ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製dll檔
COPY "$(TargetPath)" ".\$(ProjectName)\$(PlatformName)\$(ConfigurationName)"

複製h檔
COPY "C3DMatrix.h" ".\$(ProjectName)" 
COPY "C3DVector.h" ".\$(ProjectName)"                                                                                             
COPY "C3DMathLoader.h" ".\$(ProjectName)"        

2009年8月27日 星期四

如何在C#中使用Unmanaged dll

C#中可以透過PInvoke(Platform Invocation Services), 呼叫 DLL 內實作的Unmanaged函式,
其流程可以參考 MSDN - PInvok的流程圖

Managed的程式中使用Unmanaged dll的方法有兩種:
  • 呼叫DLL匯出(dllexport)的函式
  • 呼叫COM元件上的介面
因為, 使用COM Interop的方式有點複雜, 所以本文章只介紹第一種方法, 若要使用匯出的 DLL 函式(C-Style function), 可分成下面幾個步驟:
  • 引用DllImport所在的名子空間: using System.Runtime.InteropServices;
  • 定義類別(例如class PlatformInvoke)包裝要被呼叫的DLL介面(也就是C/C++中的header檔)
  • 使用 DllImport來引用DLL的函式, 並用 staticextern 來標記函式. 此目的在告訴編譯器該方法與實體物件無關.
DllImport的格式語法如下:
//將user32.dll換成要使用的DLL檔 
[DllImport("user32.dll"), SetLastError = true]

//換成所需的方法及參數(需要將Unmanaged類型轉成Managed)
public static extern ReturnType FunctionName(type arg);
如下面範例所示:
using System;
using System.Runtime.InteropServices;

class WrapperTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);

    [DllImport("msvcrt.dll")] 
    publlic static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}


下面表格列出DllImport的屬性欄位

名稱說明
BestFitMapping將 Unicode 字元轉換成 ANSI 字元時, 設定true/false可啟用或停用自動對應行為
CallingConventionCallingConvention 列舉成員, 此欄位的預設值為 WinAPI
CharSet指示用在入口處中的字串參數, 如果未指定 CharSet, 則預設為 CharSet.Auto
EntryPoint指示要呼叫的 DLL 進入點(Entry Point) 的名稱或序數
ExactSpelling控制 DllImportAttribute CharSet 欄位是否會導致 Common Language Runtime 搜尋 Unmanaged DLL 以取得不是指定名稱的進入點名稱.
PreserveSig指定簽章是否為 Unmanaged 程式碼進入點的直接轉譯, 預設此欄位為 true
SetLasrError指示自屬性方法傳回之前, 被呼叫端是否呼叫 SetLastError Win32 API 函式。
ThrowOnUnmappableChar在無法對應的 Unicode 字元轉換為 ANSI "?" 字元時, 啟用或停用例外狀況的擲回

  • Managed 資料型別代替 Unmanaged 資料型別
Managed需要封裝Unmanaged的資料, 下面表格列出UnmanagedManaged的資料型態轉換:
Wtypes.h 中的 Unmanaged 型別Unmanaged C 語言型別Managed 類別名稱
HANALEvoid*System.IntPtr
BYTEunsigned charSystem.Byte
SHORTshortSystem.Int16
WORDunsigned shortSystem.UInt16
INTintSystem.Int32
UINTunsigned intSystem.UInt32
LONGlongSystem.Int32
BOOLlongSystem.Int32
DWORDunsigned longSystem.UInt32
ULONGunsigned longSystem.UInt32
CHARcharSystem.Char
LPSTRchar*System.String 或
System.Text.StringBuilder
LPCSTRConst char*System.String 或
System.Text.StringBuilder
LPWSTRwchar_t*System.String 或
System.Text.StringBuilder
LPCWSTRConst wchar_t*System.String 或
System.Text.StringBuilder
FLOATFloatSystem.Single
DOUBLEDoubleSystem.Double


如果要包裝一個資料結構,
在 C# 中可以使用 StructLayout屬性來表示的結構, 如下面範例說明:

CC#

struct tOTM_Point
{
int X;

int Y;

int R;

};


[Serializable, StructLayout(LayoutKind.Sequential)]
public struct tOTM_Point
{

public int X;

public int Y;

public int R;

}

如果在Struct中有Array的型態, 可以利用MarshalAs 屬性來表示, 如下所示

CC#

struct tOTM_Touch
{
int TouchType;

int TouchState;

tOTM_Point PointArray[2];

};


[Serializable, StructLayout(LayoutKind.Sequential)]
public struct tOTM_Touch
{

public tOTM_TouchType TouchType;

public tOTM_TouchState TouchState;


[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public tOTM_Point[] PointArray;

};



下面我以一個用C寫的OTMUT.dll為例子, 說明如何將Unmanaged dll轉成Managed程式碼:


C
#ifdef  OTMUT_EXPORTS
#define OTMUT_API __declspec(dllexport)
#else
#define OTMUT_API __declspec(dllimport)
#endif


struct tOTM_Point
{
int X;

int Y;

int R;
};

struct tOTM_Touch
{
int TouchType;

int TouchState;

tOTM_Point PointArray[2];
};

typedef void (_stdcall* TouchFunc) (tOTM_Touch Touch);

OTMUT_API void RegisterTouchFunc(TouchFunc Func);


C#
using System;
using System.Runtime.InteropServices;


namespace OTMUTInterop
{
[Serializable,
StructLayout(LayoutKind.Sequential)]
public struct tOTM_Point
{   
public int X;

public int Y;

public int R;

};

[Serializable,
StructLayout(LayoutKind.Sequential)]
public struct tOTM_Touch
{    
public tOTM_TouchType TouchType;

public tOTM_TouchState TouchState;

[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 2)]
public tOTM_Point[] PointArray;

};

delegate void
TouchFunc(tOTM_Touch touch);

class OTMUT
{       
[DllImport("OTMUT.dll",
CallingConvention =
CallingConvention.StdCall)]
public static extern void
RegisterTouchFunc(TouchFunc func);

}
}



參考文章 :
MSDN -
使用 Unmanaged DLL 函式
MSDN - 使用平台叫用封送處理資料

2009年5月15日 星期五

什麼是DLL?

DLL 是動態連結程式庫, DLL提供一種方法,讓處理序 (Process) 呼叫不是可執行程式碼部分的函式。函式的可執行程式碼位於 DLL 裡,它包含一或多個已編譯、連結的函式,並且儲存在與使用它們的處理序不同的地方。 例如, 我們開發的Canvas, Gesture Handler, Rubik's Cube...等一些應用程式, 都會利用OTMUT.dll來開發Touch/Gesture的相關功能。 這有助於提升程式碼重複使用及有效率的記憶體使用量。

應用程式和 DLL 之間的差異?
雖然 DLL 和應用程式都是可執行程式模組, 但是它們在許多方面卻是不同。對使用者而言,最明顯的差異在於 DLL 不是可以直接執行的程式. 從系統觀點來看,應用程式和 DLL 之間有兩個基本差異:
  • 應用程式本身可以同時在系統中執行多個執行個體,而 DLL 只能有一個執行個體。
  • 應用程式可以擁有如堆疊、全域記憶體、檔案控制代碼 (File Handle) 和訊息佇列等,而 DLL 則不含這些
動態連結與靜態連結的差異?

動態連結(Dynamic Link Libraries)允許可執行模組 (.DLL 或 .EXE 檔) 只包含在執行階段時用來找出 DLL 函式可執行程式碼的所需資訊。
在靜態連結(Static-Link Libraries)中,連結器 (Linker) 會從靜態連結程式庫取得所有參考函式,並且將它與您的程式碼一起放入可執行檔,它的內容除了程式設計者所設計的程式碼之外,還包含了所使用程式庫的程式碼,所以其檔案變得相當大。

使用DLL的優點:
  • 使用較少的Resources
    當多個程式使用相同的Library時, DLL 可以減少重複的程式碼, 並且在執行時期需要時才載入, 這樣可以提升執行的效能。

  • 模組化結構升級
    DLL 有助於提升開發模組化的程式。 這可協助您開發大型的程式需要有多個語言版本或需要模組化結構的程式, 可降低應用程式的複雜度,程式更新維護時較方便。

  • 簡化Deployment Installation
    當函式在 DLL 內需要的更新程式或修正程式, 只要函式的引數和傳回值沒有改變,使用這些函式的應用程式就不需要重新編譯或重新連結。這樣的例子, 常發生在dll 要給 third-party應用程式來使用, 而library需要經常update或fix