========================
 模拟DDE功能的Socket实现
========================

:作者: limodou
:联系: limodou@gmail.com
:日期: $Date: 2005-10-11 12:58:28 +0800 (Tue, 11 Oct 2005) $
:版本: $Id: dde.txt 42 2005-09-28 05:19:21Z limodou $
:主页: http://wiki.woodpecker.org.cn/moin/NewEdit
:BLOG: http://www.donews.net/limodou
:版权: GPL

.. contents::

DDE是动态数据交换，原本为Windows下的一个进程间数据交换的功能，在 wxWindows 中提到了已经实现了些功能，在 
Windows 下说是使用DDE，在Linux下使用Socket来模拟实现，不过我没有找到，最后学习了一些Socket和线程方面的编程终于简陋地实现了。我为什么要这个功能，原因是：

1. 使用DDE可以避免启动多个 NewEdit 实例。 
2. 当在命令行打开文件时，可以让已经存在的 NewEdit 实例打开此文件。 
3. 在Windows下可以放到资源管理员中的右键发送菜单中，在打开其它目录下的文件时更加方便。

下面是我实现的过程：

一、创建DDE模块--DDE.py
-----------------------

::

	1       from socket import *
	2       import threading
	3       import traceback
	4       
	5       ADDR = '127.0.0.1'
	6       PORT = 50000
	7       
	8       server = None
	9       
	10      def init():
	11          server = socket(AF_INET, SOCK_STREAM)
	12          try:
	13              server.bind((ADDR, PORT))
	14          except:
	15      #       traceback.print_exc()
	16              server = None
	17              return server
	18          return server
	19      
	20      def start(server, app=None):
	21          server.listen(1)
	22      #   print 'server starting'
	23          while True:
	24              conn = server.accept()[0]
	25              try:
	26                  data = conn.recv(256)
	27                  if data:
	28                      lines = data.splitlines()
	29                      cmd = lines[0]
	30                      if cmd == 'data':
	31      #                   print 'data'
	32                          if app:
	33                              app.frame.readfiles = lines[1:]
	34                      elif cmd == 'stop':
	35      #                   print 'stop'
	36                          break
	37              except:
	38      #           traceback.print_exc()
	39                  pass
	40          
	41      
	42      def sendraw(cmd, data):
	43          sendSock = socket(AF_INET, SOCK_STREAM)
	44          sendSock.connect((ADDR, PORT))
	45          sendSock.send(cmd+'\n'+data)
	46          sendSock.close()
	47          
	48      def stop():
	49          sendraw('stop', '')
	50          
	51      def senddata(data):
	52          sendraw('data', data)
	53          
	54      def run(app=None):
	55          server = init()
	56          if server:
	57              threading.Thread(target=start, args=(server, app,)).start()
	58              return True
	59          else:
	60              return False

DDE.py是一个利用Socket来模拟实现DDE的功能。它使用50000端口，不知道会不会与其它程序有冲突。

init()函数是将IP地址进行绑定，准备生成一个Server，如果成功，则说明以前没有启动过NewEdit实例，如果失败，则
说明以前启动过，则当前实例可以作为一个客户端，将向服务器发送文件名信息。

start()函数是当init()函数成功时，进入服务器的监听循环。它实现了两个简单的协议::

	data\n数据行

	这里数据行为文件，如果有多个文件名，中间用\n隔开

	stop

	用于通知服务器关闭

sendraw()是一个发送数据的最底层函数

stop()用于通知服务器退出

senddata()用于发送文件名

run()用来启动服务器，如果init()成功，则启动线程进行服务器监听循环，返回True，表示服务器启动成功。如果
init()失败，则返回False，表示已经存在服务器程序。

二、DDE模块的调用--DDESupport.py
--------------------------------

这里只是简单说明调用的地点和处理::

	1       import DDE
	2       import Mixin
	3       import wx
	4       
	5       def init(app, filenames):
	6           if app.ddeflag:
	7               if not DDE.run(app):
	8       #           print "send data"
	9                   DDE.senddata('\n'.join(filenames))
	10                  return True, False
	11      Mixin.setPlugin('app', 'init', init, Mixin.HIGH, 0)
	12      
	13      def afterclosewindow(win):
	14          if win.app.ddeflag:
	15              DDE.stop()
	16      Mixin.setPlugin('mainframe', 'afterclosewindow', afterclosewindow)
	17      
	18      def init(win):
	19          win.readfiles = []
	20      Mixin.setPlugin('mainframe', 'init', init)
	21      
	22      def on_idle(win, event):
	23          if win.readfiles:
	24              for filename in win.readfiles:
	25                  win.editctrl.new(filename)
	26              win.readfiles = []
	27              win.Show()
	28      Mixin.setPlugin('mainframe', 'on_idle', on_idle)

大家可能对NewEdit 的结构不熟悉，没关系，我只简单地说明一下。

DDE的处理是在生成主窗口之前进行的，上面的init()函数。如果调用DDE.run()成功，则什么都不做。如果不成功，则调
用senddata()将命令行中的文件名发给上一个实例(服务器)。ddeflag表示是否启动DDE功能，此值由命令行选项-n来指定的。

NewEdit对接收的文件名首先是在DDE.py模块中的start()函数(33行)处理的。可以看到::

	33                              app.frame.readfiles = lines[1:]

它是把文件名存入到了主窗体的readfiles变量中，并不直接打开。(lines[1:]索引从1开始是因为引一个元素是data命令
)。原来我是直接写的打开操作，但wxPython报错，说是不可以在线程中执行某些操作，只可以在主线程中进行。没有变法，
我想了一个变通的方法：我先将文件名保存，然后在IDLE事件激活时，此时系统不忙，再描扫是否变量有值，有则打开，打
开后，再将readfiles值置为[]即可。因此你会看到on_idle()函数的处理，就是完成这一功能。

当退出NewEdit时，必须要关闭掉线程。如果不关闭，NewEdit无法正常结束。于是在关闭NewEdit事件中加上通知服务器
线程关闭的命令，就是afterclosewindow()所做的工做。

做完这些工作，NewEdit就可以只启动一个实例，并且当打开一个文件时，如果已经存在一个实例，则当前实例不会继续运
行，而是将文件名发到上一个实例，则它打开文件。

在Windows上，你可以在sendto菜单中增加一个快捷方式，这样在使用资源管理器时可以方便的使用右键发送功能打开这个
文件。我的环境下的快捷方式的命令行为::

	C:\Python23\python.exe D:\program\newedit\NewEdit.py

`[返回]`_

.. _`[返回]`: technical.htm