iCAx开思网

标题: 【原创】动态连接库与UG二次开发 [打印本页]

作者: 深夜摔键盘    时间: 2004-9-26 16:28
标题: 【原创】动态连接库与UG二次开发
一开始,接课题啥都不懂,跑到这里问了一些问题,许多网友很热心,名字记不住,但心里挺感激他们。现在混到这份上了,从一穷二白,到多少懂了些东西,所以想搞一点东西出来回报社会。呵呵。。。。
UG自身的功能,我不熟悉,但首先要肯定,它们不难。就是个门槛的问题。UG二次开发的门槛算是比较高的。WIN32 API,MFC,DLL,C/C++,要想做高层次的开发,这些多少得懂一些。否则,就老老实实的呆在UG的环境里做个良民。而第一道门槛,就是动态连接库的问题。开发出来的东西,就是以DLL装到UG中的,理解DLL,很有必要。如果觉得没必要,看到这儿,兄弟您就可以打住了。
  
1。静态连接库
  
想先做个比喻来说这个事。
食堂里给1000个人做好了相同的午餐之后,是把它们逐份送到这1000个人手中,还是召集这些人到食堂里吃?
倘若采用第一种方式,那么需要考虑的问题有两个:
(1)  效率问题:送饭的人得够数,倘若派俩小伙去送,送到黄昏也不见得就送完。
(2)  意外问题:假如饭都送出去了,突然发现厨师误在饭菜中放了些亚硝酸盐。及时通知吃饭的兄弟也不是件易事,总会有许多人死翘翘。
倘若采用第二种方式,那么上面这两个问题都可以很容易的得到解决。只不过想吃午饭的个别懒人得动弹动弹走走路了。
  
言归正传:
一开始没有动态连接库,只有静态连接库(再往前了说,也没有静态连接库)。不知道各位有多少人在DOS系统上写过程序,我上学的时候用TC写过hello world,但那时没有人跟俺讲静态连接库是啥,估计和我们老师有点菜有关系,自己不努力看书更有关系。所以现在如果还有同志不懂,那么就往下看看吧。讲的不对的地方,请发悄悄话给俺,偷偷指正。
  
静态连接库:程序员可以将公用模块的代码写成一个一个子程序(函数),编译成obj文件,最后将多个obj文件连接成一个lib文件。当用户程序中需要使用这些公用模块时,就包含它们的lib文件包进去,用户在自己的程序中只需要指定所需要的函数名称,编译器会在编译时,从lib中抽出对应的子程序代码-----复制到用户程序中去。这样搞的最大的好处就是不必一遍遍重写相同功能的代码。这种连接方法就是静态连接。(回想一下,第一次在Turbo C下,printf(“Hello,World!!!”)时的情景,printf从哪来?是从lib中来的)。
  
Lib应该是一群懒惰的程序员想出来的。说白了,就是让编译器替我们copy别人写过的代码!正是因为这种copy,暴露了Lib显而易见的缺点。  
  
缺点1:试想假如你的机器上有1000个程序需要用到同一个静态连接库中的某个函数,由于静态连接库中的函数代码是复制到用户可执行文件中的,所以这1000个可执行文件中就会重复包含1000份相同的代码,硬盘空间就被浪费了。由于DOS系统是单任务系统,静态连接库的使用只表现在硬盘空间的浪费上了。而Windows系统是多任务的,假如把这1000个程序都装到内存中,那么就会装入1000份相同的代码!要是有象硬盘那么大的内存就好了!
  
缺点2:如果1000个程序需要用到同一个静态连接库中的某个函数。后来发现这个函数有问题,需要修正。改就改吧,于是就把静态连接库文件改改,编译一下。完事了么?没有!还得把这1000个程序全部重新编译一遍!
  
看到这儿,再回头看看前面的比喻,是不是有点贴切了。用食堂来比喻,还是不太对,应该用看电影来比喻!!!
作者: 深夜摔键盘    时间: 2004-9-26 16:29
2.动态连接库:
动态连接库(DLL)应该是随着windows95的出现而出现的。它的出现,恰恰弥补了静态连接库的缺点。
动态的连接库在程序设计过程中所起的作用与静态连接库的作用相同,都是提供一大堆函数供不爱写重复代码的程序员们使用,另外操作系统也是以DLL的形式提供接口函数的。
UG OPEN/API是什么?就是一群DLL提供的一大堆函数。
动态连接库与静态连接库,是以不同的手段来达到相同的目的。
静态连接库是将库中的代码复制到它的可执行文件中,首先它要占用多余的磁盘空间。而动态连接库只是在可执行文件运行时,才将库的代码调入内存。如果许多程序共用一个同一个动态连接库时,WINDOWS操作系统会在内存中仅保留一份库代码,通过分页机制,将这份代码映射到不同的进程空间中。这样,无论多少程序使用这样的一个库,库代码实际占用的内存永远只有一份。
  
让那1000个人到食堂里去吃饭吧,安全,而且效率高,浪费的资源亦少。
  
