COM技術初探(二)

一、COM是一個更好的 C++
   1. COM 是什麼
   2. 從 C++ 到 DLL 再到 COM
      2.1 C++
      2.2 DLL
      2.3 COM

二、COM基礎
   1. COM基本知識
      1.1 返回值HRESULT
      1.2 初識idl
      1.3 IUnkown接口
   2. 一個比較簡單的COM
      2.1 interface.h文件
      2.2 math.h文件
      2.3 math.cpp文件
      2.4 simple.cpp文件
      2.5 Math組件的二進制結構圖
      2.6 小結

三、純手工創建一個COM組件
   1. 從建工程到實現註冊
     1.1 創建一個類型爲win32 dll工程
     1.2 定義接口文件
     1.3 增加註冊功能
      1.3.1 增加一個MathCOM.def文件
      1.3.2 DllRegisterServer()和DllUnregisterServer()
     1.4 MathCOM.cpp文件
     1.5 小結
   2. 實現ISmipleMath,IAdvancedMath接口和DllGetClassObject()
     2.1 實現ISmipleMath和IAdvancedMath接口
     2.2 COM組件調入大致過程
     2.3 DllGetClassObject()實現
     2.4 客戶端
     2.5 小結
   3. 類廠

附錄

A 我對dll的一點認識
一. 沒有lib的dll
   1.1 建一個沒有lib的dll
   1.2 調試沒有lib的dll
二. 帶有lib的dll
   2.1 創建一個帶有lib的dll
   2.2 調試帶有引用但沒有頭文件的dll
三. 帶有頭文件的dll
   3.1 創建一個帶有引出信息頭文件的dll
   3.2 調試帶有頭文件的dll
四. 小結

三、純手工創建一個COM組件

1、從建工程到實現註冊

在這一過程中我們將完成三個步驟:創建dll的入口函數,定義接口文件,實現註冊功能

1.1創建一個類型爲win32 dll工程

創建一個名爲MathCOM的win32 dll工程。
在嚮導的第二步選擇"A smiple dll project"選項。當然如果你選擇一個空的工程,那你自己完成DllMain定義吧。

1.2 定義接口文件

