用DDE实现窗体防止运行多个实例并传递命令 关键字: VB,DDE,实例,命令 贴文时间 使用DDE技术,为您的应用程序增辉 上网的朋友一定都用过网络蚂蚁(Net Ants)的吧?不知你在使用过程中有没有注意过, 那就是如果你想调动两个“蚂蚁”为您效力是不可能的--它总会把新运行的关闭。这点在VB 中很容易实现: Private Sub Form_Load() If App.PrevInstance Then MsgBox "你已经运行这个应用程序了" End ' 退出新运行的程序 End If End Sub 这样如果你运行这个程序后在运行它,它会弹出一个消息框并拒绝再次运行。这非常容易。 而“蚂蚁”程序的妙处就在于:在重复运行“蚂蚁”时它不仅拒绝运行,而且能把已经运 行的“蚂蚁”激活,这样用上面的程序就无能为力了。但事实上实现拒绝运行并激活已运行的 程序有多种方法: 1、用FindWindow函数得到已经运行窗体的句柄(HWND),然后用SetActiveWindow等API函 数将其激活。其缺点也很明显,那就是没法传递参数。 2、用FindWindow函数得到已运行窗体的句柄后用SendMessage的方法给窗体传送一个自定义 消息(附带参数),然后在窗体中拦截并进行处理,但这样做要修改窗体的标准消息处理 程序,用在VC,BC或DELPHI编写的程序中还行,但在VB中工作量太大,并且容易发生“一 般保护行错误”使VB崩溃,不太可取(当然,如果你有足够的信心和不怕崩溃的精神,也 可以试一下 ^_^ )。 3、使用DDE技术。 所谓DDE技术,就是动态数据交换技术。也许你很奇怪,这与本文所讨论的内容有什么相干的? 且听我慢慢讲来。 为了实现拒绝运行并把已经运行的程序激活并实现各种功能,我们可以先用本文开头提到的方 法,检测一下程序有没有被运行过,如果没有,就正常运行,如果已经被运行过,就打通与它的 DDE通道,传给它一个(或一些)数据,然后由已经运行的程序对数据进行处理,再去实现各种 “意想不到”的功能,这时也许就有人对这你的程序喊:“酷、酷……” ^_^ 好了,耳听为虚,眼见为实,下面让我们动点真格的。 打开VB,新建一个工程,选择菜单中的“工程->工程1 属性”,把工程名称改为“P1”(我爱 偷懒,能短则短 ^_^ ),把已有的一个窗体的“LinkTopic”属性改为“FormDDE”,把“LinkMode” 属性改为“1 - Source”,添加一个PictureBox控件作为DDE执行控件,命名为picDDE。然后添加一个 TextBox控件,命名为“txtInfo”,并把“MultiLine”属性设置为“True”,以便显示多行文本,作为 消息显示控件。最后在窗体代码区输入以下代码: Const COMMANDLINE = "CommandLine=" ' 还是为了省事,定义一个常量Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer) Static lngCount As Long Dim Info As StringInfo = txtInfo.Text ' 保留原有信息Select Case CmdStr ' CmdStr 是DDE程序传送过来的参数 Case "Max" Me.WindowState = 2 Info = Info + vbNewLine + "窗体已被最大化" Case "ShowTime" Info = Info + vbNewLine + "最后一次运行这个程序的时间是:" + Str(Now) Case "Count" lngCount = lngCount + 1 Info = Info + vbNewLine + "你已经第" + Str(lngCount) + "次重复调用这个程序。" _ + vbNewLine + "但怕您不多给工资,所以只运行了一个 ^_^" End SelectIf Left(CmdStr, Len(COMMANDLINE)) = COMMANDLINE Then Info = Info + vbNewLine + "新程序曾以命令行形式运行" + vbNewLine + "命令行为:" _ + vbNewLine + Right(CmdStr, Len(CmdStr) - Len(COMMANDLINE)) End IftxtInfo.Text = Info ' 把信息显示出来Cancel = False End Sub Private Sub LinkAndSendMessage(ByVal Msg As String) Dim t As Long picDDE.LinkMode = 0 '-- picDDE.LinkTopic = "P1|FormDDE" ' |______连接DDE程序并发送数据/参数 picDDE.LinkMode = 2 ' | “|”为管道符,是“退格键”旁边的竖线, picDDE.LinkExecute Msg '-- 不是字母或数字!t = picDDE.LinkTimeout '-- picDDE.LinkTimeout = 1 ' |______终止DDE通道。当然,也可以用别的方法 picDDE.LinkMode = 0 ' | 这里用的是超时强制终止的方法 picDDE.LinkTimeout = t '-- End Sub Private Sub Form_Load() If App.PrevInstance Then ' 程序是否已经运行 Me.LinkTopic = "" ' 这两行用于清除新运行的程序的DDE服务器属性, Me.LinkMode = 0 ' 否则在连接DDE程序时会出乱子的 LinkAndSendMessage "Max" '-- LinkAndSendMessage "Count" ' |-----连接DDE接受程序并传送数据/参数 LinkAndSendMessage "ShowTime" '-- If Command <> "" Then ' 如果有命令行参数,就传递过去 LinkAndSendMessage COMMANDLINE + Command End If End ' 结束新程序的运行 End If End Sub测试一下: 把工程“P1”编译成EXE文件(设名称为 P1.EXE ) 1、打开“我的电脑”,找到 P1.EXE 并执行。可以看到程序正常运行了。 2、再运行一次,这次新程序没有运行成功,而原来运行的程序却被最大化了,而且文本框中有以下 字符: 窗体已被最大化 你已经第 1次重复调用这个程序。 但怕您不多给工资,所以只运行了一个 ^_^ 最后一次运行这个程序的时间是:00-2-6 7:11:01 3、打开 MS-DOS方式 ,用命令行方式再次运行程序,如 “P1 How Are You?” 这时原来运行的程序文本框中又多了几行字: 窗体已被最大化 你已经第 2次重复调用这个程序。 但怕您不多给工资,所以只运行了一个 ^_^ 最后一次运行这个程序的时间是:00-2-6 7:14:32 新程序曾以命令行形式运行 命令行为: How Are You?OK,运行完全正确,然后你就可以把它应用的你的程序中了。当然,这只是一些个人心得,如有疏漏之出,还请各位大虾指正。
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const GWL_WNDPROC = (-4) Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As LongPrivate Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As LongPrivate Sub Command1_Click() Me.Hide End SubPrivate Sub Form_Load() Dim frmCaption As String frmCaption = Me.Caption Me.Caption = ""
frmhwnd = FindWindow(vbNullString, frmCaption) If frmhwnd <> 0 Then SendMessage frmhwnd, WM_USER + 112, 0, ByVal 0& Unload Me Exit Sub End If
preWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WndProc Me.Caption = frmCaption End SubPrivate Sub Form_Unload(Cancel As Integer) SetWindowLong Me.hwnd, GWL_WNDPROC, preWndProc End Sub add moduelPublic preWndProc As Long Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long Private Declare Function SetfrmFocus Lib "user32" Alias "SetFocus" (ByVal hwnd As Long) As LongPublic Const WM_USER = &H400 Private Const SW_SHOWMAXIMIZED = 3 Private Const SW_RESTORE = 9 Public Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long On Error Resume Next If (Msg = WM_USER + 112) Then With frmTest If frmTest.WindowState = 1 Then .WindowState = 0
End If .Visible = True SetfrmFocus frmTest.hwnd End With End If WndProc = CallWindowProc(preWndProc, hwnd, Msg, wParam, lParam) End Function
我是这样做的: 建一个ActiveX EXE 类型的服务程序 包含一类名为myClassName的类 设置一属性CommandX 代码如下: Option Explicit'保持属性值的局部变量 Private mvarCommandX As Variant '局部复制 Public Property Let CommandX(ByVal vData As Variant) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.CommandX = 5 mvarCommandX = vData End Property Public Property Set CommandX(ByVal vData As Variant) '向属性指派对象时使用,位于 Set 语句的左边。 'Syntax: Set x.CommandX = Form1 Set mvarCommandX = vData End Property Public Property Get CommandX() As Variant '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.CommandX If IsObject(mvarCommandX) Then Set CommandX = mvarCommandX Else CommandX = mvarCommandX End If End Property 然后生成 xxx.exe 文件 然后在你的程序中引用这个xxx.exe 代码如下: dim myCommand as New myClassName 现在就可以在不同实例中通过myClassName.CommandX 来传递信息拉!补充一下 ActiveX EXE 服务程序xxx.exe 引用前要运行一下(就是注册)。
关键字:
VB,DDE,实例,命令 贴文时间 使用DDE技术,为您的应用程序增辉 上网的朋友一定都用过网络蚂蚁(Net Ants)的吧?不知你在使用过程中有没有注意过,
那就是如果你想调动两个“蚂蚁”为您效力是不可能的--它总会把新运行的关闭。这点在VB
中很容易实现:
Private Sub Form_Load()
If App.PrevInstance Then
MsgBox "你已经运行这个应用程序了"
End ' 退出新运行的程序
End If
End Sub
这样如果你运行这个程序后在运行它,它会弹出一个消息框并拒绝再次运行。这非常容易。
而“蚂蚁”程序的妙处就在于:在重复运行“蚂蚁”时它不仅拒绝运行,而且能把已经运
行的“蚂蚁”激活,这样用上面的程序就无能为力了。但事实上实现拒绝运行并激活已运行的
程序有多种方法: 1、用FindWindow函数得到已经运行窗体的句柄(HWND),然后用SetActiveWindow等API函
数将其激活。其缺点也很明显,那就是没法传递参数。 2、用FindWindow函数得到已运行窗体的句柄后用SendMessage的方法给窗体传送一个自定义
消息(附带参数),然后在窗体中拦截并进行处理,但这样做要修改窗体的标准消息处理
程序,用在VC,BC或DELPHI编写的程序中还行,但在VB中工作量太大,并且容易发生“一
般保护行错误”使VB崩溃,不太可取(当然,如果你有足够的信心和不怕崩溃的精神,也
可以试一下 ^_^ )。 3、使用DDE技术。 所谓DDE技术,就是动态数据交换技术。也许你很奇怪,这与本文所讨论的内容有什么相干的?
且听我慢慢讲来。
为了实现拒绝运行并把已经运行的程序激活并实现各种功能,我们可以先用本文开头提到的方
法,检测一下程序有没有被运行过,如果没有,就正常运行,如果已经被运行过,就打通与它的
DDE通道,传给它一个(或一些)数据,然后由已经运行的程序对数据进行处理,再去实现各种
“意想不到”的功能,这时也许就有人对这你的程序喊:“酷、酷……” ^_^
好了,耳听为虚,眼见为实,下面让我们动点真格的。 打开VB,新建一个工程,选择菜单中的“工程->工程1 属性”,把工程名称改为“P1”(我爱
偷懒,能短则短 ^_^ ),把已有的一个窗体的“LinkTopic”属性改为“FormDDE”,把“LinkMode”
属性改为“1 - Source”,添加一个PictureBox控件作为DDE执行控件,命名为picDDE。然后添加一个
TextBox控件,命名为“txtInfo”,并把“MultiLine”属性设置为“True”,以便显示多行文本,作为
消息显示控件。最后在窗体代码区输入以下代码:
Const COMMANDLINE = "CommandLine=" ' 还是为了省事,定义一个常量Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer)
Static lngCount As Long
Dim Info As StringInfo = txtInfo.Text ' 保留原有信息Select Case CmdStr ' CmdStr 是DDE程序传送过来的参数
Case "Max"
Me.WindowState = 2
Info = Info + vbNewLine + "窗体已被最大化"
Case "ShowTime"
Info = Info + vbNewLine + "最后一次运行这个程序的时间是:" + Str(Now)
Case "Count"
lngCount = lngCount + 1
Info = Info + vbNewLine + "你已经第" + Str(lngCount) + "次重复调用这个程序。" _
+ vbNewLine + "但怕您不多给工资,所以只运行了一个 ^_^"
End SelectIf Left(CmdStr, Len(COMMANDLINE)) = COMMANDLINE Then
Info = Info + vbNewLine + "新程序曾以命令行形式运行" + vbNewLine + "命令行为:" _
+ vbNewLine + Right(CmdStr, Len(CmdStr) - Len(COMMANDLINE))
End IftxtInfo.Text = Info ' 把信息显示出来Cancel = False
End Sub
Private Sub LinkAndSendMessage(ByVal Msg As String)
Dim t As Long
picDDE.LinkMode = 0 '--
picDDE.LinkTopic = "P1|FormDDE" ' |______连接DDE程序并发送数据/参数
picDDE.LinkMode = 2 ' | “|”为管道符,是“退格键”旁边的竖线,
picDDE.LinkExecute Msg '-- 不是字母或数字!t = picDDE.LinkTimeout '--
picDDE.LinkTimeout = 1 ' |______终止DDE通道。当然,也可以用别的方法
picDDE.LinkMode = 0 ' | 这里用的是超时强制终止的方法
picDDE.LinkTimeout = t '--
End Sub
Private Sub Form_Load()
If App.PrevInstance Then ' 程序是否已经运行 Me.LinkTopic = "" ' 这两行用于清除新运行的程序的DDE服务器属性,
Me.LinkMode = 0 ' 否则在连接DDE程序时会出乱子的 LinkAndSendMessage "Max" '--
LinkAndSendMessage "Count" ' |-----连接DDE接受程序并传送数据/参数
LinkAndSendMessage "ShowTime" '-- If Command <> "" Then ' 如果有命令行参数,就传递过去
LinkAndSendMessage COMMANDLINE + Command
End If
End ' 结束新程序的运行
End If
End Sub测试一下:
把工程“P1”编译成EXE文件(设名称为 P1.EXE )
1、打开“我的电脑”,找到 P1.EXE 并执行。可以看到程序正常运行了。
2、再运行一次,这次新程序没有运行成功,而原来运行的程序却被最大化了,而且文本框中有以下
字符: 窗体已被最大化
你已经第 1次重复调用这个程序。
但怕您不多给工资,所以只运行了一个 ^_^
最后一次运行这个程序的时间是:00-2-6 7:11:01 3、打开 MS-DOS方式 ,用命令行方式再次运行程序,如 “P1 How Are You?”
这时原来运行的程序文本框中又多了几行字: 窗体已被最大化
你已经第 2次重复调用这个程序。
但怕您不多给工资,所以只运行了一个 ^_^
最后一次运行这个程序的时间是:00-2-6 7:14:32
新程序曾以命令行形式运行
命令行为:
How Are You?OK,运行完全正确,然后你就可以把它应用的你的程序中了。当然,这只是一些个人心得,如有疏漏之出,还请各位大虾指正。
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const GWL_WNDPROC = (-4)
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As LongPrivate Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As LongPrivate Sub Command1_Click()
Me.Hide
End SubPrivate Sub Form_Load()
Dim frmCaption As String
frmCaption = Me.Caption
Me.Caption = ""
frmhwnd = FindWindow(vbNullString, frmCaption)
If frmhwnd <> 0 Then
SendMessage frmhwnd, WM_USER + 112, 0, ByVal 0&
Unload Me
Exit Sub
End If
preWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC)
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WndProc
Me.Caption = frmCaption
End SubPrivate Sub Form_Unload(Cancel As Integer)
SetWindowLong Me.hwnd, GWL_WNDPROC, preWndProc
End Sub
add moduelPublic preWndProc As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Private Declare Function SetfrmFocus Lib "user32" Alias "SetFocus" (ByVal hwnd As Long) As LongPublic Const WM_USER = &H400
Private Const SW_SHOWMAXIMIZED = 3
Private Const SW_RESTORE = 9
Public Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
On Error Resume Next
If (Msg = WM_USER + 112) Then
With frmTest
If frmTest.WindowState = 1 Then
.WindowState = 0
End If
.Visible = True
SetfrmFocus frmTest.hwnd
End With
End If
WndProc = CallWindowProc(preWndProc, hwnd, Msg, wParam, lParam)
End Function
同意 AdamBear(学习再学习) ,另外,希望也给我一份:[email protected]
多谢啦:[email protected]
多谢啦:[email protected]
释,希望我能在今天晚上就把它发给大家。
实现上这个贴子涉及到的一个进程间通讯的问题,这是个很大的话题,可以说就我所知
方法不下十种,不过在这个贴子里谈就有点偏题了,如果哪位愿意开一个如"到底有几种进
程间通讯的方法"这样的贴子,我很愿意来谈谈。
而仅对这个问题来说,如果想只用单一程序而不想再写一个COM组件的话,那么在VB里最
官方最通用的办法,就是发消息。当然也不一定非要用"老妖"所说的子类化窗口技术,因为
在VB里这样代价比较高(但对于自定义消息别无他法),我们完全可以考虑直接使用VB内已
有的消息,这样就不用进行子类化。我可以提一个具体的做法,比如我们可以放一个文本框
Text1,在新进程中通过FindWindowEx找到这个原窗口的Text1子窗口句柄,然后再用SetWindowText
来设置其文本,这时会触发原进程的Text1_Chang事件,从而达到通讯的目的。也可以直接通过
发关WM_SETTEXT消息达到同样的目的。若需要具体的代码,我可以试着来写写。
当然这不说完全不需要子类化技术,比如需要传对象,只需要在"老妖"的代码上稍加修改即
可,具体的方法可以参考一下VB光盘上的Unsupport目录下的SYSTRAY,这是我第三次在CSDN上推荐
这个程序了(虽然它不是实现SYSTRAY最好的方法)。
至于我上面说的ATOM原子技术,实现上是一种再发明车轮,实际上uguess说的DDE技术的本质
就是用专用的DDE消息使用全局原子来传递Link的源和TOPIC,若我用SendMessage自定义消息来传
ATOM,那实质上是返古。API View上没有GlobalAddAtom的声明就说明它是一种要淘汰的技术。
要在多进程中共享大块的数据,在现代Win32程序设计中最流行的还是使用内存文件映射
就是使用CreateFileMapping和MapViewOfFile来建立一个多进程都可读写内存,把它引入到VB
还是很有必要的,所以我将它的例子代码做出来,现在正在注释。当然要有HACK精神也可以直接
用WriteProcessMemory,但这是个粗暴而危险的方法。
好了就说这么多了,今天下午还要上班,晚上我希望能把代码发出来,还有大家可以再思考
一下进程通讯还有什么方法,我先抛块砖,有没有想过用ClipBoard,有没有想更原始的INI文件、
注册表,有没有想过怎么用一个服务COM组件,有没有想过用WinSock控制,有没有想过……,想
一想,十种方法都不止。
唉,30分的贴子写了这么多,呵呵。
居然没听过。不过,也是现在到处都是Event。
用MTS、MSMQ这些东西都要自己写COM组件。
用组件的话,在组件里直接Fire Event,不需要什么标量。
我收到了,正在研读
[email protected]
THANK YOU !!
[email protected]
建一个ActiveX EXE 类型的服务程序
包含一类名为myClassName的类
设置一属性CommandX 代码如下:
Option Explicit'保持属性值的局部变量
Private mvarCommandX As Variant '局部复制
Public Property Let CommandX(ByVal vData As Variant)
'向属性指派值时使用,位于赋值语句的左边。
'Syntax: X.CommandX = 5
mvarCommandX = vData
End Property
Public Property Set CommandX(ByVal vData As Variant)
'向属性指派对象时使用,位于 Set 语句的左边。
'Syntax: Set x.CommandX = Form1
Set mvarCommandX = vData
End Property
Public Property Get CommandX() As Variant
'检索属性值时使用,位于赋值语句的右边。
'Syntax: Debug.Print X.CommandX
If IsObject(mvarCommandX) Then
Set CommandX = mvarCommandX
Else
CommandX = mvarCommandX
End If
End Property
然后生成 xxx.exe 文件
然后在你的程序中引用这个xxx.exe
代码如下:
dim myCommand as New myClassName
现在就可以在不同实例中通过myClassName.CommandX 来传递信息拉!补充一下 ActiveX EXE 服务程序xxx.exe 引用前要运行一下(就是注册)。