动态连接库是被映射到其他应用程序空间中执行的,它和连接它的应用程序可以看成是一体的。
WINDOWS系统中,每个程序都可以拥有自己的4G空间(实际上也就2G),而动态连接库没有自己的私有空间,它们扮演人民公仆的角色。

  
在32位的windows操作系统中,每个进程都可以有4GB的运行空间。该空间中可以存放操作系统代码,系统DLL,还有用户自己开发的DLL的代码,再除去其他一些乱七八糟的空间,最后剩下的,就是程序自己的私有空间了。说它是私有空间,就是说只有这个运行中的程序(进程)自己可以访问,其他的进程靠边站。DLL是没有私有空间的,它们是公仆啊,所有进程都可以访问它们。所以它们和内存映射文件,全局共享变量等等东西一起呆在内存公共区域中。
  
另外应当注意,DLL中的非共享变量不会被映射到各进程中去,而是被分配在各进程的私有空间中!这是因为DLL只有自己的代码段,而没有数据段。这就是说你在DLL中做了一个全局的数据对象,应用程序加载这个DLL时,会把DLL中的全局对象copy到自己的数据段中。
  
还有,系统DLL要被映射到所有进程中。而用户自己开发的DLL,谁用它,就被映射到相应的进程中。
作者: 深夜摔键盘    时间: 2004-9-26 16:29
WINDOWS的三种DLL结构
在VC的创建工程向导中,我们可有如下3种生成DLL的方式。
一:常规的win32 动态连接库:Win32 Dynamic-Link Library
这是常规的dll,不支持MFC。支持C接口的win32 Api。
空说无益,还是以具体的例子来说要好一些。
我们采用Win32 Dynamic-Link Library方式创建一个名为dllDemo1的工程。在创建工程的step 1中,我们选择“A dll exports some symbols"。
  
这样我们的工程中,就可以得到4个文件。分别是StdAfx.h ,StdAfx.cpp ,dllDemo1.h,  dllDemo1.cpp。
StdAfx.h 和StdAfx.cpp中包含了windows的一些头文件,暂时不管它们,后面两种情况类似。但是要记住不要删除它们,否则,程序编译会出错。
  
把注意力集中在dllDemo1.h,  dllDemo1.cpp上。
下面是它们的代码。
//----------dllDemo1.h------------------
#ifdef DLLDEMO1_EXPORTS
#define DLLDEMO1_API __declspec(dllexport)
#else
#define DLLDEMO1_API __declspec(dllimport)
#endif
//------注意看看上面的宏定义
class DLLDEMO1_API CDllDemo1  
{//这是一个要被输出的类
public:
   CDllDemo1(void);
   double add(double,double);
   double z;
private:
   double x;
   double y;
};
extern DLLDEMO1_API int nDllDemo1;//要被输出的一个变量
DLLDEMO1_API char *fnDllDemo1(void);//要被输出的一个函数
  
//-------dllDemo1.cpp------------
#include "stdafx.h"
#include "dllDemo1.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
     switch (ul_reason_for_call)
   {
     case DLL_PROCESS_ATTACH:
       break;
     case DLL_THREAD_ATTACH:
       break;
     case DLL_THREAD_DETACH:
       break;
     case DLL_PROCESS_DETACH:
       break;
     }
     return TRUE;
}
DLLDEMO1_API int nDllDemo1=19810801;
DLLDEMO1_API char *fnDllDemo1(void)
{
     char *str="我被人家调用了";
   return str;
}
CDllDemo1: :CDllDemo1()
{  
   x=1;
   y=1;
   return;  
}
double CDllDemo1:: add(double,double)
{
   return x+y;
}
  
工程dllDemo1经过编译连接之后,在它的debug或者release文件夹下可以看到dllDemo.dll和dllDemo.lib文件。这个动态连接库要输出3个东西:一个整型的变量,一个类,还有一个函数。
这里先撇去它们的具体实现过程,下面得创建一个基于对话框的MFC exe工程来调用这个dll。
在这个对话框的OK按钮的消息函数里显示dllDemo.dll所输出的那三个东西。
void CImportDllDemo1Dlg: :OnImportDll()  
{
   CString testStr;
   CDllDemo1 DllExportClass;
   DllExportClass.z=DllExportClass.add();
   testStr.Format("CDllDemo.z=%f\n nDllDemo1=%d\n fnDllDemo1():%s",
           DllExportClass.z,nDllDemo1,fnDllDemo1());
   AfxMessageBox((LPCTSTR)testStr);   
}
  
