2009年12月31日 星期四

用Visual C++製作VB6可使用的dll

用Visual C++製作VB6可使用的dll
作者: Timothy Lin 於 下午10:40
vb6是一套相當好用的程式設計工具,但是它的缺點就是速度慢。所以,如果需要大量運算的時候,就可以使用C++來撰寫這些運算的函式,vb只要呼叫使用就可以了。我在這裡要說明使用Visual C++製作dll給vb6使用的方法,希望對需要用的人有幫助。




我用的軟體是Visual C++ 2005 和 Visual Basic 6.0 Professional


假設我的專案名稱為dlltest


首先,開啟vc++,在「新增專案」中選擇「Win32專案」,在專案精靈中選擇 dll 選項,以及勾選「空專案」,然後按下「完成」,精靈就幫我們製作了一個dll專案檔


因為這個專案檔是空的,所以沒有檔案,我們必須自行新增一個.cpp檔案,一個.h標頭檔還有一個.def定義檔,必須要有這個定義檔,vb6才可以使用這個dll


注意.cpp及.h的檔名必須相同


記得在header檔中include必要的檔案


# include

# include
在cpp檔中include header檔

# include "dlltest.h"
撰寫函式時,加入「WINAPI」這個字,例如:

dlltest.h

int WINAPI fntest(void);

int WINAPI add1(int number);


dlltest.cpp

int WINAPI fntest(void){

return 0;

}

int WINAPI add1(int number){

return number+1;

}



寫完所有函式後,下一個步驟就是撰寫def定義檔
在def中先加上EXPOTS,然後再把每一個函式的「名稱」逐行加上去。
dlltest.def

EXPORTS

fntest

add1

...



--------------------------------------------------------------------------------
接下來就是vb6的部分
在vb的專案中新增一個模組(module)
在這個module裡面使用以下方法來宣告函式
(注意以下是同一行,不要寫成多行)


Declare Function vb使用時的函式名稱 Lib "dll檔名" Alias "dll裡的函式名稱" (參數) As 回傳型態

vb使用時的函式名稱就是在vb中呼叫這個函式時要使用的名稱。

dll檔名就是剛才用vc++製作並放到system32目錄的dll檔名。

dll裡的函式名稱就是剛才那個dll中的函式名稱,如果與第一項相同就不用寫。

參數就是這個函式要用到的參數。
回傳型態就是這個函式的回傳型態。

例如如果我們要呼叫剛才寫的add1的話就可以這樣寫

Module1.bas

Declare Function LongAdd1 Lib "dlltest" Alias "add1" (ByVal n as Long) As Long


Form1.frm

Private Sub Form_Load()

Form1.Caption = LongAdd1(1)

End Sub


要注意的是,vc++裡面的 int 和 long 在 vb裡面都是 long。還有其他參數轉換的問題請見MSDN或上網搜尋。

參考來源:

"用Visual C++製作VB6可使用的dll"
- Timothy's Workspace: 用Visual C++製作VB6可使用的dll (在「Google 網頁註解」中檢視)

串口通訊編程一日通2(Overlapped IO模型)

第一篇初步瞭解串口的大致運作,接下來我們看基本操作

先看串口操作的數據結構:

串口操作有幾個比較重要的Struct

1.Overlapped I/O 異步I/O模型

異步I/O和同步I/O不同,同步I/O時,程序被掛起,一直到I/O處理完,程序才能獲得控制。異步I/O,調用一個函數告訴OS,進行I/O操作,不等I/O結束就立即返回,繼續程序執行,操作系統完成I/O之後,通知消息給你。Overlapped I/O只是一種模型,它可以由內核對像(hand),事件內核對像(hEvent), 異步過程調用(apcs) 和完成端口(I/O completion)實現。

Overlapped數據結構:

typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;