生成一個名爲MathCOM.idl的接口文件。並將此文件加入到剛纔創建的那個工程裏。
//MathCOM.idl文件
// MathCOM.idl : IDL source for MathCOM.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (MathCOM.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
	[
		uuid(FAEAE6B7-67BE-42a4-A318-3256781E945A),
		helpstring("ISimpleMath Interface"),
		object,
		pointer_default(unique)
	]
	interface ISimpleMath : IUnknown
	{
		HRESULT Add([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
		HRESULT Subtract([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
		HRESULT Multiply([in]int nOp1,[in]int nOp2,[out,retval] int * pret);
		HRESULT Divide([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
	};

	[
		uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E),
		helpstring("IAdvancedMath Interface"),
		object,
		pointer_default(unique)
	]
	interface IAdvancedMath : IUnknown
	{
		HRESULT Factorial([in]int nOp1,[out,retval]int * pret);
		HRESULT Fabonacci([in]int nOp1,[out,retval]int * pret);
	};
[
	uuid(CA3B37EA-E44A-49b8-9729-6E9222CAE844),
	version(1.0),
	helpstring("MATHCOM 1.0 Type Library")
]
library MATHCOMLib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	[
		uuid(3BCFE27E-C88D-453C-8C94-F5F7B97E7841),
		helpstring("MATHCOM Class")
	]
	coclass MATHCOM
	{
		[default] interface ISimpleMath;
		interface IAdvancedMath;
	};
};
    
在編譯此工程之前請檢查Project/Setting/MIDL中的設置。正確設置如下圖:


圖1.4 midl的正確設置

在正確設置後,如編譯無錯誤,那麼將在工程的目錄下產生四個

 
文件名 作用
MathCOM.h 接口的頭文件,如果想聲明或定義接口時使用此文件
MathCOM_i.c 定義了接口和類對象以及庫,只有在要使用到有關與GUID有關的東西時才引入此文件,此文件在整個工程中只能引入一次,否則會有重複定義的錯誤
MathCOM_p.c 用於存根與代理
dlldata.c 不明

1.3 增加註冊功能

作爲COM必須要註冊與註銷的功能。

1.3.1 增加一個MathCOM.def文件

DEF文件是模塊定義文件(Module Definition File)。它允許引出符號被化名爲不同的引入符號。

//MathCOM.def文件
; MathCOM.def : Declares the module parameters.

LIBRARY      "MathCOM.DLL"

EXPORTS
	DllCanUnloadNow     @1 PRIVATE
	DllGetClassObject   @2 PRIVATE
	DllRegisterServer   @3 PRIVATE
	DllUnregisterServer	@4 PRIVATE   
DllUnregisterServer 這是函數名稱 @4<――這是函數序號 PRIVATE

接下來大致介紹一下DllRegisterServer()和DllUnregisterServer()。(其他兩個函數的作用將在後面介紹)

1.3.2 DllRegisterServer()和DllUnregisterServer()

DllRegisterServer() 函數的作用是將COM服務器註冊到本機上。

DllUnregisterServer() 函數的作用是將COM服務器從本機註銷。
 
1.4 MathCOM.cpp文件

現在請將 MathCOM.cpp 文件修改成如下:
// MATHCOM.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <objbase.h>
#include <initguid.h>
#include "MathCOM.h"

//standard self-registration table
const char * g_RegTable[][3]={
	{"CLSID//{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}",0,"MathCOM"},
	{"CLSID//{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}//InprocServer32",
	                                                 0,
	                                                 (const char * )-1 /*表示文件名的值*/},
	{"CLSID//{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}//ProgID",0,"tulip.MathCOM.1"},
	{"tulip.MathCOM.1",0,"MathCOM"},
	{"tulip.MathCOM.1//CLSID",0,"{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}"},
};

HINSTANCE  g_hinstDll;

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	g_hinstDll=(HINSTANCE)hModule;
    return TRUE;
}

/*********************************************************************
 * Function Declare : DllUnregisterServer
 * Explain : self-unregistration routine
 * Parameters : 
 * void -- 
 * Return : 
 * STDAPI  -- 
 * Author : tulip 
 * Time : 2003-10-29 19:07:42 
*********************************************************************/
STDAPI DllUnregisterServer(void)
{
	HRESULT hr=S_OK;
	char szFileName [MAX_PATH];
	::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);

	int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
	for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
	{
		const char * pszKeyName=g_RegTable[i][0];
		long err=::RegDeleteKey(HKEY_CLASSES_ROOT,pszKeyName);
		if(err!=ERROR_SUCCESS)
			hr=S_FALSE;
	}

	return hr;
}

/*********************************************************************
 * Function Declare : DllRegisterServer
 * Explain : self Registration routine
 * Parameters : 
 * void -- 
 * Return : 
 * STDAPI  -- 
 * Author : tulip 
 * Time : 2003-10-29 19:43:51 
*********************************************************************/
STDAPI DllRegisterServer(void)
{
	HRESULT hr=S_OK;
	char szFileName [MAX_PATH];
	::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);

	int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
	for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
	{
		const char * pszKeyName=g_RegTable[i][0];
		const char * pszValueName=g_RegTable[i][1];
		const char * pszValue=g_RegTable[i][2];

		if(pszValue==(const char *)-1)
		{
			pszValue=szFileName;
		}

		HKEY hkey;
		long err=::RegCreateKey(HKEY_CLASSES_ROOT,pszKeyName,&hkey);
		if(err==ERROR_SUCCESS)
		{
			err=::RegSetValueEx( hkey,
					pszValueName,
					0,
					REG_SZ,
					( const BYTE*)pszValue,
					( strlen(pszValue)+1 ) );
			::RegCloseKey(hkey);
		}
		if(err!=ERROR_SUCCESS)
		{
			::DllUnregisterServer();
			hr=E_FAIL;
		}

	}
   return hr;
}
						
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
	return CLASS_E_CLASSNOTAVAILABLE;
}