这个测试dllDemo.dll的对话框程序如果想调用DLL中输出的东西,那么它在被编译之前,还需要营造一个调用环境,用白话文说,就是修一条通往DLL的道路,因为前面谈及动态连接库时就讲过了,动态连接库是被放在内存的公共区域中的,要想调用它提供的函数或者变量,那么必须得有一条进入它的道路。书上常把这个过程叫做DLL的加载(load),我觉得不是加载,加载看起来象是把DLL拿到我们的进程中的意思。还是以食堂的例子来做比方,现在我们的食堂就是一个动态连接库,食堂做的饭菜,就是我们要输出的变量或者函数。现在我们应该明白是1000个人去食堂就餐,而不是人家把饭送到这1000个人的手中。食堂就在那里,饭菜就在食堂里,想吃饭就要寻找或者创建一条通往食堂的路,老老实实地走过去吧。
如果从程序员的角度来看,那么这条道路的实现有两种:
(1):政府出钱出人来修路。书上讲这就是“隐式加载”,我想叫它“隐式访问”。
这就象是你在自己的房间里睡到了中午,想吃午餐的时候,一推开房门,就可以看到一条通往食堂的路了,你并没有为这条路的修建出过什么力气。是爱护民众的政府给你修的!对于WINDOWS下的程序员,自然WINDOWS系统就是政府。然后你要做的就是别管这条路是怎么来的,只管去食堂吃饭就是了。当然,政府给你修了路,不会再给你弄个车,怎么走,那是你自己的事。所以程序员在隐式加载DLL时,还需要做一点点工作!!!
在vc++6的工程 |设置(Alt+F7)对话框的Link选项卡中,将伴随dll产生的lib文件加到连接项中。还要在工程文件中#include dll的头文件!!!前面工程dllDemo1经过编译连接之后,在它的debug或者release文件夹下可以看到dllDemo.dll和dllDemo.lib文件,这里,就把dllDemo.lib加到你的MFC对话框工程的连接项中。然后在你的对话框.cpp文件中#include"dllDemo.h“..记着,还要把dll文件放到你的对话框程序所在的文件夹中,不然程序运行之后,会找不到它要使用的DLL在磁盘的哪个地方。如果不怕自己的系统变乱,把DLL放到WINDOWS/System32下也可以。
一定要注意这些文件的路径,要让对话框程序在编译时,能够找到它们!!!
  
进程加载并访问DLL时,Windows寻找相应DLL的次序如下:
  
在当前工作盘中吗?
Windows目录下?;GetWindowsDirectory( )函数可提供该目录的路径名。
Windows系统目录,即System子目录?调用GetSystemDiretory( )函数可获得这个目录的路径名。
DOS的PATH命令中罗列的所有目录?
网络中映象的目录列表中的全部目录?
  
隐式的访问方法还有一种,就是让VC++6自己来做。限于篇幅,不讲这个了。
  
(2)自己出钱去力修路,我的叫法是“显示访问”
假如你不放心政府搞的工程,那么就自立更生,把政府为你做的事情揽过来自己做,使用修路的工具,自己开拓一条通往食堂的路。表现在程序上,就是使用windows提供的3个接口函数来实现调用DLL的过程。
过程如下:
load DLL,WINDOWS32提供了这么一个API:LoadLibrary.
进入你需要的dll导出的函数。也有这么一个API:GetProcAddress.
最后不想再用DLL了,就释放掉。是这么一个API :FreeLibrary.
具体过程如下:
HANDLE hLibrary;
FARPROC lpFunc;
int PortValue;
hLibrary=LoadLibrary("ORTDLL.DLL"); //加载DLL
lpFunc=GetProcAddress(hLibrary,"ortIn"); //检取PortIn函数地址
if(lpFunc!=(FARPROC)NULL) //检取成功则调用
PortValue=(*lpFunc)(port); //读port端口的值
FreeLibrary(hLibrary); //释放占用的内存
  
程序代码见附件:
作者: tari    时间: 2004-9-26 18:46
楼主写的好,望斑竹能加分以鼓励!
作者: zzz    时间: 2004-9-26 20:16
好久没有人写这么长的东西拉,呵呵。
作者: 深夜摔键盘    时间: 2004-9-26 21:32
下面来分析一下Win32 Dynamic-Link Library的程序结构
(1)连接库的入口
当动态连接库首次启动和结束时,操作系统会自动调用连接库的DllMain函数,DllMain函数就是DLL的入口,它对于访问dll的程序是不可见的。这个的名字也可以不叫DllMain,而叫其他的名字,但是它的格式是不变的,必须是下面的格式:
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
  
观察一下DllMain函数,可以看到3个参数。
第一个参数是链接库的运行时的程序句柄(句柄,win32 SDK程序中的概念,windows中每个对象都有这么一个东西,象是身份证)。如果您的连接库中,使用这个句柄作为参数来装载资源时,就表示资源是定义在DLL中的,那么此时您应该将hInstance保存在一个全局变量中,否则,DllMain函数返回之后,就无法再获取这个东西了。
  
第二个参数ul_reason_for_call可以是四个值之一,这4个值就是函数中,switch ....case...结构中的分支常量。它可以说明Windows调用DllMain函数的原因,然后在DllMain函数中分情况对其处理。在下面的讨论中,请记住一个dll可以被访问多次(书上在讲到这里,用的还是“加载”这个词,我依然自作主张改成了“访问”)。参数ul_reason_for_call的4个值的意义如下所述:
DLL_PROCESS_ATTACH:表示动态连接库被一个进程访问了。连接库可以在函数中与之相应的case中对连接库所需要的资源初始化。例如,这类初始化可能包括内存配置。返回TRUE,表示初始化成功;返回FALSE,表示初始化失败,如果是隐式访问,那么访问dll的程序就会出错,不能继续执行。如果显式访问,程序可能会继续运行,但程序中与dll相关的功能就不可以被执行了。
  
