[python-win32] Re: how to load 2 python COM DLLs in single app?

Niki Spahiev niki at vintech.bg
Thu Nov 25 16:42:56 CET 2004


SUCCESS!

Renaming scheme works for me (TM) and i can load 2 Inprocess COM servers.
Attached patch is for py2exe 0.54

Option --unique=MN turns on renaming. MN are any 2 characters valid for 
filename.

No debug-python support (_d.pyd).

Thomas will you include it in py2exe?

Niki Spahiev

-------------- next part --------------
--- C:\Python23\Lib\site-packages\py2exe\build_exe.pyw	Fri Oct 22 17:32:32 2004
+++ C:\Python23\Lib\site-packages\py2exe\build_exe.py	Thu Nov 25 17:33:51 2004
@@ -69,6 +69,19 @@
 del __load
 """
 
+BOOT_UNIQUE = """
+def __load(name,ext):
+    import imp, sys
+    dirname = sys.prefix
+    path = dirname + '/' + name + ext
+    mod = imp.load_dynamic(name, path)
+##    mod.frozen = 1
+__load('zlib','%(UN)s.pyd')
+__load('pywintypes','%(UN)s.dll')
+__load('pythoncom','%(UN)s.dll')
+del __load
+"""
+
 # A very loosely defined "target".  We assume either a "script" or "modules"
 # attribute.  Some attributes will be target specific.
 class Target:
@@ -139,7 +152,10 @@
          "create a compressed zipfile"),
 
         ("xref", 'x',
-         "create and show a module crosss reference")
+         "create and show a module crosss reference"),
+
+        ("unique", 'u',
+         "patch all DLLs to be unique"),
         ]
 
     boolean_options = ["compressed", "xref"]
@@ -156,6 +172,7 @@
         self.dist_dir = None
         self.dll_excludes = None
         self.typelibs = None
+        self.unique = ''
 
     def finalize_options (self):
         self.optimize = int(self.optimize)
@@ -359,7 +376,10 @@
             # magic which relies on this exact filename.
             # So we do it via a custom loader - see create_loader()
             dst = os.path.join(self.lib_dir, os.path.basename(item.__file__))
-            self.copy_file(src, dst)
+            dst = patch_filename(dst, self.unique)
+            where,copied = self.copy_file(src, dst)
+            if copied:
+                patch_python_dll_import(where, self.unique)
             self.lib_files.append(dst)
 
         # create the shared zipfile containing all Python modules
@@ -384,7 +404,10 @@
                 dst = os.path.join(self.exe_dir, base)
             else:
                 dst = os.path.join(self.lib_dir, base)
-            self.copy_file(dll, dst)
+            dst = patch_filename(dst, self.unique)
+            where,copied = self.copy_file(dll, dst)
+            if copied:
+                patch_python_dll_import(where, self.unique)
             self.lib_files.append(dst)
 
         for target in self.distribution.isapi:
@@ -577,6 +600,7 @@
         old_force = self.force
         self.force = True
         self.copy_file(src, exe_path)
+        patch_python_dll_import(exe_path, self.unique)
         self.force = old_force
 
         # Make sure the file is writeable...
@@ -602,6 +626,10 @@
             code_object = compile(open(script, "U").read() + "\n",
                                   os.path.basename(script), "exec")
             code_objects.append(code_object)
+        if self.unique:
+            code_object = compile(BOOT_UNIQUE % dict(UN=self.unique),
+                                  "boot_unique", "exec")
+            code_objects.insert(0,code_object)
         code_bytes = marshal.dumps(code_objects)
 
         if self.distribution.zipfile is None:
@@ -881,6 +909,7 @@
             print "creating python loader for extension '%s'" % item.__name__
 
         fname = os.path.basename(item.__file__)
+        fname = patch_filename(fname, self.unique)
         source = LOADER % fname
         if not self.dry_run:
             open(pathname, "w").write(source)
@@ -1307,3 +1336,45 @@
             if verbose:
                 print grok_environment_error(
                     exc, "error removing %s: " % directory)
+
+def patch_filename(fn, AB):
+    base = os.path.basename(fn).lower()
+    if not AB or base.startswith('tcl') or base.startswith('tk'):
+        return fn
+    pv = '%d%d' % sys.version_info[:2]
+    fn, ext = os.path.splitext( fn )
+    if fn.endswith( pv ):
+        fn = fn[:-2]
+    fn += AB+ext
+    return fn
+
+def patch_python_dll_import(dll_name, AB):
+    base = os.path.basename(dll_name).lower()
+    if not AB or base.startswith('tcl') or base.startswith('tk'):
+        return
+    pv = '%d%d' % sys.version_info[:2]
+    # We preserve the times on the file, so the dependency tracker works.
+    st = os.stat(dll_name)
+    os.chmod(dll_name, stat.S_IREAD | stat.S_IWRITE)
+    f = open(dll_name, "r+b")
+    bytes = f.read()
+    for import_name in (python_dll, 'pywintypes%s.dll'%pv,
+                    'pythoncom%s.dll'%pv, 'pythoncom%s%s%s.dll\0'):
+        pos = bytes.find( import_name )
+        if pos < 0:
+            if import_name == python_dll:
+                raise RuntimeError(import_name+' not in '+dll_name)
+            continue
+        dup = bytes.find(import_name, pos+1)
+        if dup >= 0 and pv in import_name:
+            raise RuntimeError('multi '+import_name)
+        if pv in import_name:
+            r = import_name.replace( pv, AB )
+        else:
+            r = import_name.replace( '%s%s%s', AB )
+        f.seek( pos, 0 )
+        f.write( r )
+    f.close()
+
+    # restore the time.
+    os.utime(dll_name, (st[stat.ST_ATIME], st[stat.ST_MTIME]))


More information about the Python-win32 mailing list