STDAPI DllCanUnloadNow(void)
{
	return E_FAIL;
}
我只是在此文件中加幾個必要的頭文件和幾個全局變量。並實現了 DllRegisterServer()和DllUnregisterServer()。而對於其他兩引出函數我只返回一個錯誤值罷了。

1.5 小結

現在我們的工程中應該有如下文件:
 
文件名 作用
Stdafx.h和stdafx.cpp 預編譯文件
MathCOM.cpp Dll入口函數及其他重要函數定義的地方
MathCOM.def 模塊定義文件
MathCOM.idl 接口定義文件(在1.2後如果編譯的話應該還有四個文件)

好了到現在,我的所謂COM已經實現註冊與註銷功能。

如果在命令行或"運行"菜單下項執行如下"regsvr32 絕對路徑+MathCOM.dll"就註冊此COM組件。在執行完此命令後,請查看註冊表項的HKEY_CLASSES_ROOT/CLSID項看看3BCFE27E-C88D-453C-8C94-F5F7B97E7841這一項是否存在(上帝保佑存在)。

如同上方法再執行一下"regsvr32 -u 絕對路徑+MathCOM.dll",再看看註冊表。
其實剛纔生成的dll根本不是COM組件,哈哈!!!因爲他沒有實現DllGetClassObject()也沒有實現ISmipleMath和IAdvancedMath兩個接口中任何一個。
讓我們繼續前行吧!!!

2、實現ISmipleMath,IAdvancedMath接口和DllGetClassObject()

2.1 實現ISmipleMath和IAdvancedMath接口

讓我們將原來的 CMath 類修改來實現ISmipleMath接口和IAdvancedMath接口。
修改的地方如下:

1) Math.h文件
	/*@**#---2003-10-29 21:33:44 (tulip)---#**@

#include "interface.h"*/

#include "MathCOM.h"//新增加的,以替換上面的東東

class CMath : public ISimpleMath,
			  public IAdvancedMath
{
private:
	ULONG m_cRef;

private:
	int calcFactorial(int nOp);
	int calcFabonacci(int nOp);

public:
	CMath();
	//IUnknown Method
	STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();

	//	ISimpleMath Method
	STDMETHOD (Add)(int nOp1, int nOp2,int * pret);
	STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret);
	STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret);
	STDMETHOD (Divide)(int nOp1, int nOp2,int * pret);

	//	IAdvancedMath Method
	STDMETHOD (Factorial)(int nOp,int *pret);
	STDMETHOD (Fabonacci)(int nOp,int *pret);
};

2) Math.cpp文件
/*@**#---2003-10-29 21:32:35 (tulip)---#**@

#include "interface.h"  */
#include "math.h"


STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{//	這裏這是實現dynamic_cast的功能,但由於dynamic_cast與編譯器相關。
	if(riid == IID_ISimpleMath)
		*ppv = static_cast<ISimpleMath *>(this);
	else if(riid == IID_IAdvancedMath)
		*ppv = static_cast<IAdvancedMath *>(this);
	else if(riid == IID_IUnknown)
		*ppv = static_cast<ISimpleMath *>(this);
	else {
		*ppv = 0;
		return E_NOINTERFACE;
	}

	reinterpret_cast<IUnknown *>(*ppv)->AddRef();	//這裏要這樣是因爲引用計數是針對組件的
	return S_OK;
}

STDMETHODIMP_(ULONG) CMath::AddRef()
{
	return ++m_cRef;
}

STDMETHODIMP_(ULONG) CMath::Release()
{
	ULONG res = --m_cRef;	// 使用臨時變量把修改後的引用計數值緩存起來
	if(res == 0)		// 因爲在對象已經銷燬後再引用這個對象的數據將是非法的
		delete this;
	return res;
}