DLL_PROCESS_DETACH:意味着某进程不再需要DLL了,“从而提供给链接库自己清除自己的机会”,这依然是书里所说的,很有可能是直译的。我觉得这不是连接库自己清除自己,而是操作系统把它们从该程序的时间片中的页面集里消除掉了(可以回头看看前面讲的windows的分时机制)。在32位的Windows下,这种释放资源的处理并不是严格必须的,但这是一种良好的程序写作习惯。实际上,申请了资源不释放,就是“内存泄露”,不安全的代码!检查检查我们上学时用的教科书里,出现了多少这样的错误。
  
DLL_THREAD_ATTACH:意味着某个程序建立了一个新的线程,windows系统会以此为作为ul_reason_for_call参数调用DllMain,来为线程分配资源。
DLL_THREAD_DETACH:进程中,某线程终止时WINDOWS系统会以此为参数调用DllMain,释放为线程分配的资源。
请注意,如果动态连接库被一个进程访问之前,进程中创建了一个线程,那么当线程结束后,可能会得到一个没有事先对应一个与DLL_THREAD_ATTACH不对称的DLL_THREAD_DETACH情况。比如,程序访问dll时,可能会产生4次DLL_THREAD_DETACH,而只有3次DLL_THREAD_ATTACH。这种不对称的情况会有什么不好,我没有深究过,还应该是资源分配与释放的问题。
  
第三个参数没有什么用处,是windows的保留参数,也就是微软还没想好怎么用这个参数。
  
(2)dll的输出对象:这里所说的对象,不是C++中的对象。它是一个范范的对象,包括变量,函数,C++的类。它们皆可以输出,为访问该DLL的程序所用。前面花了许多篇幅讲的dll入口,其实对程序员来说没什么太重要的意义,而这里谈到的dll输出对象就是dll的精华之所在!
我们去餐厅是为了吃餐厅提供的饭菜,而不是为了看看餐厅的大门而去的。餐厅有没有门,对于吃饭的人来说,无所谓。文章写到这里,终于可以和UG二次开发沾上一些边了。UGNX提供的程序向导中虽然编译连接后得到的也是dll,但是我们在程序代码中却没有看到与DllMain类似的DLL入口函数!!!呵呵,这下知道即使餐厅没有门,也不会影响你去吃饭了吧。这个是后话。
  
前面我们在dllDemo.h中声明了3个欲输出的对象:一个C++类,一个整型变量,还有一个函数。
所有欲输出的对象必须要在dll工程的头文件中声明(事实上,不声明未尝不可,但还是规矩一点好)。dll想让你的程序看到什么,就会在头文件里声明什么,虽然你可以访问它的全部空间,但是有些东西它不想让你看到,也许你也不想看到,就没有必要在dll的头文件中声明了。谁脑子有水,会在餐厅吃饭时,偷偷跑到餐厅后台去看那里的厨具?
Dll中的对象有两种输出方式:
第一种:
是在欲输出的对象前加上输出关键词。请注意,在dllDemo.h中,凡是欲输出的对象,都有一个前缀DLLDEMO1_API 。看看下面dllDemo.h中的宏定义,就是说只要定义了宏DLLDEMO1_EXPORTS,那么,DLLDEMO1_API 就表示__declspec(dllexport),是输出一个对象的关键词,否则就是引入对象的前缀__declspec(dllimport)。
#ifdef DLLDEMO1_EXPORTS
#define DLLDEMO1_API __declspec(dllexport)
#else
#define DLLDEMO1_API __declspec(dllimport)
#endif
那么我们是否定义了DLLDEMO1_EXPORTS宏呢?定义了,Alt+F7,看看你的工程设置对话框的C/C++选项卡中的Preproccessor definitions框中是否有!
  
__declspec(dllimport)在访问DLL输出的进程中调用dll提供的变量而用的关键词。MSDN中讲,引入DLL的函数时,__declspec(dllimport)可以不用,但是用了可以优化代码的执行速度。但引用dll输出的变量时,需要使用__declspec(dllimport)。
譬如,在我们的程序中,如果需要使用dll输出的变量nDllDemo1,那么在访问dll的程序文件中就需要这么做:__declspec(dllimport)int nDllDemo1;
然后才可以正常使用nDllDemo1(但是我没有使用这个关键词,变量还是可以正常使用的。安全起见,还是尽量用)。
  
第二种输出对象的方式:
在工程的source files项目中,创建一个扩展名为.def的文本文件。把你要输出的函数名放在这个文件中,格式如下:
; def文件
LIBRARY dllDemo1;库名
EXPORTS
fnDllDemo1;输出的函数名。
  
对于C++类的输出,需要对obj文件做手脚,还是略过吧/
  