DWORD Internal; 通常被保留,當GetOverlappedResult()傳回False並且GatLastError()並非傳回ERROR_IO_PENDINO時,該狀態置為系統定的狀態。
DWORD InternalHigh; 通常被保留,當GetOverlappedResult()傳回False時,為被傳輸數據的長度。
DWORD Offset; 指定文件的位置,從該位置傳送數據,文件位置是相對文件開始處的字節偏移量。調用 ReadFile或WriteFile函數之前調用進程設置這個成員,讀寫命名管道及通信設備時調用進程忽略這個成員;
DWORD OffsetHigh; 指定開始傳送數據的字節偏移量的高位字,讀寫命名管道及通
信設備時調用進程忽略這個成員;
HANDLE hEvent; 標識事件,數據傳送完成時把它設為信號狀態,調用ReadFile
WriteFile ConnectNamedPipe TransactNamedPipe函數前,調用進程設置這個成員. 相關函數
CreateEvent ResetEvent GetOverlappedResult
WaitForSingleObject CWinThread GetLastError

OVERLAPPED和數據緩衝區釋放問題:
在請求時,不能釋放,只有在I/O請求完成之後,才可以釋放。如果發出多個overlapped請求,每個overlapped讀寫操作,都必須包含文件位置(socket),另外,如果有多個磁盤,I/O執行次序無法保證。(每個overlapped都是獨立的請求操作)。


內核對像(hand)實現:
例子:用overlapped模型讀一個磁盤文件內容。
1.把設備句柄看作同步對象,ReadFile將設備句柄設為無信號。ReadFile 異步I/O字節位置必須在OVERLAPPED結構中指定。
2.完成I/O,設置信息狀態。為有信號。
3.WaitForSingleObject或WaitForMultipleObject判斷
或者異步設備調用GetOverLappedResult函數。

view plaincopy to clipboardprint?
int main()
{
BOOL rc;
HANDLE hFile;
DWORD numread;
OVERLAPPED overlap;
char buf[READ_SIZE];
char szPath[MAX_PATH];
CheckOsVersion();

GetWindowsDirectory(szPath, sizeof(szPath));
strcat(szPath, "\\WINHLP32.EXE");
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Could not open %s\n", szPath);
return -1;
}

memset(&overlap, 0, sizeof(overlap));
overlap.Offset = 1500;