STDMETHODIMP CMath::Add(int nOp1, int nOp2,int * pret)
{
	 *pret=nOp1+nOp2;
	 return S_OK;
}

STDMETHODIMP CMath::Subtract(int nOp1, int nOp2,int * pret)
{
	*pret= nOp1 - nOp2;
	return S_OK;
}

STDMETHODIMP CMath::Multiply(int nOp1, int nOp2,int * pret)
{
	*pret=nOp1 * nOp2;
	return S_OK;
}

STDMETHODIMP CMath::Divide(int nOp1, int nOp2,int * pret)
{
	*pret= nOp1 / nOp2;
	return S_OK;
}

int CMath::calcFactorial(int nOp)
{
	if(nOp <= 1)
		return 1;

	return nOp * calcFactorial(nOp - 1);
}

STDMETHODIMP CMath::Factorial(int nOp,int * pret)
{
	*pret=calcFactorial(nOp);
	return S_OK;
}

int CMath::calcFabonacci(int nOp)
{
	if(nOp <= 1)
		return 1;

	return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}

STDMETHODIMP CMath::Fabonacci(int nOp,int * pret)
{
	*pret=calcFabonacci(nOp);
	return S_OK;
}

CMath::CMath()
{
	m_cRef=0;
}    
2.2 COM組件調入大致過程
  • 1) COM庫初始化 使用CoInitialize序列函數(客戶端)
  • 2)激活COM(客戶端)
  • 3) 通過註冊表項將對應的dll調入COM庫中(COM庫)
  • 4) 調用COM組件內的DllGetClassObject()函數(COM組件)
  • 5)通過類廠返回接口指針(COM庫)這一步不是必需的
2.3 DllGetClassObject()實現

在MathCOM.cpp里加入下列語句,
#include "math.h"
#include "MathCOM_i.c"
並將MathCOM.cpp裏的DllGetClassObject()修改成如下:
/*********************************************************************
 * Function Declare : DllGetClassObject
 * Explain : 
 * Parameters : 
 * REFCLSID rclsid  -- 
 * REFIID riid -- 
 * void **ppv -- 
 * Return : 
 * STDAPI  -- 
 * Author : tulip 
 * Time : 2003-10-29 22:03:53 
*********************************************************************/
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
	static CMath *pm_math=new CMath;
	if(rclsid==CLSID_MATHCOM)
		return pm_math->QueryInterface(riid,ppv);

	return CLASS_E_CLASSNOTAVAILABLE;
}    
2.4 客戶端

接下來我們寫個客戶端程序對此COM進行測試。
新建一個空的名爲 TestMathCOM 的 win32 Console 工程,將它添加到 MathCOM workspace 中。
在 TestMathCOM 工程裏添加一個名爲 main.cpp 的文件,此文件的內容如下:
//main.cpp文件
#include <windows.h>
#include "../MathCOM.h"//這裏請注意路徑
#include "../MathCOM_i.c"//這裏請注意路徑
#include <iostream>
using namespace std;

void main(void)
{
	//初始化COM庫
	HRESULT hr=::CoInitialize(0);
	ISimpleMath * pSimpleMath=NULL;
	IAdvancedMath * pAdvancedMath=NULL;

	int nReturnValue=0;

	hr=::CoGetClassObject(CLSID_MATHCOM,
 			CLSCTX_INPROC,
			NULL,IID_ISimpleMath,
			(void **)&pSimpleMath);
	if(SUCCEEDED(hr))
	{
		hr=pSimpleMath->Add(10,4,&nReturnValue);
		if(SUCCEEDED(hr))
			cout << "10 + 4 = " <<nReturnValue<< endl;
		nReturnValue=0;
	}

	// 查詢對象實現的接口IAdvancedMath
	hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);	
	if(SUCCEEDED(hr))
	{
		hr=pAdvancedMath->Fabonacci(10,&nReturnValue);
		if(SUCCEEDED(hr))
			cout << "10 Fabonacci is " << nReturnValue << endl;	
	}
	pAdvancedMath->Release();
	pSimpleMath->Release();

	::CoUninitialize();

	::system("pause");
	return ;

}    
關於如何調試dll請參閱附錄A