这样,在dll头文件中,就不需要使用__declspec(dllexport)来修饰输出函数了!
(3)生成库文件:
dlDemol程序文件经过编译连接之后可以得到dllDemo1.dll和dllDemo1.lib。前者就是动态连接库文件。后者是供程序开发用的导入库,别把它当开头所讲的静态连接库了!
动态连接库和EXE文件的格式是一样的,都是PE格式。每个PE文件的文件头都有导入表和导出表。导入表中,记录了程序中调用了哪些外来函数的名称和它们所在的dll名称。当PE文件被WINDOWS装入内存之时,windows就会根据导入表中记录的信息,有选择的将一个或多个dll映射到PE文件的进程空间中,这就是“动态连接”的实质。而对于导出表,则是记录这个PE文件中有哪些函数可以供其他程序使用,只有导出表中列出的函数才可以被其他程序所调用。
由于用户开发的dll一般都是供自己用,所以必须要有动态连接库的导入库文件(.lib)。如果dll文件是当作最终应用程序发布的,可以仅发布dll文件。如果是当作插件供其他人做二次开发用的,那么就要为程序员提供dll文件,lib文件,还有相应的头文件。现在,我们在UG中开发时,应该要想想UG给了我们什么东西?是一堆头文件,还有libufun.lib libugopenint.lib两个导入库文件,然后,我们才可以在UG中调用它的功能来实现我们需要实现的东西!
作者: 深夜摔键盘    时间: 2004-9-26 21:32
,windows的这种常规的连接库,暂时就说这么多吧,要是细说起来,还是有好多东西可以讲的。比如进程间的通信,系统钩子的使用等等。这些技术的运用,足可以让你从UG二次开发上升到1。5次开发了。
站在前面的基础上,来理解UG的二次开发,就很简单了。
  
现在就来看看UGNX提供的程序向导生成的一个DLL文件的代码:
#include <stdio.h>
#include <uf.h>
#include <uf_ui.h>
#include "ugnx_code.h"
  
void do_open_api(void)
{
       return;
}
  
extern DllExport void ufusr( char *parm, int *returnCode, int rlen )
{
     /* Initialize the API environment */
     int errorCode = UF_initialize();
     do_open_api();
      
     if ( 0 == errorCode )
     {
         errorCode = UF_terminate();
     }
}
  
extern int ufusr_ask_unload( void )
{
     return( UF_UNLOAD_IMMEDIATELY );
}
  
这个程序可以编译连接后,能够生成一个啥也不会做的dll。因为它从头到尾,除了空壳的函数就是空壳的函数。但它却算得上是一个UG二次开发中的标准代码。这个DLL入口是ufusr,是靠菜单触发而装入内存中的,而且,它一旦运行完毕,系统会自动释放掉它。
  
现在我们可以利用上面所讲过的windows常规的动态连接库的知识来分析一下UG中运行DLL的过程。
注意上面的代码中,UG文档里所言ufusr函数是UG二次开发程序的入口。留心一下它的修饰符,实际上,它只不过是win32 dll中的一个普通的输出函数而已。前面我们讲过一次没有DllMain的动态连接库,UG中开发的dll,就是这个(就是没有门的餐厅)!!!
假如在这个运行在UG中的DLL代码中添加一个DllMain函数,在这个函数入口处设定断点,而后在ufusr函数入口出设断,在VC6中单步跟踪调试一下就可以一目了然了,改后代码如下:
#include<windows.h>
#include <stdio.h>
#include <uf.h>
#include <uf_ui.h>
#include "ugnx_code.h"
  
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
     switch (ul_reason_for_call)
   {
     case DLL_PROCESS_ATTACH:
       break;
     case DLL_THREAD_ATTACH:
       break;
     case DLL_THREAD_DETACH:
       break;
     case DLL_PROCESS_DETACH:
       break;
     }
     return TRUE;
}
extern DllExport void ufusr( char *parm, int *returnCode, int rlen )
{
     /* Initialize the API environment */
     int errorCode = UF_initialize();
  
     if ( 0 == errorCode )
     {
         errorCode = UF_terminate();
     }
}
  
extern int ufusr_ask_unload( void )
{
     return( UF_UNLOAD_IMMEDIATELY );
}
为前面那个空壳程序加了一个DllMain()入口函数,设断单步跟踪时,重点观察它的第二个参数ul_reason_for_call,前面我们说过这个参数可以是4个值之一,这四个值实际上是整型数,以宏的形式表现出来的。它们所表示的值分别如下:
DLL_PROCESS_ATTACH==1
DLL_THREAD_ATTACH==2
DLL_THREAD_DETACH==3
DLL_PROCESS_DETACH==0
  
UG菜单触发这个程序时,首先进入DllMain(),它的第二个参数ul_reason_for_call的值在debug窗中是1,就是DLL_PROCESS_ATTACH,表示动态连接库被一个进程访问了,这个进程就是UG。然后DllMain()返回TRUE,倘若你修改一下代码,使DllMain()返回FALSE,此时UG会报错,但不会影响其他模块使用,从这一点来看,很有些UG是显式访问DLL的意思,如果是隐式访问dll,那么如果DLL出错,整个程序都会停止运行。
  