rc = ReadFile(
hFile,
buf,
READ_SIZE,
&numread,
&overlap
);
printf("Issued read request\n");
if (rc)
{
printf("Request was returned immediately\n");
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
printf("Request queued, waiting...\n");
WaitForSingleObject(hFile, INFINITE);
printf("Request completed.\n");
rc = GetOverlappedResult(

參考來源:

"串口通訊編程一日通2(Overlapped IO模型)"
- 串口通讯编程一日通2(Overlapped IO模型) - September - CSDN博客 (在「Google 網頁註解」中檢視)

COMMTIMEOUTS結構 超時設置

2.COMMTIMEOUTS結構 超時設置

COMMTIMEOUTS:COMMTIMEOUTS主要用於串口超時參數設置。COMMTIMEOUTS結構如下:

typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //讀間隔超時
DWORD ReadTotalTimeoutMultiplier;// 讀時間係數
DWORD ReadTotalTimeoutConstant; // 讀時間常量
DWORD WriteTotalTimeoutMultiplier; // 寫時間係數
DWORD WriteTotalTimeoutConstant; // 寫時間常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;


  ReadIntervalTimeout:兩字符之間最大的延時,當讀取串口數據時,一旦兩個字符傳輸的時間差超過該時間,讀取函數將返回現有的數據。設置為0表示該參數不起作用。

  ReadTotalTimeoutMultiplier:讀取每字符間的超時。

  ReadTotalTimeoutConstant:一次讀取串口數據的固定超時。所以在一次讀取串口的操作中,其超時為ReadTotalTimeoutMultiplier乘以讀取的字節數再加上 ReadTotalTimeoutConstant。將ReadIntervalTimeout設置為MAXDWORD,並將ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant設置為0,表示讀取操作將立即返回存放在輸入緩衝區的字符。

  WriteTotalTimeoutMultiplier:寫入每字符間的超時。

  WriteTotalTimeoutConstant:一次寫入串口數據的固定超時。所以在一次寫入串口的操作中,其超時為WriteTotalTimeoutMultiplier乘以寫入的字節數再加上 WriteTotalTimeoutConstant。

一般都會做以下設置:
TimeOuts.ReadIntervalTimeout=MAXDWORD;
// 把間隔超時設為最大,把總超時設為0將導致ReadFile立即返回並完成操作

TimeOuts.ReadTotalTimeoutMultiplier=0;
//讀時間係數

TimeOuts.ReadTotalTimeoutConstant=0;
//讀時間常量

TimeOuts.WriteTotalTimeoutMultiplier=50;
//總超時=時間係數*要求讀/寫的字符數+時間常量

TimeOuts.WriteTotalTimeoutConstant=2000;
//設置寫超時以指定WriteComm成員函數中的

3.DCB結構

DCB (Device Control Block) 設備控制塊

在打開通訊串口後,我們需要對串口進行初始化,比如,波特率、奇偶位、校驗位等,在查詢或者配置這些數據時,都要用DCB進行緩衝,可以調用GetcommState函數獲得當前串口配置,以下是DCB的具體成員:

typedef struct _DCB {// dcb
  DWORD DCBlength; // sizeof(DCB)
  DWORD BaudRate; // current baud rate
  指定當前的波特率
  DWORD fBinary: 1; // binary mode, no EOF check
  指定是否允許二進制模式,
  WINDOWS 95中必須為TRUE
  DWORD fParity: 1; // enable parity checking
  指定奇偶校驗是否允許
  DWORD fOutxCtsFlow:1; // CTS output flow control
  指定CTS是否用於檢測發送控制。
  當為TRUE是CTS為OFF,發送將被掛起。
  DWORD fOutxDsrFlow:1; // DSR output flow control
  指定CTS是否用於檢測發送控制。
  當為TRUE是CTS為OFF,發送將被掛起。
  DWORD fDtrControl:2; // DTR flow control type
  DTR_CONTROL_DISABLE值將DTR置為OFF, DTR_CONTROL_ENABLE值將DTR置為ON, DTR_CONTROL_HANDSHAKE允許DTR"握手",DWORD fDsrSensitivity:1; // DSR sensitivity 當該值為TRUE時DSR為OFF時接收的字節被忽略
  DWORD fTXContinueOnXoff:1; // XOFF continues Tx
  指定當接收緩衝區已滿,並且驅動程序已經發
  送出XoffChar字符時發送是否停止。
  TRUE時,在接收緩衝區接收到緩衝區已滿的字節XoffLim且驅動程序已經發送出XoffChar字符中止接收字節之後,發送繼續進行。
  FALSE時,在接收緩衝區接收到代表緩衝區已空的字節XonChar且驅動程序已經發送出恢復發送的XonChar之後,發送繼續進行。
  DWORD fOutX: 1; // XON/XOFF out flow c

參考來源:

"COMMTIMEOUTS結構 超時設置"
- 串口通讯编程一日通3(COMMTIMEOUTS DCB整理) - September - CSDN博客 (在「Google 網頁註解」中檢視)

2009年12月30日 星期三

程序的自我修改

程序的自我修改
April 30th, 2008 | lonkil | Visual C++ | 發表評論 | trackback
本文目的在於向讀者說明程序進行自我修改的基本方法,並希望可以起到拋磚引玉的作用。
如果讀者有更好的方法或見解,歡迎來信交流E-mail: default_and_default_AT_yahoo.cn

C++代碼
/*//////////////////////////////////////////////////////////////////////////////
This program will modify itself at the running time,
These methods will be very useful in some situations,
Gook Luck!
//////////////////////////////////////////////////////////////////////////////*/
#include
#include
void main()
{
TCHAR Info001[MAX_PATH]=「Welcome to Big Apple!」;
TCHAR Info002[MAX_PATH]=「Welcome to Washington!」;
char temp=(char)0×90;
WORD temp001=0×9090;
DWORD temp002=0×90909090;
PVOID BaseAddressOne=NULL;
PVOID BaseAddressTwo=NULL;
_asm
{
mov BaseAddressOne,offset LabelOne
mov BaseAddressTwo,offset LabelTwo
}
MessageBox(NULL,Info001,「Information」,MB_OK|MB_ICONINFORMATION);
//a kind of method to modify itself
WriteProcessMemory(GetCurrentProcess(),BaseAddressTwo,&temp001,2,NULL);
WriteProcessMemory(GetCurrentProcess(),BaseAddressOne,&temp001,2,NULL);
/*
//Another method to modify itself,this method needs to modify the code section’s
//characteristics in PE file.
_asm
{
mov ebx,BaseAddressOne
mov ecx,BaseAddressTwo
mov dx,temp001
mov [ebx],dx
mov [ecx],dx
}
*/
LabelTwo:
_asm
{
jmp LabelOne
}
_asm
{
nop
nop
nop
}
MessageBox(NULL,Info002,「Information」,MB_OK|MB_ICONINFORMATION);
LabelOne:
_asm
{
jmp Over
}
MessageBox(NULL,Info002,「Information」,MB_OK|MB_ICONINFORMATION);
Over:
return;
}
編譯這個程序,我們發現WriteProcessMemory() 成功修改了程序自身代碼,程序運行正常。
然後我們屏蔽程序中的WriteProcessMemory()調用,用/* */之中的代碼完成自我修改,
運行後會發現系統拋出異常 Access Violation.這是因為PE 中 代碼節的屬性默認為 0×60000020,
20 表示代碼 20000000表示可執行,40000000表示可讀,如果我們在此基礎上加上 0×80000000(可寫)
操作系統的loader在裝載可執行文件時,便會將存放代碼節數據的內存標記為可讀,可寫,可執行。
這樣就不會有異常了。
讀者可使用下面的程序來修改節屬性:

C++代碼
/**************************************************************************************/
//The following code is used to modify characteristics of sections
#include
#include
BOOL ModifyCharacteristicsOfSections (LPCTSTR FileName)
{
DWORD i=0;
HANDLE hDestinationFile=NULL;
TCHAR DestinationPEFile[MAX_PATH];
DWORD NumberOfBytesRead=0; //Number of bytes read
DWORD NumberOfBytesWritten=0; //Number of bytes written
DWORD ImageNtSignature=0; //PE signature
DWORD OffsetOfNewHeader=0;
DWORD NumberOfSections=0;
DWORD SizeOfSectionTable=0; //size of section table
HANDLE hGlobalAllocatedMemory=NULL; //use GlobalAlloc();
PIMAGE_SECTION_HEADER pImageSectionHeader=NULL; //a pointer to IMAGE_SECTION_TABLE
IMAGE_DOS_HEADER ImageDosHeader;
IMAGE_NT_HEADERS ImageNTHeaders;
IMAGE_FILE_HEADER ImageFileHeader;
IMAGE_OPTIONAL_HEADER ImageOptionalHeader;
IMAGE_SECTION_HEADER ImageSectionHeader;
DWORD dwFileSize=0;
RtlZeroMemory(&ImageDosHeader,sizeof(IMAGE_DOS_HEADER));
RtlZeroMemory(&ImageNTHeaders,sizeof(IMAGE_NT_HEADERS));
RtlZeroMemory(&ImageFileHeader,sizeof(IMAGE_FILE_HEADER));
RtlZeroMemory(&ImageOptionalHeader,sizeof(IMAGE_OPTIONAL_HEADER));
RtlZeroMemory(&ImageSectionHeader,sizeof(IMAGE_SECTION_HEADER));
strcpy(DestinationPEFile,FileName);
hDestinationFile=CreateFile(DestinationPEFile,
FILE_WRITE_DATA|FILE_READ_DATA,
FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_ARCHIVE,NULL);
if(hDestinationFile==INVALID_HANDLE_VALUE)
{
// printf(」\nCreateFile() fails!Can’t open file. Please try again!\n」);
// CloseHandle

參考來源:

"上一篇: Windows XP SP3 簡體中文官方http下載地址下一篇: 算法的時間複雜度"
- 程序的自我修改 « Vc爱好者 v3.0 (在「Google 網頁註解」中檢視)

給應用程序加裝「看門狗」[

給應用程序加裝「看門狗」[轉]
January 9th, 2009 lonkil Visual C++ 發表評論 trackback
//最近比較懶,一直沒更新。by lonkil

今天無意中年到這篇文章,很不錯的主意,就轉過來了。

原文:http://blog.csdn.net/bhw98/archive/2004/04/28/19683.aspx

相信大多數的程序員或用戶,在Windows中見到類似於下面的親切而又溫馨的提示信息,都不會感到陌生:

「XXX執行了非法操作,將被關閉。要終止程序,請單擊;要調試程序,請單擊。」或者,「是否向Microsoft發送錯誤報告?,。」

如果這個程序運行在無人值守、需要保持連續工作狀態的場合,而其中的bug又一時難以排除,就需要採取應急措施,消除或減少程序出錯造成的影響。本文討論解決這個問題的辦法。

做過一定硬件開發的人都知道,惡劣的工作環境,帶有缺陷的硬件設計,不完善的算法等內外因素,都可能造成程序「跑飛」,因此專門加裝一個「看門狗」,負責監視程序主體,必要時產生復位中斷,有效地避免設備當機。

「看門狗」的思想,完全可以拿到高級語言編程中來用。基本做法是:設計一個簡單的監視程序做為主進程,將原來的工作程 序作為子進程,由主進程啟動子進程並監視子進程的運行狀態。子進程在發生嚴重錯誤時不彈出本文開始時描述的對話框,而是悄悄退出。主進程發現子進程退出 後,重新啟動子進程。如此反覆。

在具體實現上,下面以VC為例說明:

設置子進程為「靜默模式」
在系統初始化部分(CWinApp或main中的開頭),調用API函數SetErrorMode

SetErrorMode(SEM_NOGPFAULTERRORBOX);保證程序在發生嚴重錯誤時不彈出對話框,無需人工干預,自行退出。

啟動子進程
在主進程中,創建子進程並運行。假定子進程的可執行文件為work.exe,示意性代碼如下

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory( π, sizeof(pi) );
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);

// Start the child process
if (CreateProcess("work.exe", "", NULL, NULL, FALSE, 0, NULL, NULL, &si, π))
{
// success
… …
}CreateProcess有10個參數,看起來挺嚇人,其實並不複雜,很容易理解。最後一個參數會返回子進程的ID和句柄等信息,後面就是對進程ID或句柄進行監視。

監視子進程
定時檢查子進程是否在正常運行。有好幾個API都可以用於對指定ID的進程進行監視,像GetProcessVersion,GetProcessTimes,GetProcessIoCounters等,其中GetProcessVersion最簡單,只有一個參數:

DWORD GetProcessVersion( DWORD ProcessId);當子進程已經退出時,該函數返回0。

更為「專業」的函數是GetExitCodeProcess,它甚至能告訴我們子進程退出的原因:

BOOL GetExitCodeProcess(
HANDLE hProcess, // handle to the process
LPDWORD lpExitCode // termination status
);更進一步的考慮
為增強系統的可靠性,給工作程序加裝「看門狗」,不失為一種可行的技術方案。但如果有兩套用戶界面,看起來就有點不那 麼專業了。可將子進程設計為基於console的應用,不帶用戶界面,所有的信息都通過主進程窗口輸出。主進程CreateProcess的第6個參數需 加入CREATE_NO_WINDOW項,將子進程隱藏起來。這樣從用戶的角度看起來,就像只存在一個應用程序。

另一個問題是,如果用戶關閉主進程,如何同時關閉子進程?用TerminateProcess函數固然能結束子進程,但可能會造成內存洩漏等新問題。最好是主進程向子進程發出結束的消息並進行同步,使子進程能夠從容地退出。
再擴展一下,一個主進程可以同時管理多個子進程。典型的例子是利用多塊網卡進行抓包、分析、處理的系統,將每一塊網卡應用與一個子進程綁定,而主進程負責監視所有的子進程的工作。
上面的討論涉及到進程間通信(IPC)問題。解決的辦法有很多,像file mapping, mailslot, pipe, DDE, COM, RPC, clipboard, socket, WM_COPYDATA等都能達到目的,可根據個人喜好和具體情況採用。

參考來源:

"給應用程序加"
- 给应用程序加装“看门狗”[转] « Vc爱好者 v3.0 (在「Google 網頁註解」中檢視)