2.5 小結

到現在我們應該有 2 個工程和 8 個文件,具體如下:
 
工程  文件 作用
MathCOM Stdafx.h 和 stdafx.cpp 預編譯文件
  MathCOM.cpp Dll入口函數及其他重要函數定義的地方
  MathCOM.def 模塊定義文件
  MathCOM.idl 接口定義文件(在1.2後如果編譯的話應該還有四個文件)
  math.h和math.cpp ISmipleMath,IadvancedMath接口的實現類
TestMathCOM Main.cpp MathCOM的客戶端,用於測試MathCOM組件

在此部分中我們已經完成一個可以實用的接近於完整的 COM組件。我們完成了此COM組件的客戶端。如果你已經創建COM實例的話,你可能會發現在此部分的客戶端並不是用CoCreateInstance()來創建COM實例,那是因爲我們還沒有在此COM組件裏實現IClassFactory接口(此接口在下一部分實現)。
通過這個例子,我希望大家明白以下幾點:

  • 1) DllGetClassObject()的作用,請參看COM組件調入大致過程這一節,同時也請將斷點打在DllGetClassObject()函數上,仔細看看他的實現(在沒有實現IClassFactory接口的情況下)和他的傳入參數。
  • 2) 爲什麼在這個客戶端程序裏不使用CoCreateInstance()來創建COM實例而使用CoGetClassObject()來創建COM實例。你可以試着用CoCreateInstance()來創建Cmath,看看DllGetClassObject()的第一參數是什麼?
  • 3) 實現IClassFactory接口不是必需的,但應該說是必要的(如何實現請看下一章)
  • 4) 應掌握DllRegisterServer()和DllUnregisterServer()的實現。
  • 5) 客戶端在調用COM組件時需要那幾個文件(只要由idl文件產生的兩個文件)
3、類廠

附錄

A 我對 dll 的一點認識

目標:寫幾個比較簡單的dll並瞭解**.dll與**.lib的關係。

一:沒有lib的dll

1.1建一個沒有lib的dll
  • 1) 新建一個com_1.cpp文件(注意此dll根本沒有什麼用)
  • 2) 在com_1.cpp寫下下面的代碼
  • 3) 按下F5運行,所有的東西都按確定。
  • 4) 應該出現如下錯誤:
    Linking...
       Creating library Debug/COM_1.lib and object Debug/COM_1.exp
    LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
    Debug/COM_1.exe : fatal error LNK1120: 1 unresolved externals
  • 5)進入 project|setting,在 "C/C++" 屬性框的 "project Options" 裏把
      "/D ''_console''" 修改成"/D ''_WINDOWS''"。
  • 6)進入project|setting,在 "link" 屬性框的 "project Options" 裏增加下
    面的編譯開關 "/dll "
增加的編譯開關大致如下:
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib 
ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:yes 
/pdb:"Debug/COM_1.pdb" /debug /machine:I386 /out:"Debug/COM_1.dll" /implib:"Debug/COM_1.lib" 
/pdbtype:sept    
注意:"/dll"應該與後面的開關之間有一個空格
//com_1.cpp
#include <objbase.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
		HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}
}    
現在可以編譯了,這小片段代碼將會生成一個dll,但這個dll是沒有用的。沒有引出函數和變量。

1.2 調試沒有 lib 的 dll

1) 新建一個工程 Client,工程類型爲 console,將上面創建的 dll copy 到 client 工程目錄下
2) 增加 Client.cpp(代碼見下)到工程 Client 中去
3) 選中 Client 工程,並在 project|setting|debug|Category 下拉框,如圖:


圖1.4 調試

注意這是一種調試 dll 的方法