DllMain函数第一次执行完毕后,系统会调用ufusr函数,在ufusr中,系统会突然第二次访问DllMain函数,此时ul_reason_for_call的值为2,这就是DLL_THREAD_ATTACH,表示系统创建了一个线程,这个线程,自然是在UG 初始化函数 UF_initialize()中搞出来的!!!
  
然后程序回到ufusr中,继续执行完其他指令。而后,系统会调用
extern int ufusr_ask_unload( void )
{
     return( UF_UNLOAD_IMMEDIATELY );
}
最后的最后,系统会再次访问DllMain()函数,此时ul_reason_for_call的值为0,就是DLL_PROCESS_DETACH,表示意味着某进程不再需要DLL了,windows会从该进程空间中释放掉该DLL所占用的空间。
  
以上就是UG菜单触发用户DLL的整个过程。
仿照这种观察手段,可以试试把这个dll放到startup文件夹中,在UG启动过程中,可能会调用10几次DllMain(),多是因为UG创建了其他线程的缘故调用的。
  
--------------------------------------
总结:
(1)UG二次开发中所生成的dll是没有DllMain的动态连接库,这个DllMain()很有可能在程序被编译连接时被UG的库隐秘的添加的,象MFC中的WinMain()函数一样。
(2)UG在装载用户DLL时,采用的应该是显式的装入,就是采用LoadLibrary,FreeLibrary等Windows系统函数实现的。
(3)UG dll的入口ufusr和ufsta,仅仅是一个常规的DLL输出函数。
作者: wgwang    时间: 2004-9-27 12:35
顶啊顶^_^
作者: suye007    时间: 2004-9-27 16:29
好东东!键盘兄==好人啊!呵呵
作者: zhxfan    时间: 2004-9-28 10:57
遇到你是上帝对我的照顾。感激的话不再多说。今生今世你是我的好兄弟!!!!
作者: kangarooxy    时间: 2004-9-30 16:45

作者: 深夜摔键盘    时间: 2004-9-30 17:21
楼上兄弟所言甚是。
时间片这个东西我用的含糊。起因是,书上是说线程是CPU的最小分配单位,每个CPU时刻只能运行一个线程。但是我觉得这个线程的运行也不能是孤立的,它还需要所属进程的一些资源,这些资源也是被WINDOWS内存以页面来组织的,缺了,就要加载,时间片里的东西应该是一个线程的运行所必须的资源。
  
二:动态连接库在VC++6中的第二种生成方式,支持MFC的dll
前面花了很长的篇幅来讲win32常规的dll,因为那个是windows动态连接库的基本结构,它仅支持win32 sdk编程,不支持MFC。理解它,足可以理解UG的二次开发内幕。对于没有多少windows环境程序设计经验的初学者,可能太注重UG的示例中的规范,而对于windows程序设计的大鸟们,这些东西一看就明其里。倘若UG中用户开发的程序运行机制搞清楚了,那么扩展性就强悍了,你可以用数据库,用MFC,用COM,用钩子!倘若不管这些,只是UG让你用啥,你就用啥,太过于温顺了!
VC工程向导中,采用MFC WIZARD(DLL)方式可以生成支持MFC的动态连接库工程,实际上,你只要在win32常规的dll工程中添加一些MFC的头文件和相应的库文件也可以支持MFC。安全起见,还是利用向导来生成模板代码最好!
在MFC AppWizard[dll]下生成DLL文件又有三种方式
(1)常规DLL静态链接到MFC。
(2)常规DLL动态链接到MFC
两者的区别是:前者使用的是MFC的静态链接库,生成的DLL文件长度大,一般不使用这种方式,后者使用MFC的动态链接库,生成的DLL文件长度小,可以回想一下这个帖子最开头所讲过的东西。
动态链接到MFC的规则DLL所有输出的函数应该以如下语句开始:  
AFX_MANAGE_STATE(AfxGetStaticModuleState( ));此语句用来正确地切换MFC模块状态,这个也许就是许多兄弟在UG开发过程中使用MFC时,出现许多莫名其妙的故障得最大原因,在UG二次开发中,这个切换MFC状态模块的语句应该在ufusr/ufsta函数中进行!

(3)就是楼上老兄说得MFC扩展DLL,这种DLL特点是用来建立MFC的派生类,Dll只被用MFC类库所编写的应用程序所调用。这种扩展得MFC DLL和Extension DLLs 和常规得MFC DLLs不一样,它没有一个从CWinApp继承而来的类的对象,编译器默认了一个DLL入口函数DLLMain()作为对DLL的初始化,你可以在此函数中实现自己得dll运行环境得初始化。
作者: pesrty    时间: 2004-10-7 22:18
顶啊
作者: leechongqing    时间: 2004-10-8 15:21

