MDI 封装DLL 子窗体,要注意的问题:
1) 传递 TApplication
2) 必须要将 Dynamic RTL,Build with runtime packags 勾选编译,否则主窗体无法获取子窗体信息。MDIChildCount 一直为0,不能响应排列等一系列功能。
DLL 方面:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#include "Main.h"
#pragma hdrstop
//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use MEMMGR.LIB
// if any other projects which use the DLL will be performing new or delete
// operations on any non-TObject-derived classes which are exported from the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
//
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//---------------------------------------------------------------------------
#pragma argsused
extern "C" {
__declspec(dllexport) void __stdcall ShowMDIForm(TApplication *app);
__declspec(dllexport) void __stdcall GetDescribe(char *szBuf, int len);
};
TApplication *dllApp = NULL;
//---------------------------------------------------------------------------
void __stdcall ShowMDIForm(TApplication *app)
{
if (dllApp == NULL)
{
dllApp = Application;
Application = app;
}
//TDllForm1 *frm = new TDllForm1(Application->MainForm);
TDllForm1 *frm = new TDllForm1(Application);
frm->Show();
}
//---------------------------------------------------------------------------
void __stdcall GetDescribe(char *szBuf, int len)
{
strncpy(szBuf, "TDllForm1", len);
}
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
//CoInitialize(NULL);
break;
case DLL_PROCESS_DETACH:
//CoUninitialize();
//Application = NULL;
if (dllApp != NULL)
Application = dllApp;
break;
}
return 1;
}
//-------------------------------------------------------------------------
DLL 窗体:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TDllForm1 *DllForm1;
//---------------------------------------------------------------------------
__fastcall TDllForm1::TDllForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TDllForm1::Button1Click(TObject *Sender)
{
ShowMessage("Test mdi child dll");
}
//---------------------------------------------------------------------------
void __fastcall TDllForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
Action = caFree;
}
//---------------------------------------------------------------------------
DLL 中的子窗体 要将 FormType 设置成 fsMDIChild, 在OnClose 事件中 要将 Action 设置成 caFree,保证窗体卸载不会出问题。
主函数部分:
新建个 MDI 工程,在上面搞俩测试按钮,一个建立唯一窗体,另外一个可以建立多个窗体。
声明一个 DLL 字典变量,用来保证 dll 加载一次,并且Close 的时候 卸载掉。
protected:
map<AnsiString, HINSTANCE> map_dll_inst; // Dll 名称,句柄
开头加个 导出函数声明:
typedef void __declspec(dllimport) ShowMDIFormType(TApplication *);
typedef void __declspec(dllimport) GetDescribeType(char *, int);
函数部分,其他都是BCB 自建的,我就不列出来了。
void __fastcall TMainForm::Button1Click(TObject *Sender)
{
CreateDllForm("Form1.dll", false); //建立唯一子窗口
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::Button2Click(TObject *Sender)
{
CreateDllForm("Form1.dll", true); //建立多个子窗口
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::FormClose(TObject *Sender, TCloseAction &Action)
{
// Make sure all the children forms are closed
while (MDIChildCount)
MDIChildren[0]->Free();
for (map<AnsiString, HINSTANCE>::iterator it = map_dll_inst.begin();
it != map_dll_inst.end(); it++)
{
//DWORD dwError;
//HINSTANCE hInst = it->second;
if (FreeLibrary(it->second) == 0)
{
//dwError = GetLastError();
}
}
map_dll_inst.clear();
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::IsMDIChildCreate(const String Name, TForm **Child)
{
String TmpStr = "";
for (int i=0; i<MDIChildCount; i++)
{
TmpStr = MDIChildren[i]->ClassName();
if ( TmpStr == Name) {
if (Child != NULL)
*Child = MDIChildren[i];
//return i;
return true;
}
}
return false;
//return -1;
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::CreateDllForm(AnsiString DllName, bool MutilCreate/*是否创建多个窗体*/) //通过 DLL 创建 Form
{
if (DllName.IsEmpty()) return false;
HINSTANCE hInst;
if (map_dll_inst[DllName] == NULL)
{
hInst = LoadLibrary(DllName.c_str());
//if (hInst == NULL) return false;
}
else {
hInst = map_dll_inst[DllName];
}
if (hInst == NULL) return false;
else
{
ShowMDIFormType *ShowMDIForm = (ShowMDIFormType *)GetProcAddress(hInst, "ShowMDIForm");
GetDescribeType *GetDescribe = (GetDescribeType *)GetProcAddress(hInst, "GetDescribe");
if (ShowMDIForm != NULL && GetDescribe != NULL)
{
char szBuf[100] = {0};
TForm *frm;
GetDescribe(szBuf, 99);
String DllFormTypeName = szBuf;
if (!MutilCreate && IsMDIChildCreate(DllFormTypeName, &frm))
frm->BringToFront();
else
ShowMDIForm(Application);
map_dll_inst[DllName] = hInst;
} // 获取地址完成
else {
//清除 队列 卸载 DLL
FreeLibrary(hInst);
map_dll_inst.erase(DllName);
} // 获取地址不成功
}
}
//---------------------------------------------------------------------------
如果 DLL 和 主窗体 是在一个 工程组里面,一定要分别 设置各自的 Options 设置 使用动态库什么的。直接把 相应工程激活,然后通过菜单栏设置的不行,我吃过这个亏,明明分别激活,然后勾选使用了动态库,运行的时候,主窗体就是识别不了子窗体,布局按钮无效。
参考文档:
<<C++ Builder 5 Developer's Guide>> P943页,"Using MDI Child Forms in DLLs " 上面讲的很清楚。
另附: 工程文件(基于BCB2010)