5) 現在可以在Client和COM_1.dll裏打斷點調試了。
在這裏我們只能調試DllMain()函數,因爲那個dll裏除了就沒別的東西了,下面我開始 增加一點東西。

二:帶有lib的dll

2.1 創建一個帶有lib的dll

我們在原來的基礎上讓上面的代碼產生一個lib了。新的代碼如下:
#include <objbase.h>

extern "C" __declspec(dllexport)  void tulip (void)
{
	::MessageBox(NULL,"ok","I''am fine",MB_OK);
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
	HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}

	return TRUE;
}
在這個dll裏,我們引出一個tulip函數。如果此時我們想要在客戶調用此函數應該用什麼方法呢?

上面的代碼除了生成dll外,他比第一個程序多產生一個lib文件,現在應該知道dll與lib的關係吧。Lib文件是dll輸出符號文件。如果一個dll沒有任何東西輸出那麼不會有對應的lib文件,但只要一個dll輸出一個變量或函數就會相應的lib文件。總的說來,dll與lib是相互配套的。
當某個dll他有輸出函數(或變量)而沒有lib文件時,我們應該怎麼調用 dll 的函數呢?請看下面的方法。

2.2 調試帶有引用但沒有頭文件的 dll

注意:本方法根本沒有用 COM_1.lib 文件,你可以把 COM_1.lib 文件刪除而不影響。
此時的客戶端代碼如果下:
#include <windows.h>

int main(void)
{
	//定義一個函數指針
	typedef void (  * TULIPFUNC )(void);  

	//定義一個函數指針變量
	TULIPFUNC tulipFunc;  

	//加載我們的dll
	HINSTANCE hinst=::LoadLibrary("COM_1.dll");  
	
	//找到dll的tulip函數
	tulipFunc=(TULIPFUNC)GetProcAddress(hinst,"tulip");  

	//調用dll裏的函數
	tulipFunc();   
	
	return 0;
}
對於調用系統函數用上面的方法非常方便,因爲對於User32.dll,GUI32.dll這種dll,我沒有對應的lib,所以一般用上面的方法。

三:帶有頭文件的dll

3.1 創建一個帶有引出信息頭文件的dll

如果用上面的方法調用我們自己創建的dll那太煩了!因爲我們的dll可能沒有像window這樣標準化的文檔。可能過了一段時間後,我們都會忘記dll內部函數的格式。再如當我們把此dll發佈客戶時,那個客戶肯定會在背後罵你的!

這時我們需要一個能瞭解dll引出信息途徑。我創建一個.h文件。繼續我們旅途。
我們的dll代碼只需要修改一點點,代碼如下:
#include <objbase.h>
#include "header.h"//看到沒有,這就是我們增加的頭文件

extern "C" __declspec(dllexport)  void tulip (void)
{
	::MessageBox(NULL,"ok","I''am fine",MB_OK);
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
	HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}

	return TRUE;
}
而 header.h文件只有一行代碼:
extern "C" __declspec(dllexport)  void tulip (void);   
3.2 調試帶有頭文件的dll

而此時我們的客戶程序應該變成如下樣子:(比第二要簡單多了)
#include <windows.h>
#include "../header.h"//注意路徑

//注意路徑,加載 COM_1.lib 的另一種方法是 Project | setting | link 設置裏
#pragma comment(lib,"COM_1.lib")

int main(void)
{
	tulip();//只要這樣我們就可以調用dll裏的函數了

	return 0;
}   
四:小結

今天講了三種 dll 形式,第一種是沒有什麼實用價值的,但能講清楚 dll 與 lib 的關係。我們遇到的情況大多數是第三種,dll 的提供一般會提供 **.lib 和 **.h 文件,而第二種方法適用於系統函數。

希望各位高手指正與交流,

注:今天一時興起,寫了上面的東西,本來我是總結一下有關 COM 的東西,但寫着寫着就成這個樣子,COM 也是從 dll 起步的。 
發佈了14 篇原創文章 · 獲贊 5 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章