作者: boliang99    时间: 2004-10-11 11:23
顶。
作者: katako    时间: 2004-10-11 17:34
顶,学习中!
作者: 深夜摔键盘    时间: 2004-10-11 19:21
这些天工作重,一直没有精力都没有继续下去把MFC DLL搞完整了。很有点虎头蛇尾的感觉。关键这块内容涉及的知识多了一些,我想等过一段日子,我想把这些东西整理成一份论文的形式(课题凑数的论文),届时会贴出来,让各位指正。
作者: suye007    时间: 2004-10-11 20:50
深夜摔键盘 wrote:
这些天工作重,一直没有精力都没有继续下去把MFC DLL搞完整了。很有点虎头蛇尾的感觉。关键这块内容涉及的知识多了一些,我想等过一段日子,我想把这些东西整理成一份论文的形式(课题凑数的论文),届时会贴出来,让各位指正。

期待中......
"课题凑数的论文"何解?
作者: 深夜摔键盘    时间: 2004-10-11 23:23
呵呵,只是传说中的说法:一个课题要进行鉴定,得有一定数量的论文打底。。。
作者: 深夜摔键盘    时间: 2004-10-13 09:37
可能这么来搞二次开发,有点误入歧途的意思。
重要还是学习UG,设计与加工的知识,熟悉它的API,做有用的事才是正经路数。
作者: suye007    时间: 2004-10-13 10:16
这东西真是挺矛盾,真正应用UG的很少有人熟悉API,或者说不懂C/C++;而搞开发的似乎对应用也只是一知半解,很少在实际加工或设计上锻炼过;
想在两方面都下功夫,工作时间上又不允许......
可能像偶这样两者都不精通的人也不在少数,呵呵
作者: boliang99    时间: 2004-10-14 19:41
好,有些比喻实在上精彩,听君一席话,胜读N年书啊
作者: qiuxiaodong78    时间: 2004-11-19 11:35
好料,多谢了
作者: yesken    时间: 2004-11-19 11:38
谢谢 了
作者: Patton_icax    时间: 2004-11-21 23:39
同意!
作者: huun2000    时间: 2004-11-28 17:41
hao
作者: miraculous    时间: 2004-11-28 21:50
获益匪浅!
以前对动态和静态链接库没有一个整体的认识,看了深夜摔键盘的阐述,对动态链接库概念清晰了很多。对以后的二次开发会有启发的
期待重新整理后的文章
作者: hmjjh    时间: 2004-12-11 22:54

作者: landgrave    时间: 2004-12-14 11:47
发表点个人意见:对Dll讲的很深入,值得学习。只不过觉得有点像对搞房屋装修的人讲房子是如何建成的?
作者: DEEPMOON    时间: 2004-12-20 11:35
good!!
作者: mrliuli    时间: 2004-12-22 21:53
佩服佩服!
一点要向键盘兄学习了
作者: linxue2008    时间: 2004-12-28 14:03
好文章呀,不知道怎么联系楼主?
作者: moshaocong    时间: 2004-12-29 11:45
我用UG创建个对话框,生成三个文件,导入VC++后报错  
e:\my_source\my_first\first_dlg_template.c(458) : fatal error C1010: unexpected end of file while looking for precompiled header directive  
  
这是哪里没有弄好啊?请高手指教,小弟初学,很急,跪求答案~~~~
作者: ice-snow    时间: 2004-12-31 19:59
谢谢键盘哥哥!
作者: thomasw    时间: 2005-4-8 15:46
深夜摔键盘兄弟真是高手啊,我要能达到你那个水平,恐怕我就要入土了,哈哈!多向你学习啊!
  
作者: answer1977    时间: 2005-4-24 11:24
深夜摔键盘 ,万岁!!!!
  
作者: YOWAKAK47    时间: 2005-4-25 15:06
嗯,我除了明白人们都应该在饭堂吃饭之外,别的都不懂~~键盘兄辛苦了
作者: h2023197    时间: 2005-4-25 18:56
顶啊,学习中!
作者: answer1977    时间: 2005-4-25 20:15
请问键盘兄一下,两个工程怎么进行数据交换,困惑ING
作者: slader    时间: 2005-5-11 09:09
好贴
作者: gao264    时间: 2005-5-11 11:44
answer1977 wrote:
请问键盘兄一下,两个工程怎么进行数据交换,困惑ING

工程之间好像不能交换数据,要么整合到一个工程里面,要么用数据文件,一个先写进去,一个再读出来。
作者: wenxiao_violet    时间: 2005-5-12 19:52
高手
皎皎我函数得用发把
给电函数资料
wenxiao_violet@sina.com
qq:252014226
  
xiexie
作者: chjt34    时间: 2005-5-25 15:21
请教键盘兄:我做UG的二次开发,我想通过在对话框中输入,运行后可以将数据用Access数据库来存储,不知道怎么样才能实现?
如果有实例的话,可以发到我的邮箱中:chjt34@163.com
提前致谢!
作者: David2005    时间: 2005-5-26 20:04
谢谢,  深夜摔键盘,你太伟大了太伟大!!!顶!!!!!!!!!!!!!!!!!!!!   
  
作者: andytjy05    时间: 2005-5-31 12:30
hao
作者: UCSICON    时间: 2005-5-31 14:30

作者: spline    时间: 2005-6-2 16:36
这篇文章以前实现过身的,不过后来似乎就不见了。苦苦追寻了很久,终于等到又出山了。
  
可喜可呵!
作者: qqhomec    时间: 2005-7-1 20:29
好厉害!学习原理可以解决很多开发中的问题的!加快开发速度!还有呢!借用其他方面的技术完善把.!啥也不会儿吹牛皮了.!
作者: darren8167    时间: 2005-7-1 20:55
受益非浅!
作者: searching john    时间: 2005-7-8 15:11
本人熟练UG模具设计
对二次开发也很感冒,也有些研究
很想从事这方面工作,就是苦无机会
作者: bingyusky    时间: 2005-7-10 17:43
顶啊 真是太好了!!感谢楼主不吝赐教!!!
作者: pzytony    时间: 2005-7-10 22:40
上帝!
作者: pzytony    时间: 2005-7-10 22:40
上帝
作者: shuoqi    时间: 2005-7-11 16:53
我也要从事这方面,可是自己对vc还不是很熟,真是郁闷啊,如果能有楼主的编程能力就好了,羡慕ing,要加油啊!!!
作者: help    时间: 2005-7-13 11:52
好贴
作者: hui477    时间: 2005-7-23 11:57
用通俗的話。
把復雜的問題講得如此清楚。難得。謝了。
作者: 深夜摔键盘    时间: 2005-7-23 12:41
写的太乱了,都没什么章法。这么多兄弟来顶,真的很不好意思。
最近在重新整理中,快一年过去了,多少又学了点东西。
作者: wangmin00y    时间: 2005-7-24 14:16
大侠:
       请问一下在ug二次开发时怎样实现用多个按钮控制一个DLL呀
作者: hui477    时间: 2005-8-12 14:50
市面上的书满多的,可都是文诌诌的.跟文言文似的.
  
能理解这么透彻,表达这么清晰的已经很难得了.
作者: lihqing2000    时间: 2005-8-19 11:05
深夜摔键盘
我按照你说的做了一次,可是在编译对话框时出现:
E:\lianxi\importDllDemo1\importDllDemo1Dlg.cpp(178) : error C2660: 'add' : function does not take 0 parameters
这里的add()函数是 DllExportClass.z=DllExportClass.add();这个函数
  我运行你的文件就可以运行,不知是为什么会这个样?
作者: ugthinking    时间: 2008-7-4 10:34
标题: 回复 21楼 suye007 的帖子
说的有道理,我是个开发人员,但是对UG的设计和加工,知之甚少。
所以现在想做二次开发,难度很大。
悲哀啊!
作者: ffhi    时间: 2008-7-4 15:53
不错不错,赞一个。
作者: andytjy05    时间: 2008-7-5 07:53
实在太好!谢谢了!!!!!
作者: yufen    时间: 2008-7-5 11:12
好铁就要来一下。
作者: htc850905    时间: 2008-12-29 01:49
好贴,学会了很多
作者: w1593    时间: 2009-3-16 10:27
谢谢诶,无私奉献!我牛了也学你!
作者: 风扬天上    时间: 2009-7-5 17:11
来学习了。
作者: HMK    时间: 2009-8-13 20:38
好东西)))):)
作者: 吴哓    时间: 2009-8-13 20:56
很不错了!
作者: GMingZ    时间: 2009-8-14 20:33
不知道为什么这么简单的一个东西怎么获得了这么的跟帖。只能说明两个问题,一个是楼主的耐心可嘉,
另外,说明搞UG二次开发的对于MFC等基础的编程知识了解甚少。
我编程还算有一点点基础,上手UG二次开发感觉非常快。
所以希望后续开发的兄弟,先去专业的编程论坛看看编程知识先。你就会发现楼主写的实在是些皮毛,而且,有些地方说的有错误,不过,对于搞二次开发的这个错误就没什么关系了。
本人佩服楼主的耐心!!!!希望哪天我也能写一篇经验之谈!
作者: batigol_cj    时间: 2010-10-9 14:58
好贴

顶起来
作者: egligang    时间: 2010-12-21 09:04
希望再次看到楼主的好贴
作者: 平台洗马    时间: 2011-4-12 13:28
70# GMingZ
期待中
作者: XUSIR98    时间: 2011-5-15 22:57
用了两个小时跟踪了一下,确实如键盘兄说的,是显式访问
但是用的不是LoadLibrary
而是用的LdrLoadDll这个函数
获取地址LdrGetProcedureAddress这个函数
作者: cdplane    时间: 2011-5-25 17:00
楼主好人啊!!!非常感谢!!!
作者: wdg110    时间: 2011-5-25 21:54
楼主好人啊,多谢分享经验
作者: sbw_hl    时间: 2012-5-6 10:39
还要回复才能看




欢迎光临 iCAx开思网 (https://www.icax.org/) Powered by Discuz! X3.3