Todo: 集成多平台 解决因SaiNiu线程抢占资源问题 本地提交测试环境打包 和 正式打包脚本与正式环境打包bat 提交Python32环境包 改进多日志文件生成情况修改打包日志细节

This commit is contained in:
2025-09-18 15:52:03 +08:00
parent 8b9fc925fa
commit 7cfc0c22b7
7608 changed files with 2424791 additions and 25 deletions

View File

@@ -0,0 +1,4 @@
# See if we have a special directory for the binaries (for developers)
import win32com
win32com.__PackageSupportBuildPath__(__path__)

View File

@@ -0,0 +1,467 @@
"""The glue between the Python debugger interface and the Active Debugger interface"""
import _thread
import bdb
import os
import sys
import traceback
import pythoncom
import win32api
import win32com.client.connect
from win32com.axdebug.util import _wrap, trace
from . import axdebug, gateways, stackframe
def fnull(*args):
pass
debugging = "DEBUG_AXDEBUG" in os.environ
traceenter = fnull # trace enter of functions
tracev = fnull # verbose trace
if debugging:
traceenter = trace # trace enter of functions
tracev = trace # verbose trace
class OutputReflector:
def __init__(self, file, writefunc):
self.writefunc = writefunc
self.file = file
def __getattr__(self, name):
return getattr(self.file, name)
def write(self, message):
self.writefunc(message)
self.file.write(message)
def _dumpf(frame):
if frame is None:
return "<None>"
else:
addn = "(with trace!)"
if frame.f_trace is None:
addn = " **No Trace Set **"
return f"Frame at {id(frame)}, file {frame.f_code.co_filename}, line: {frame.f_lineno}{addn}"
g_adb = None
def OnSetBreakPoint(codeContext, breakPointState, lineNo):
try:
fileName = codeContext.codeContainer.GetFileName()
# inject the code into linecache.
import linecache
linecache.cache[fileName] = 0, 0, codeContext.codeContainer.GetText(), fileName
g_adb._OnSetBreakPoint(fileName, codeContext, breakPointState, lineNo + 1)
except:
traceback.print_exc()
class Adb(bdb.Bdb, gateways.RemoteDebugApplicationEvents):
def __init__(self):
self.debugApplication = None
self.debuggingThread = None
self.debuggingThreadStateHandle = None
self.stackSnifferCookie = self.stackSniffer = None
self.codeContainerProvider = None
self.debuggingThread = None
self.breakFlags = None
self.breakReason = None
self.appDebugger = None
self.appEventConnection = None
self.logicalbotframe = None # Anything at this level or below does not exist!
self.currentframe = None # The frame we are currently in.
self.recursiveData = [] # Data saved for each reentery on this thread.
bdb.Bdb.__init__(self)
self._threadprotectlock = _thread.allocate_lock()
self.reset()
def canonic(self, fname):
if fname[0] == "<":
return fname
return bdb.Bdb.canonic(self, fname)
def reset(self):
traceenter("adb.reset")
bdb.Bdb.reset(self)
def __xxxxx__set_break(self, filename, lineno, cond=None):
# As per standard one, except no linecache checking!
if filename not in self.breaks:
self.breaks[filename] = []
list = self.breaks[filename]
if lineno in list:
return "There is already a breakpoint there!"
list.append(lineno)
if cond is not None:
self.cbreaks[filename, lineno] = cond
def stop_here(self, frame):
traceenter("stop_here", _dumpf(frame), _dumpf(self.stopframe))
# As per bdb.stop_here, except for logicalbotframe
# if self.stopframe is None:
# return 1
if frame is self.stopframe:
return 1
tracev("stop_here said 'No'!")
return 0
def break_here(self, frame):
traceenter("break_here", self.breakFlags, _dumpf(frame))
self.breakReason = None
if self.breakFlags == axdebug.APPBREAKFLAG_DEBUGGER_HALT:
self.breakReason = axdebug.BREAKREASON_DEBUGGER_HALT
elif self.breakFlags == axdebug.APPBREAKFLAG_DEBUGGER_BLOCK:
self.breakReason = axdebug.BREAKREASON_DEBUGGER_BLOCK
elif self.breakFlags == axdebug.APPBREAKFLAG_STEP:
self.breakReason = axdebug.BREAKREASON_STEP
else:
print("Calling base 'break_here' with", self.breaks)
if bdb.Bdb.break_here(self, frame):
self.breakReason = axdebug.BREAKREASON_BREAKPOINT
return self.breakReason is not None
def break_anywhere(self, frame):
traceenter("break_anywhere", _dumpf(frame))
if self.breakFlags == axdebug.APPBREAKFLAG_DEBUGGER_HALT:
self.breakReason = axdebug.BREAKREASON_DEBUGGER_HALT
return 1
rc = bdb.Bdb.break_anywhere(self, frame)
tracev("break_anywhere", _dumpf(frame), "returning", rc)
return rc
def dispatch_return(self, frame, arg):
traceenter("dispatch_return", _dumpf(frame), arg)
if self.logicalbotframe is frame:
# We don't want to debug parent frames.
tracev("dispatch_return resetting sys.trace")
sys.settrace(None)
return
# self.bSetTrace = 0
self.currentframe = frame.f_back
return bdb.Bdb.dispatch_return(self, frame, arg)
def dispatch_line(self, frame):
traceenter("dispatch_line", _dumpf(frame), _dumpf(self.botframe))
# trace("logbotframe is", _dumpf(self.logicalbotframe), "botframe is", self.botframe)
if frame is self.logicalbotframe:
trace("dispatch_line", _dumpf(frame), "for bottom frame returing tracer")
# The next code executed in the frame above may be a builtin (eg, apply())
# in which sys.trace needs to be set.
sys.settrace(self.trace_dispatch)
# And return the tracer incase we are about to execute Python code,
# in which case sys tracer is ignored!
return self.trace_dispatch
if self.codeContainerProvider.FromFileName(frame.f_code.co_filename) is None:
trace(
"dispatch_line has no document for", _dumpf(frame), "- skipping trace!"
)
return None
self.currentframe = (
frame # So the stack sniffer knows our most recent, debuggable code.
)
return bdb.Bdb.dispatch_line(self, frame)
def dispatch_call(self, frame, arg):
traceenter("dispatch_call", _dumpf(frame))
frame.f_locals["__axstack_address__"] = axdebug.GetStackAddress()
if frame is self.botframe:
trace("dispatch_call is self.botframe - returning tracer")
return self.trace_dispatch
# Not our bottom frame. If we have a document for it,
# then trace it, otherwise run at full speed.
if self.codeContainerProvider.FromFileName(frame.f_code.co_filename) is None:
trace(
"dispatch_call has no document for", _dumpf(frame), "- skipping trace!"
)
# sys.settrace(None)
return None
return self.trace_dispatch
# rc = bdb.Bdb.dispatch_call(self, frame, arg)
# trace("dispatch_call", _dumpf(frame),"returned",rc)
# return rc
def trace_dispatch(self, frame, event, arg):
traceenter("trace_dispatch", _dumpf(frame), event, arg)
if self.debugApplication is None:
trace("trace_dispatch has no application!")
return # None
return bdb.Bdb.trace_dispatch(self, frame, event, arg)
#
# The user functions do bugger all!
#
# def user_call(self, frame, argument_list):
# traceenter("user_call",_dumpf(frame))
def user_line(self, frame):
traceenter("user_line", _dumpf(frame))
# Traces at line zero
if frame.f_lineno != 0:
breakReason = self.breakReason
if breakReason is None:
breakReason = axdebug.BREAKREASON_STEP
self._HandleBreakPoint(frame, None, breakReason)
def user_return(self, frame, return_value):
# traceenter("user_return",_dumpf(frame),return_value)
bdb.Bdb.user_return(self, frame, return_value)
def user_exception(self, frame, exc_info):
# traceenter("user_exception")
bdb.Bdb.user_exception(self, frame, exc_info)
def _HandleBreakPoint(self, frame, tb, reason):
traceenter(
"Calling HandleBreakPoint with reason", reason, "at frame", _dumpf(frame)
)
traceenter(" Current frame is", _dumpf(self.currentframe))
try:
resumeAction = self.debugApplication.HandleBreakPoint(reason)
tracev("HandleBreakPoint returned with ", resumeAction)
except pythoncom.com_error as details:
# Eeek - the debugger is dead, or something serious is happening.
# Assume we should continue
resumeAction = axdebug.BREAKRESUMEACTION_CONTINUE
trace("HandleBreakPoint FAILED with", details)
self.stack = []
self.curindex = 0
if resumeAction == axdebug.BREAKRESUMEACTION_ABORT:
self.set_quit()
elif resumeAction == axdebug.BREAKRESUMEACTION_CONTINUE:
tracev("resume action is continue")
self.set_continue()
elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_INTO:
tracev("resume action is step")
self.set_step()
elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_OVER:
tracev("resume action is next")
self.set_next(frame)
elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_OUT:
tracev("resume action is stop out")
self.set_return(frame)
else:
raise ValueError("unknown resume action flags")
self.breakReason = None
def set_trace(self):
self.breakReason = axdebug.BREAKREASON_LANGUAGE_INITIATED
bdb.Bdb.set_trace(self)
def CloseApp(self):
traceenter("ClosingApp")
self.reset()
self.logicalbotframe = None
if self.stackSnifferCookie is not None:
try:
self.debugApplication.RemoveStackFrameSniffer(self.stackSnifferCookie)
except pythoncom.com_error:
trace(
f"*** Could not RemoveStackFrameSniffer {self.stackSnifferCookie}"
)
self.stackSnifferCookie = self.stackSniffer = None
if self.appEventConnection is not None:
self.appEventConnection.Disconnect()
self.appEventConnection = None
self.debugApplication = None
self.appDebugger = None
if self.codeContainerProvider is not None:
self.codeContainerProvider.Close()
self.codeContainerProvider = None
def AttachApp(self, debugApplication, codeContainerProvider):
# traceenter("AttachApp", debugApplication, codeContainerProvider)
self.codeContainerProvider = codeContainerProvider
self.debugApplication = debugApplication
self.stackSniffer = _wrap(
stackframe.DebugStackFrameSniffer(self), axdebug.IID_IDebugStackFrameSniffer
)
self.stackSnifferCookie = debugApplication.AddStackFrameSniffer(
self.stackSniffer
)
# trace(f"StackFrameSniffer added ({self.stackSnifferCookie})")
# Connect to the application events.
self.appEventConnection = win32com.client.connect.SimpleConnection(
self.debugApplication, self, axdebug.IID_IRemoteDebugApplicationEvents
)
def ResetAXDebugging(self):
traceenter("ResetAXDebugging", self, "with refcount", len(self.recursiveData))
if win32api.GetCurrentThreadId() != self.debuggingThread:
trace("ResetAXDebugging called on other thread")
return
if len(self.recursiveData) == 0:
# print("ResetAXDebugging called for final time.")
self.logicalbotframe = None
self.debuggingThread = None
self.currentframe = None
self.debuggingThreadStateHandle = None
return
(
self.logbotframe,
self.stopframe,
self.currentframe,
self.debuggingThreadStateHandle,
) = self.recursiveData[0]
self.recursiveData = self.recursiveData[1:]
def SetupAXDebugging(self, baseFrame=None, userFrame=None):
"""Get ready for potential debugging. Must be called on the thread
that is being debugged.
"""
# userFrame is for non AXScript debugging. This is the first frame of the
# users code.
if userFrame is None:
userFrame = baseFrame
else:
# We have missed the "dispatch_call" function, so set this up now!
userFrame.f_locals["__axstack_address__"] = axdebug.GetStackAddress()
traceenter("SetupAXDebugging", self)
self._threadprotectlock.acquire()
try:
thisThread = win32api.GetCurrentThreadId()
if self.debuggingThread is None:
self.debuggingThread = thisThread
else:
if self.debuggingThread != thisThread:
trace("SetupAXDebugging called on other thread - ignored!")
return
# push our context.
self.recursiveData.insert(
0,
(
self.logicalbotframe,
self.stopframe,
self.currentframe,
self.debuggingThreadStateHandle,
),
)
finally:
self._threadprotectlock.release()
trace("SetupAXDebugging has base frame as", _dumpf(baseFrame))
self.botframe = baseFrame
self.stopframe = userFrame
self.logicalbotframe = baseFrame
self.currentframe = None
self.debuggingThreadStateHandle = axdebug.GetThreadStateHandle()
self._BreakFlagsChanged()
# RemoteDebugApplicationEvents
def OnConnectDebugger(self, appDebugger):
traceenter("OnConnectDebugger", appDebugger)
self.appDebugger = appDebugger
# Reflect output to appDebugger
writefunc = lambda s: appDebugger.onDebugOutput(s)
sys.stdout = OutputReflector(sys.stdout, writefunc)
sys.stderr = OutputReflector(sys.stderr, writefunc)
def OnDisconnectDebugger(self):
traceenter("OnDisconnectDebugger")
# Stop reflecting output
if isinstance(sys.stdout, OutputReflector):
sys.stdout = sys.stdout.file
if isinstance(sys.stderr, OutputReflector):
sys.stderr = sys.stderr.file
self.appDebugger = None
self.set_quit()
def OnSetName(self, name):
traceenter("OnSetName", name)
def OnDebugOutput(self, string):
traceenter("OnDebugOutput", string)
def OnClose(self):
traceenter("OnClose")
def OnEnterBreakPoint(self, rdat):
traceenter("OnEnterBreakPoint", rdat)
def OnLeaveBreakPoint(self, rdat):
traceenter("OnLeaveBreakPoint", rdat)
def OnCreateThread(self, rdat):
traceenter("OnCreateThread", rdat)
def OnDestroyThread(self, rdat):
traceenter("OnDestroyThread", rdat)
def OnBreakFlagChange(self, abf, rdat):
traceenter("Debugger OnBreakFlagChange", abf, rdat)
self.breakFlags = abf
self._BreakFlagsChanged()
def _BreakFlagsChanged(self):
traceenter(
f"_BreakFlagsChanged to {self.breakFlags} "
+ f"with our thread = {self.debuggingThread}, "
+ f"and debugging thread = {win32api.GetCurrentThreadId()}"
)
trace("_BreakFlagsChanged has breaks", self.breaks)
# If a request comes on our debugging thread, then do it now!
# if self.debuggingThread!=win32api.GetCurrentThreadId():
# return
if len(self.breaks) or self.breakFlags:
if self.logicalbotframe:
trace("BreakFlagsChange with bot frame", _dumpf(self.logicalbotframe))
# We have frames not to be debugged (eg, Scripting engine frames
# (sys.settrace will be set when out logicalbotframe is hit -
# this may not be the right thing to do, as it may not cause the
# immediate break we desire.)
self.logicalbotframe.f_trace = self.trace_dispatch
else:
trace("BreakFlagsChanged, but no bottom frame")
if self.stopframe is not None:
self.stopframe.f_trace = self.trace_dispatch
# If we have the thread-state for the thread being debugged, then
# we dynamically set its trace function - it is possible that the thread
# being debugged is in a blocked call (eg, a message box) and we
# want to hit the debugger the instant we return
if (
self.debuggingThreadStateHandle is not None
and self.breakFlags
and self.debuggingThread != win32api.GetCurrentThreadId()
):
axdebug.SetThreadStateTrace(
self.debuggingThreadStateHandle, self.trace_dispatch
)
def _OnSetBreakPoint(self, key, codeContext, bps, lineNo):
traceenter("_OnSetBreakPoint", self, key, codeContext, bps, lineNo)
if bps == axdebug.BREAKPOINT_ENABLED:
problem = self.set_break(key, lineNo)
if problem:
print("*** set_break failed -", problem)
trace("_OnSetBreakPoint just set BP and has breaks", self.breaks)
else:
self.clear_break(key, lineNo)
self._BreakFlagsChanged()
trace("_OnSetBreakPoint leaving with breaks", self.breaks)
def Debugger():
global g_adb
if g_adb is None:
g_adb = Adb()
return g_adb

View File

@@ -0,0 +1,279 @@
"""A utility class for a code container.
A code container is a class which holds source code for a debugger. It knows how
to color the text, and also how to translate lines into offsets, and back.
"""
from __future__ import annotations
import os
import sys
import tokenize
from keyword import kwlist
from typing import Any
import win32api
import winerror
from win32com.axdebug import axdebug, contexts
from win32com.axdebug.util import _wrap
from win32com.server.exception import COMException
_keywords = {
_keyword
for _keyword in kwlist
# Avoids including True/False/None
if _keyword.islower()
}
"""set of Python keywords"""
class SourceCodeContainer:
def __init__(
self,
text: str | None,
fileName="<Remove Me!>",
sourceContext=0,
startLineNumber=0,
site=None,
debugDocument=None,
):
self.sourceContext = sourceContext # The source context added by a smart host.
self.text: str | None = text
if text:
self._buildlines()
self.nextLineNo = 0
self.fileName = fileName
# Any: PyIDispatch type is not statically exposed
self.codeContexts: dict[int, Any] = {}
self.site = site
self.startLineNumber = startLineNumber
self.debugDocument = debugDocument
def _Close(self):
self.text = self.lines = self.lineOffsets = None
self.codeContexts = None
self.debugDocument = None
self.site = None
self.sourceContext = None
def GetText(self):
return self.text
def GetName(self, dnt):
raise NotImplementedError("You must subclass this")
def GetFileName(self):
return self.fileName
def GetPositionOfLine(self, cLineNumber):
self.GetText() # Prime us.
try:
return self.lineOffsets[cLineNumber]
except IndexError:
raise COMException(scode=winerror.S_FALSE)
def GetLineOfPosition(self, charPos):
self.GetText() # Prime us.
lastOffset = 0
lineNo = 0
for lineOffset in self.lineOffsets[1:]:
if lineOffset > charPos:
break
lastOffset = lineOffset
lineNo += 1
else: # for not broken.
# print("Can't find", charPos, "in", self.lineOffsets)
raise COMException(scode=winerror.S_FALSE)
# print("GLOP ret=", lineNo, (charPos - lastOffset))
return lineNo, (charPos - lastOffset)
def GetNextLine(self):
if self.nextLineNo >= len(self.lines):
self.nextLineNo = 0 # auto-reset.
return ""
rc = self.lines[self.nextLineNo]
self.nextLineNo += 1
return rc
def GetLine(self, num):
self.GetText() # Prime us.
return self.lines[num]
def GetNumChars(self):
return len(self.GetText())
def GetNumLines(self):
self.GetText() # Prime us.
return len(self.lines)
def _buildline(self, pos):
i = self.text.find("\n", pos)
if i < 0:
newpos = len(self.text)
else:
newpos = i + 1
r = self.text[pos:newpos]
return r, newpos
def _buildlines(self):
self.lines = []
self.lineOffsets = [0]
line, pos = self._buildline(0)
while line:
self.lines.append(line)
self.lineOffsets.append(pos)
line, pos = self._buildline(pos)
def _ProcessToken(self, type, token, spos, epos, line):
srow, scol = spos
erow, ecol = epos
self.GetText() # Prime us.
linenum = srow - 1 # Lines zero based for us too.
realCharPos = self.lineOffsets[linenum] + scol
numskipped = realCharPos - self.lastPos
if numskipped == 0:
pass
elif numskipped == 1:
self.attrs.append(axdebug.SOURCETEXT_ATTR_COMMENT)
else:
self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numskipped))
kwSize = len(token)
self.lastPos = realCharPos + kwSize
attr = 0
if type == tokenize.NAME:
if token in _keywords:
attr = axdebug.SOURCETEXT_ATTR_KEYWORD
elif type == tokenize.STRING:
attr = axdebug.SOURCETEXT_ATTR_STRING
elif type == tokenize.NUMBER:
attr = axdebug.SOURCETEXT_ATTR_NUMBER
elif type == tokenize.OP:
attr = axdebug.SOURCETEXT_ATTR_OPERATOR
elif type == tokenize.COMMENT:
attr = axdebug.SOURCETEXT_ATTR_COMMENT
# else attr remains zero...
if kwSize == 0:
pass
elif kwSize == 1:
self.attrs.append(attr)
else:
self.attrs.append((attr, kwSize))
def GetSyntaxColorAttributes(self):
self.lastPos = 0
self.attrs = []
try:
for tokens in tokenize.tokenize(self.GetNextLine):
self._ProcessToken(*tokens)
except tokenize.TokenError:
pass # Ignore - will cause all subsequent text to be commented.
numAtEnd = len(self.GetText()) - self.lastPos
if numAtEnd:
self.attrs.append((axdebug.SOURCETEXT_ATTR_COMMENT, numAtEnd))
return self.attrs
# We also provide and manage DebugDocumentContext objects
def _MakeDebugCodeContext(self, lineNo, charPos, len):
return _wrap(
contexts.DebugCodeContext(lineNo, charPos, len, self, self.site),
axdebug.IID_IDebugCodeContext,
)
# Make a context at the given position. It should take up the entire context.
def _MakeContextAtPosition(self, charPos):
lineNo, offset = self.GetLineOfPosition(charPos)
try:
endPos = self.GetPositionOfLine(lineNo + 1)
except:
endPos = charPos
codecontext = self._MakeDebugCodeContext(lineNo, charPos, endPos - charPos)
return codecontext
# Returns a DebugCodeContext. debugDocument can be None for smart hosts.
def GetCodeContextAtPosition(self, charPos):
# trace("GetContextOfPos", charPos, maxChars)
# Convert to line number.
lineNo, offset = self.GetLineOfPosition(charPos)
charPos = self.GetPositionOfLine(lineNo)
try:
cc = self.codeContexts[charPos]
except KeyError:
cc = self._MakeContextAtPosition(charPos)
self.codeContexts[charPos] = cc
return cc
class SourceModuleContainer(SourceCodeContainer):
def __init__(self, module):
self.module = module
if hasattr(module, "__file__"):
fname = self.module.__file__
# Check for .pyc or .pyo or even .pys!
if fname[-1] in ["O", "o", "C", "c", "S", "s"]:
fname = fname[:-1]
try:
fname = win32api.GetFullPathName(fname)
except win32api.error:
pass
else:
if module.__name__ == "__main__" and len(sys.argv) > 0:
fname = sys.argv[0]
else:
fname = "<Unknown!>"
SourceCodeContainer.__init__(self, None, fname)
def GetText(self):
if self.text is None:
fname = self.GetFileName()
if fname:
try:
self.text = open(fname, "r").read()
except OSError as details:
self.text = f"# COMException opening file\n# {details!r}"
else:
self.text = f"# No file available for module '{self.module}'"
self._buildlines()
return self.text
def GetName(self, dnt):
name = self.module.__name__
try:
fname = win32api.GetFullPathName(self.module.__file__)
except win32api.error:
fname = self.module.__file__
except AttributeError:
fname = name
if dnt == axdebug.DOCUMENTNAMETYPE_APPNODE:
return name.split(".")[-1]
elif dnt == axdebug.DOCUMENTNAMETYPE_TITLE:
return fname
elif dnt == axdebug.DOCUMENTNAMETYPE_FILE_TAIL:
return os.path.split(fname)[1]
elif dnt == axdebug.DOCUMENTNAMETYPE_URL:
return f"file:{fname}"
else:
raise COMException(scode=winerror.E_UNEXPECTED)
if __name__ == "__main__":
from Test import ttest
sc = SourceModuleContainer(ttest)
# sc = SourceCodeContainer(open(sys.argv[1], "rb").read(), sys.argv[1])
attrs = sc.GetSyntaxColorAttributes()
attrlen = 0
for attr in attrs:
if isinstance(attr, tuple):
attrlen += attr[1]
else:
attrlen += 1
text = sc.GetText()
if attrlen != len(text):
print(f"Lengths don't match!!! ({attrlen}/{len(text)})")
# print("Attributes:")
# print(attrs)
print("GetLineOfPos=", sc.GetLineOfPosition(0))
print("GetLineOfPos=", sc.GetLineOfPosition(4))
print("GetLineOfPos=", sc.GetLineOfPosition(10))

View File

@@ -0,0 +1,58 @@
"""A module for managing the AXDebug I*Contexts"""
from . import adb, axdebug, gateways
# Utility function for wrapping object created by this module.
from .util import _wrap
class DebugCodeContext(gateways.DebugCodeContext, gateways.DebugDocumentContext):
# NOTE: We also implement the IDebugDocumentContext interface for Simple Hosts.
# Thus, debugDocument may be NULL when we have smart hosts - but in that case, we
# won't be called upon to provide it.
_public_methods_ = (
gateways.DebugCodeContext._public_methods_
+ gateways.DebugDocumentContext._public_methods_
)
_com_interfaces_ = (
gateways.DebugCodeContext._com_interfaces_
+ gateways.DebugDocumentContext._com_interfaces_
)
def __init__(self, lineNo, charPos, len, codeContainer, debugSite):
self.debugSite = debugSite
self.offset = charPos
self.length = len
self.breakPointState = 0
self.lineno = lineNo
gateways.DebugCodeContext.__init__(self)
self.codeContainer = codeContainer
def _Close(self):
self.debugSite = None
def GetDocumentContext(self):
if self.debugSite is not None:
# We have a smart host - let him give it to us.
return self.debugSite.GetDocumentContextFromPosition(
self.codeContainer.sourceContext, self.offset, self.length
)
else:
# Simple host - Fine - I'll do it myself!
return _wrap(self, axdebug.IID_IDebugDocumentContext)
def SetBreakPoint(self, bps):
self.breakPointState = bps
adb.OnSetBreakPoint(self, bps, self.lineno)
# The DebugDocumentContext methods for simple hosts.
def GetDocument(self):
return self.codeContainer.debugDocument
def EnumCodeContexts(self):
return _wrap(EnumDebugCodeContexts([self]), axdebug.IID_IEnumDebugCodeContexts)
class EnumDebugCodeContexts(gateways.EnumDebugCodeContexts):
def _wrap(self, obj):
return _wrap(obj, axdebug.IID_IDebugCodeContext)

View File

@@ -0,0 +1,238 @@
import os
import sys
import pythoncom
from win32com.axdebug import adb, axdebug, codecontainer, documents, expressions
from win32com.axdebug.util import _wrap
currentDebugger = None
class ModuleTreeNode:
"""Helper class for building a module tree"""
def __init__(self, module):
modName = module.__name__
self.moduleName = modName
self.module = module
self.realNode = None
self.cont = codecontainer.SourceModuleContainer(module)
def __repr__(self):
return f"<ModuleTreeNode wrapping {self.module}>"
def Attach(self, parentRealNode):
self.realNode.Attach(parentRealNode)
def Close(self):
self.module = None
self.cont = None
self.realNode = None
def BuildModule(module, built_nodes, rootNode, create_node_fn, create_node_args):
if module:
keep = module.__name__
keep = keep and (built_nodes.get(module) is None)
if keep and hasattr(module, "__file__"):
keep = os.path.splitext(module.__file__)[1].lower() not in [
".pyd",
".dll",
]
# keep = keep and module.__name__=='__main__'
if module and keep:
# print("keeping", module.__name__)
node = ModuleTreeNode(module)
built_nodes[module] = node
realNode = create_node_fn(*(node,) + create_node_args)
node.realNode = realNode
# Split into parent nodes.
parts = module.__name__.split(".")
if parts[-1][:8] == "__init__":
parts = parts[:-1]
parent = ".".join(parts[:-1])
parentNode = rootNode
if parent:
parentModule = sys.modules[parent]
BuildModule(
parentModule, built_nodes, rootNode, create_node_fn, create_node_args
)
if parentModule in built_nodes:
parentNode = built_nodes[parentModule].realNode
node.Attach(parentNode)
def RefreshAllModules(builtItems, rootNode, create_node, create_node_args):
for module in sys.modules.values():
BuildModule(module, builtItems, rootNode, create_node, create_node_args)
# realNode = pdm.CreateDebugDocumentHelper(None) # DebugDocumentHelper node?
# app.CreateApplicationNode() # doc provider node.
class CodeContainerProvider(documents.CodeContainerProvider):
def __init__(self, axdebugger):
self.axdebugger = axdebugger
documents.CodeContainerProvider.__init__(self)
self.currentNumModules = len(sys.modules)
self.nodes = {}
self.axdebugger.RefreshAllModules(self.nodes, self)
def FromFileName(self, fname):
# It appears we can't add modules during a debug session!
# if self.currentNumModules != len(sys.modules):
# self.axdebugger.RefreshAllModules(self.nodes, self)
# self.currentNumModules = len(sys.modules)
# for key in self.ccsAndNodes:
# print("File:", key)
return documents.CodeContainerProvider.FromFileName(self, fname)
def Close(self):
documents.CodeContainerProvider.Close(self)
self.axdebugger = None
print(f"Closing {len(self.nodes)} nodes")
for node in self.nodes.values():
node.Close()
self.nodes = {}
class OriginalInterfaceMaker:
def MakeInterfaces(self, pdm):
app = self.pdm.CreateApplication()
self.cookie = pdm.AddApplication(app)
root = app.GetRootNode()
return app, root
def CloseInterfaces(self, pdm):
pdm.RemoveApplication(self.cookie)
class SimpleHostStyleInterfaceMaker:
def MakeInterfaces(self, pdm):
app = pdm.GetDefaultApplication()
root = app.GetRootNode()
return app, root
def CloseInterfaces(self, pdm):
pass
class AXDebugger:
def __init__(self, interfaceMaker=None, processName=None):
if processName is None:
processName = "Python Process"
if interfaceMaker is None:
interfaceMaker = SimpleHostStyleInterfaceMaker()
self.pydebugger = adb.Debugger()
self.pdm = pythoncom.CoCreateInstance(
axdebug.CLSID_ProcessDebugManager,
None,
pythoncom.CLSCTX_ALL,
axdebug.IID_IProcessDebugManager,
)
self.app, self.root = interfaceMaker.MakeInterfaces(self.pdm)
self.app.SetName(processName)
self.interfaceMaker = interfaceMaker
expressionProvider = _wrap(
expressions.ProvideExpressionContexts(),
axdebug.IID_IProvideExpressionContexts,
)
self.expressionCookie = self.app.AddGlobalExpressionContextProvider(
expressionProvider
)
contProvider = CodeContainerProvider(self)
self.pydebugger.AttachApp(self.app, contProvider)
def Break(self):
# Get the frame we start debugging from - this is the frame 1 level up
try:
1 + ""
except:
frame = sys.exc_info()[2].tb_frame.f_back
# Get/create the debugger, and tell it to break.
self.app.StartDebugSession()
# self.app.CauseBreak()
self.pydebugger.SetupAXDebugging(None, frame)
self.pydebugger.set_trace()
def Close(self):
self.pydebugger.ResetAXDebugging()
self.interfaceMaker.CloseInterfaces(self.pdm)
self.pydebugger.CloseApp()
self.app.RemoveGlobalExpressionContextProvider(self.expressionCookie)
self.expressionCookie = None
self.pdm = None
self.app = None
self.pydebugger = None
self.root = None
def RefreshAllModules(self, nodes, containerProvider):
RefreshAllModules(
nodes, self.root, self.CreateApplicationNode, (containerProvider,)
)
def CreateApplicationNode(self, node, containerProvider):
realNode = self.app.CreateApplicationNode()
document = documents.DebugDocumentText(node.cont)
document = _wrap(document, axdebug.IID_IDebugDocument)
node.cont.debugDocument = document
provider = documents.DebugDocumentProvider(document)
provider = _wrap(provider, axdebug.IID_IDebugDocumentProvider)
realNode.SetDocumentProvider(provider)
containerProvider.AddCodeContainer(node.cont, realNode)
return realNode
def _GetCurrentDebugger():
global currentDebugger
if currentDebugger is None:
currentDebugger = AXDebugger()
return currentDebugger
def Break():
_GetCurrentDebugger().Break()
brk = Break
set_trace = Break
def dosomethingelse():
a = 2
b = "Hi there"
def dosomething():
a = 1
b = 2
dosomethingelse()
def test():
Break()
input("Waiting...")
dosomething()
print("Done")
if __name__ == "__main__":
print("About to test the debugging interfaces!")
test()
print(
f" {pythoncom._GetInterfaceCount()}/{pythoncom._GetGatewayCount()} com objects still alive"
)

View File

@@ -0,0 +1,135 @@
"""Management of documents for AXDebugging."""
import pythoncom
import win32api
from win32com.server.util import unwrap
from . import axdebug, gateways
from .util import _wrap, trace
# def trace(*args):
# pass
def GetGoodFileName(fname):
if fname[0] != "<":
return win32api.GetFullPathName(fname)
return fname
class DebugDocumentProvider(gateways.DebugDocumentProvider):
def __init__(self, doc):
self.doc = doc
def GetName(self, dnt):
return self.doc.GetName(dnt)
def GetDocumentClassId(self):
return self.doc.GetDocumentClassId()
def GetDocument(self):
return self.doc
class DebugDocumentText(gateways.DebugDocumentText):
_com_interfaces_ = (
gateways.DebugDocumentInfo._com_interfaces_
+ gateways.DebugDocumentText._com_interfaces_
+ gateways.DebugDocument._com_interfaces_
)
_public_methods_ = (
gateways.DebugDocumentInfo._public_methods_
+ gateways.DebugDocumentText._public_methods_
+ gateways.DebugDocument._public_methods_
)
# A class which implements a DebugDocumentText, using the functionality
# provided by a codeContainer
def __init__(self, codeContainer):
gateways.DebugDocumentText.__init__(self)
gateways.DebugDocumentInfo.__init__(self)
gateways.DebugDocument.__init__(self)
self.codeContainer = codeContainer
def _Close(self):
self.docContexts = None
# self.codeContainer._Close()
self.codeContainer = None
# IDebugDocumentInfo
def GetName(self, dnt):
return self.codeContainer.GetName(dnt)
def GetDocumentClassId(self):
return "{DF630910-1C1D-11d0-AE36-8C0F5E000000}"
# IDebugDocument has no methods!
#
# IDebugDocumentText methods.
# def GetDocumentAttributes
def GetSize(self):
# trace("GetSize")
return self.codeContainer.GetNumLines(), self.codeContainer.GetNumChars()
def GetPositionOfLine(self, cLineNumber):
return self.codeContainer.GetPositionOfLine(cLineNumber)
def GetLineOfPosition(self, charPos):
return self.codeContainer.GetLineOfPosition(charPos)
def GetText(self, charPos, maxChars, wantAttr):
# Get all the attributes, else the tokenizer will get upset.
# XXX - not yet!
# trace("GetText", charPos, maxChars, wantAttr)
cont = self.codeContainer
attr = cont.GetSyntaxColorAttributes()
return cont.GetText(), attr
def GetPositionOfContext(self, context):
trace("GetPositionOfContext", context)
context = unwrap(context)
return context.offset, context.length
# Return a DebugDocumentContext.
def GetContextOfPosition(self, charPos, maxChars):
# Make one
doc = _wrap(self, axdebug.IID_IDebugDocument)
rc = self.codeContainer.GetCodeContextAtPosition(charPos)
return rc.QueryInterface(axdebug.IID_IDebugDocumentContext)
class CodeContainerProvider:
"""An abstract Python class which provides code containers!
Given a Python file name (as the debugger knows it by) this will
return a CodeContainer interface suitable for use.
This provides a simple base implementation that simply supports
a dictionary of nodes and providers.
"""
def __init__(self):
self.ccsAndNodes = {}
def AddCodeContainer(self, cc, node=None):
fname = GetGoodFileName(cc.fileName)
self.ccsAndNodes[fname] = cc, node
def FromFileName(self, fname):
cc, node = self.ccsAndNodes.get(GetGoodFileName(fname), (None, None))
# if cc is None:
# print(f"FromFileName for {fname} returning None")
return cc
def Close(self):
for cc, node in self.ccsAndNodes.values():
try:
# Must close the node before closing the provider
# as node may make calls on provider (eg Reset breakpoints etc)
if node is not None:
node.Close()
cc._Close()
except pythoncom.com_error:
pass
self.ccsAndNodes = {}

View File

@@ -0,0 +1,57 @@
import traceback
import pythoncom
from win32com.axdebug import axdebug
from win32com.client.util import Enumerator
def DumpDebugApplicationNode(node, level=0):
# Recursive dump of a DebugApplicationNode
spacer = " " * level
for desc, attr in [
("Node Name", axdebug.DOCUMENTNAMETYPE_APPNODE),
("Title", axdebug.DOCUMENTNAMETYPE_TITLE),
("Filename", axdebug.DOCUMENTNAMETYPE_FILE_TAIL),
("URL", axdebug.DOCUMENTNAMETYPE_URL),
]:
try:
info = node.GetName(attr)
except pythoncom.com_error:
info = "<N/A>"
print(f"{spacer}{desc}: {info}")
try:
doc = node.GetDocument()
except pythoncom.com_error:
doc = None
if doc:
doctext = doc.QueryInterface(axdebug.IID_IDebugDocumentText)
numLines, numChars = doctext.GetSize()
# text, attr = doctext.GetText(0, 20, 1)
text, attr = doctext.GetText(0, numChars, 1)
print(f"{spacer}Text is '{text[:40] + '...'}', {len(text)} bytes long")
else:
print(f"{spacer*2}<No document available>")
for child in Enumerator(node.EnumChildren()):
DumpDebugApplicationNode(child, level + 1)
def dumpall():
dm = pythoncom.CoCreateInstance(
axdebug.CLSID_MachineDebugManager,
None,
pythoncom.CLSCTX_ALL,
axdebug.IID_IMachineDebugManager,
)
e = Enumerator(dm.EnumApplications())
for app in e:
print(f"Application: {app.GetName()}")
node = app.GetRootNode() # of type PyIDebugApplicationNode->PyIDebugDocumentProvider->PyIDebugDocumentInfo
DumpDebugApplicationNode(node)
if __name__ == "__main__":
try:
dumpall()
except:
traceback.print_exc()

View File

@@ -0,0 +1,212 @@
import io
import sys
import traceback
from pprint import pprint
import winerror
from . import axdebug, gateways
from .util import RaiseNotImpl, _wrap
# Given an object, return a nice string
def MakeNiceString(ob):
stream = io.StringIO()
pprint(ob, stream)
return stream.getvalue().strip()
class ProvideExpressionContexts(gateways.ProvideExpressionContexts):
pass
class ExpressionContext(gateways.DebugExpressionContext):
def __init__(self, frame):
self.frame = frame
def ParseLanguageText(self, code, radix, delim, flags):
return _wrap(
Expression(self.frame, code, radix, delim, flags),
axdebug.IID_IDebugExpression,
)
def GetLanguageInfo(self):
# print("GetLanguageInfo")
return "Python", "{DF630910-1C1D-11d0-AE36-8C0F5E000000}"
class Expression(gateways.DebugExpression):
def __init__(self, frame, code, radix, delim, flags):
self.callback = None
self.frame = frame
self.code = code
self.radix = radix
self.delim = delim
self.flags = flags
self.isComplete = 0
self.result = None
self.hresult = winerror.E_UNEXPECTED
def Start(self, callback):
try:
try:
try:
self.result = eval(
self.code, self.frame.f_globals, self.frame.f_locals
)
except SyntaxError:
exec(self.code, self.frame.f_globals, self.frame.f_locals)
self.result = ""
self.hresult = 0
except:
l = traceback.format_exception_only(
sys.exc_info()[0], sys.exc_info()[1]
)
# l is a list of strings with trailing "\n"
self.result = "\n".join(s[:-1] for s in l)
self.hresult = winerror.E_FAIL
finally:
self.isComplete = 1
callback.onComplete()
def Abort(self):
print("** ABORT **")
def QueryIsComplete(self):
return self.isComplete
def GetResultAsString(self):
# print("GetStrAsResult returning", self.result)
return self.hresult, MakeNiceString(self.result)
def GetResultAsDebugProperty(self):
result = _wrap(
DebugProperty(self.code, self.result, None, self.hresult),
axdebug.IID_IDebugProperty,
)
return self.hresult, result
def MakeEnumDebugProperty(object, dwFieldSpec, nRadix, iid, stackFrame=None):
name_vals = []
if hasattr(object, "items") and hasattr(object, "keys"): # If it is a dict.
name_vals = object.items()
dictionary = object
elif hasattr(object, "__dict__"): # object with dictionary, module
name_vals = object.__dict__.items()
dictionary = object.__dict__
infos = []
for name, val in name_vals:
infos.append(
GetPropertyInfo(name, val, dwFieldSpec, nRadix, 0, dictionary, stackFrame)
)
return _wrap(EnumDebugPropertyInfo(infos), axdebug.IID_IEnumDebugPropertyInfo)
def GetPropertyInfo(
obname, obvalue, dwFieldSpec, nRadix, hresult=0, dictionary=None, stackFrame=None
):
# returns a tuple
name = typ = value = fullname = attrib = dbgprop = None
if dwFieldSpec & axdebug.DBGPROP_INFO_VALUE:
value = MakeNiceString(obvalue)
if dwFieldSpec & axdebug.DBGPROP_INFO_NAME:
name = obname
if dwFieldSpec & axdebug.DBGPROP_INFO_TYPE:
if hresult:
typ = "Error"
else:
try:
typ = type(obvalue).__name__
except AttributeError:
typ = str(type(obvalue))
if dwFieldSpec & axdebug.DBGPROP_INFO_FULLNAME:
fullname = obname
if dwFieldSpec & axdebug.DBGPROP_INFO_ATTRIBUTES:
if hasattr(obvalue, "has_key") or hasattr(
obvalue, "__dict__"
): # If it is a dict or object
attrib = axdebug.DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE
else:
attrib = 0
if dwFieldSpec & axdebug.DBGPROP_INFO_DEBUGPROP:
dbgprop = _wrap(
DebugProperty(name, obvalue, None, hresult, dictionary, stackFrame),
axdebug.IID_IDebugProperty,
)
return name, typ, value, fullname, attrib, dbgprop
from win32com.server.util import ListEnumeratorGateway
class EnumDebugPropertyInfo(ListEnumeratorGateway):
"""A class to expose a Python sequence as an EnumDebugCodeContexts
Create an instance of this class passing a sequence (list, tuple, or
any sequence protocol supporting object) and it will automatically
support the EnumDebugCodeContexts interface for the object.
"""
_public_methods_ = ListEnumeratorGateway._public_methods_ + ["GetCount"]
_com_interfaces_ = [axdebug.IID_IEnumDebugPropertyInfo]
def GetCount(self):
return len(self._list_)
def _wrap(self, ob):
return ob
class DebugProperty:
_com_interfaces_ = [axdebug.IID_IDebugProperty]
_public_methods_ = [
"GetPropertyInfo",
"GetExtendedInfo",
"SetValueAsString",
"EnumMembers",
"GetParent",
]
def __init__(
self, name, value, parent=None, hresult=0, dictionary=None, stackFrame=None
):
self.name = name
self.value = value
self.parent = parent
self.hresult = hresult
self.dictionary = dictionary
self.stackFrame = stackFrame
def GetPropertyInfo(self, dwFieldSpec, nRadix):
return GetPropertyInfo(
self.name,
self.value,
dwFieldSpec,
nRadix,
self.hresult,
self.dictionary,
self.stackFrame,
)
def GetExtendedInfo(self): ### Note - not in the framework.
RaiseNotImpl("DebugProperty::GetExtendedInfo")
def SetValueAsString(self, value, radix):
if self.stackFrame and self.dictionary:
self.dictionary[self.name] = eval(
value, self.stackFrame.f_globals, self.stackFrame.f_locals
)
else:
RaiseNotImpl("DebugProperty::SetValueAsString")
def EnumMembers(self, dwFieldSpec, nRadix, iid):
# Returns IEnumDebugPropertyInfo
return MakeEnumDebugProperty(
self.value, dwFieldSpec, nRadix, iid, self.stackFrame
)
def GetParent(self):
# return IDebugProperty
RaiseNotImpl("DebugProperty::GetParent")

View File

@@ -0,0 +1,583 @@
# Classes which describe interfaces.
import pythoncom
import win32com.server.connect
import winerror
from win32com.axdebug import axdebug
from win32com.axdebug.util import RaiseNotImpl, _wrap
from win32com.server.exception import COMException
from win32com.server.util import ListEnumeratorGateway
class EnumDebugCodeContexts(ListEnumeratorGateway):
"""A class to expose a Python sequence as an EnumDebugCodeContexts
Create an instance of this class passing a sequence (list, tuple, or
any sequence protocol supporting object) and it will automatically
support the EnumDebugCodeContexts interface for the object.
"""
_com_interfaces_ = [axdebug.IID_IEnumDebugCodeContexts]
class EnumDebugStackFrames(ListEnumeratorGateway):
"""A class to expose a Python sequence as an EnumDebugStackFrames
Create an instance of this class passing a sequence (list, tuple, or
any sequence protocol supporting object) and it will automatically
support the EnumDebugStackFrames interface for the object.
"""
_com_interfaces_ = [axdebug.IID_IEnumDebugStackFrames]
class EnumDebugApplicationNodes(ListEnumeratorGateway):
"""A class to expose a Python sequence as an EnumDebugStackFrames
Create an instance of this class passing a sequence (list, tuple, or
any sequence protocol supporting object) and it will automatically
support the EnumDebugApplicationNodes interface for the object.
"""
_com_interfaces_ = [axdebug.IID_IEnumDebugApplicationNodes]
class EnumRemoteDebugApplications(ListEnumeratorGateway):
_com_interfaces_ = [axdebug.IID_IEnumRemoteDebugApplications]
class EnumRemoteDebugApplicationThreads(ListEnumeratorGateway):
_com_interfaces_ = [axdebug.IID_IEnumRemoteDebugApplicationThreads]
class DebugDocumentInfo:
_public_methods_ = ["GetName", "GetDocumentClassId"]
_com_interfaces_ = [axdebug.IID_IDebugDocumentInfo]
def __init__(self):
pass
def GetName(self, dnt):
"""Get the one of the name of the document
dnt -- int DOCUMENTNAMETYPE
"""
RaiseNotImpl("GetName")
def GetDocumentClassId(self):
"""
Result must be an IID object (or string representing one).
"""
RaiseNotImpl("GetDocumentClassId")
class DebugDocumentProvider(DebugDocumentInfo):
_public_methods_ = DebugDocumentInfo._public_methods_ + ["GetDocument"]
_com_interfaces_ = DebugDocumentInfo._com_interfaces_ + [
axdebug.IID_IDebugDocumentProvider
]
def GetDocument(self):
RaiseNotImpl("GetDocument")
class DebugApplicationNode(DebugDocumentProvider):
"""Provides the functionality of IDebugDocumentProvider, plus a context within a project tree."""
_public_methods_ = (
"""EnumChildren GetParent SetDocumentProvider
Close Attach Detach""".split()
+ DebugDocumentProvider._public_methods_
)
_com_interfaces_ = [
axdebug.IID_IDebugDocumentProvider
] + DebugDocumentProvider._com_interfaces_
def __init__(self):
DebugDocumentProvider.__init__(self)
def EnumChildren(self):
# Result is type PyIEnumDebugApplicationNodes
RaiseNotImpl("EnumChildren")
def GetParent(self):
# result is type PyIDebugApplicationNode
RaiseNotImpl("GetParent")
def SetDocumentProvider(self, pddp): # PyIDebugDocumentProvider pddp
# void result.
RaiseNotImpl("SetDocumentProvider")
def Close(self):
# void result.
RaiseNotImpl("Close")
def Attach(self, parent): # PyIDebugApplicationNode
# void result.
RaiseNotImpl("Attach")
def Detach(self):
# void result.
RaiseNotImpl("Detach")
class DebugApplicationNodeEvents:
"""Event interface for DebugApplicationNode object."""
_public_methods_ = "onAddChild onRemoveChild onDetach".split()
_com_interfaces_ = [axdebug.IID_IDebugApplicationNodeEvents]
def __init__(self):
pass
def onAddChild(self, child): # PyIDebugApplicationNode
# void result.
RaiseNotImpl("onAddChild")
def onRemoveChild(self, child): # PyIDebugApplicationNode
# void result.
RaiseNotImpl("onRemoveChild")
def onDetach(self):
# void result.
RaiseNotImpl("onDetach")
def onAttach(self, parent): # PyIDebugApplicationNode
# void result.
RaiseNotImpl("onAttach")
class DebugDocument(DebugDocumentInfo):
"""The base interface to all debug documents."""
_public_methods_ = DebugDocumentInfo._public_methods_
_com_interfaces_ = [axdebug.IID_IDebugDocument] + DebugDocumentInfo._com_interfaces_
class DebugDocumentText(DebugDocument):
"""The interface to a text only debug document."""
_com_interfaces_ = [axdebug.IID_IDebugDocumentText] + DebugDocument._com_interfaces_
_public_methods_ = [
"GetDocumentAttributes",
"GetSize",
"GetPositionOfLine",
"GetLineOfPosition",
"GetText",
"GetPositionOfContext",
"GetContextOfPosition",
] + DebugDocument._public_methods_
def __init__(self):
pass
# IDebugDocumentText
def GetDocumentAttributes(self):
# Result is int (TEXT_DOC_ATTR)
RaiseNotImpl("GetDocumentAttributes")
def GetSize(self):
# Result is (numLines, numChars)
RaiseNotImpl("GetSize")
def GetPositionOfLine(self, cLineNumber):
# Result is int char position
RaiseNotImpl("GetPositionOfLine")
def GetLineOfPosition(self, charPos):
# Result is int, int (lineNo, offset)
RaiseNotImpl("GetLineOfPosition")
def GetText(self, charPos, maxChars, wantAttr):
"""Params
charPos -- integer
maxChars -- integer
wantAttr -- Should the function compute attributes.
Return value must be (string, attribtues). attributes may be
None if(not wantAttr)
"""
RaiseNotImpl("GetText")
def GetPositionOfContext(self, debugDocumentContext):
"""Params
debugDocumentContext -- a PyIDebugDocumentContext object.
Return value must be (charPos, numChars)
"""
RaiseNotImpl("GetPositionOfContext")
def GetContextOfPosition(self, charPos, maxChars):
"""Params are integers.
Return value must be PyIDebugDocumentContext object
"""
print(self)
RaiseNotImpl("GetContextOfPosition")
class DebugDocumentTextExternalAuthor:
"""Allow external editors to edit file-based debugger documents, and to notify the document when the source file has been changed."""
_public_methods_ = ["GetPathName", "GetFileName", "NotifyChanged"]
_com_interfaces_ = [axdebug.IID_IDebugDocumentTextExternalAuthor]
def __init__(self):
pass
def GetPathName(self):
"""Return the full path (including file name) to the document's source file.
Result must be (filename, fIsOriginal), where
- if fIsOriginalPath is TRUE if the path refers to the original file for the document.
- if fIsOriginalPath is FALSE if the path refers to a newly created temporary file.
raise COMException(winerror.E_FAIL) if no source file can be created/determined.
"""
RaiseNotImpl("GetPathName")
def GetFileName(self):
"""Return just the name of the document, with no path information. (Used for "Save As...")
Result is a string
"""
RaiseNotImpl("GetFileName")
def NotifyChanged(self):
"""Notify the host that the document's source file has been saved and
that its contents should be refreshed.
"""
RaiseNotImpl("NotifyChanged")
class DebugDocumentTextEvents:
_public_methods_ = """onDestroy onInsertText onRemoveText
onReplaceText onUpdateTextAttributes
onUpdateDocumentAttributes""".split()
_com_interfaces_ = [axdebug.IID_IDebugDocumentTextEvents]
def __init__(self):
pass
def onDestroy(self):
# Result is void.
RaiseNotImpl("onDestroy")
def onInsertText(self, cCharacterPosition, cNumToInsert):
# Result is void.
RaiseNotImpl("onInsertText")
def onRemoveText(self, cCharacterPosition, cNumToRemove):
# Result is void.
RaiseNotImpl("onRemoveText")
def onReplaceText(self, cCharacterPosition, cNumToReplace):
# Result is void.
RaiseNotImpl("onReplaceText")
def onUpdateTextAttributes(self, cCharacterPosition, cNumToUpdate):
# Result is void.
RaiseNotImpl("onUpdateTextAttributes")
def onUpdateDocumentAttributes(self, textdocattr): # TEXT_DOC_ATTR
# Result is void.
RaiseNotImpl("onUpdateDocumentAttributes")
class DebugDocumentContext:
_public_methods_ = ["GetDocument", "EnumCodeContexts"]
_com_interfaces_ = [axdebug.IID_IDebugDocumentContext]
def __init__(self):
pass
def GetDocument(self):
"""Return value must be a PyIDebugDocument object"""
RaiseNotImpl("GetDocument")
def EnumCodeContexts(self):
"""Return value must be a PyIEnumDebugCodeContexts object"""
RaiseNotImpl("EnumCodeContexts")
class DebugCodeContext:
_public_methods_ = ["GetDocumentContext", "SetBreakPoint"]
_com_interfaces_ = [axdebug.IID_IDebugCodeContext]
def __init__(self):
pass
def GetDocumentContext(self):
"""Return value must be a PyIDebugDocumentContext object"""
RaiseNotImpl("GetDocumentContext")
def SetBreakPoint(self, bps):
"""bps -- an integer with flags."""
RaiseNotImpl("SetBreakPoint")
class DebugStackFrame:
"""Abstraction representing a logical stack frame on the stack of a thread."""
_public_methods_ = [
"GetCodeContext",
"GetDescriptionString",
"GetLanguageString",
"GetThread",
"GetDebugProperty",
]
_com_interfaces_ = [axdebug.IID_IDebugStackFrame]
def __init__(self):
pass
def GetCodeContext(self):
"""Returns the current code context associated with the stack frame.
Return value must be a IDebugCodeContext object
"""
RaiseNotImpl("GetCodeContext")
def GetDescriptionString(self, fLong):
"""Returns a textual description of the stack frame.
fLong -- A flag indicating if the long name is requested.
"""
RaiseNotImpl("GetDescriptionString")
def GetLanguageString(self):
"""Returns a short or long textual description of the language.
fLong -- A flag indicating if the long name is requested.
"""
RaiseNotImpl("GetLanguageString")
def GetThread(self):
"""Returns the thread associated with this stack frame.
Result must be a IDebugApplicationThread
"""
RaiseNotImpl("GetThread")
def GetDebugProperty(self):
RaiseNotImpl("GetDebugProperty")
class DebugDocumentHost:
"""The interface from the IDebugDocumentHelper back to
the smart host or language engine. This interface
exposes host specific functionality such as syntax coloring.
"""
_public_methods_ = [
"GetDeferredText",
"GetScriptTextAttributes",
"OnCreateDocumentContext",
"GetPathName",
"GetFileName",
"NotifyChanged",
]
_com_interfaces_ = [axdebug.IID_IDebugDocumentHost]
def __init__(self):
pass
def GetDeferredText(self, dwTextStartCookie, maxChars, bWantAttr):
RaiseNotImpl("GetDeferredText")
def GetScriptTextAttributes(self, codeText, delimterText, flags):
# Result must be an attribute sequence of same "length" as the code.
RaiseNotImpl("GetScriptTextAttributes")
def OnCreateDocumentContext(self):
# Result must be a PyIUnknown
RaiseNotImpl("OnCreateDocumentContext")
def GetPathName(self):
# Result must be (string, int) where the int is a BOOL
# - TRUE if the path refers to the original file for the document.
# - FALSE if the path refers to a newly created temporary file.
# - raise COMException(scode=E_FAIL) if no source file can be created/determined.
RaiseNotImpl("GetPathName")
def GetFileName(self):
# Result is a string with just the name of the document, no path information.
RaiseNotImpl("GetFileName")
def NotifyChanged(self):
RaiseNotImpl("NotifyChanged")
# Additional gateway related functions.
class DebugDocumentTextConnectServer:
_public_methods_ = (
win32com.server.connect.IConnectionPointContainer_methods
+ win32com.server.connect.IConnectionPoint_methods
)
_com_interfaces_ = [
pythoncom.IID_IConnectionPoint,
pythoncom.IID_IConnectionPointContainer,
]
# IConnectionPoint interfaces
def __init__(self):
self.cookieNo = -1
self.connections = {}
def EnumConnections(self):
RaiseNotImpl("EnumConnections")
def GetConnectionInterface(self):
RaiseNotImpl("GetConnectionInterface")
def GetConnectionPointContainer(self):
return _wrap(self)
def Advise(self, pUnk):
# Creates a connection to the client. Simply allocate a new cookie,
# find the clients interface, and store it in a dictionary.
interface = pUnk.QueryInterface(axdebug.IID_IDebugDocumentTextEvents, 1)
self.cookieNo += 1
self.connections[self.cookieNo] = interface
return self.cookieNo
def Unadvise(self, cookie):
# Destroy a connection - simply delete interface from the map.
try:
del self.connections[cookie]
except KeyError:
return COMException(scode=winerror.E_UNEXPECTED)
# IConnectionPointContainer interfaces
def EnumConnectionPoints(self):
RaiseNotImpl("EnumConnectionPoints")
def FindConnectionPoint(self, iid):
# Find a connection we support. Only support the single event interface.
if iid == axdebug.IID_IDebugDocumentTextEvents:
return _wrap(self)
raise COMException(scode=winerror.E_NOINTERFACE) # ??
class RemoteDebugApplicationEvents:
_public_methods_ = [
"OnConnectDebugger",
"OnDisconnectDebugger",
"OnSetName",
"OnDebugOutput",
"OnClose",
"OnEnterBreakPoint",
"OnLeaveBreakPoint",
"OnCreateThread",
"OnDestroyThread",
"OnBreakFlagChange",
]
_com_interfaces_ = [axdebug.IID_IRemoteDebugApplicationEvents]
def OnConnectDebugger(self, appDebugger):
"""appDebugger -- a PyIApplicationDebugger"""
RaiseNotImpl("OnConnectDebugger")
def OnDisconnectDebugger(self):
RaiseNotImpl("OnDisconnectDebugger")
def OnSetName(self, name):
RaiseNotImpl("OnSetName")
def OnDebugOutput(self, string):
RaiseNotImpl("OnDebugOutput")
def OnClose(self):
RaiseNotImpl("OnClose")
def OnEnterBreakPoint(self, rdat):
"""rdat -- PyIRemoteDebugApplicationThread"""
RaiseNotImpl("OnEnterBreakPoint")
def OnLeaveBreakPoint(self, rdat):
"""rdat -- PyIRemoteDebugApplicationThread"""
RaiseNotImpl("OnLeaveBreakPoint")
def OnCreateThread(self, rdat):
"""rdat -- PyIRemoteDebugApplicationThread"""
RaiseNotImpl("OnCreateThread")
def OnDestroyThread(self, rdat):
"""rdat -- PyIRemoteDebugApplicationThread"""
RaiseNotImpl("OnDestroyThread")
def OnBreakFlagChange(self, abf, rdat):
"""abf -- int - one of the axdebug.APPBREAKFLAGS constants
rdat -- PyIRemoteDebugApplicationThread
RaiseNotImpl("OnBreakFlagChange")
"""
class DebugExpressionContext:
_public_methods_ = ["ParseLanguageText", "GetLanguageInfo"]
_com_interfaces_ = [axdebug.IID_IDebugExpressionContext]
def __init__(self):
pass
def ParseLanguageText(self, code, radix, delim, flags):
"""
result is IDebugExpression
"""
RaiseNotImpl("ParseLanguageText")
def GetLanguageInfo(self):
"""
result is (string langName, iid langId)
"""
RaiseNotImpl("GetLanguageInfo")
class DebugExpression:
_public_methods_ = [
"Start",
"Abort",
"QueryIsComplete",
"GetResultAsString",
"GetResultAsDebugProperty",
]
_com_interfaces_ = [axdebug.IID_IDebugExpression]
def Start(self, callback):
"""
callback -- an IDebugExpressionCallback
result - void
"""
RaiseNotImpl("Start")
def Abort(self):
"""
no params
result -- void
"""
RaiseNotImpl("Abort")
def QueryIsComplete(self):
"""
no params
result -- void
"""
RaiseNotImpl("QueryIsComplete")
def GetResultAsString(self):
RaiseNotImpl("GetResultAsString")
def GetResultAsDebugProperty(self):
RaiseNotImpl("GetResultAsDebugProperty")
class ProvideExpressionContexts:
_public_methods_ = ["EnumExpressionContexts"]
_com_interfaces_ = [axdebug.IID_IProvideExpressionContexts]
def EnumExpressionContexts(self):
RaiseNotImpl("EnumExpressionContexts")

View File

@@ -0,0 +1,178 @@
"""Support for stack-frames.
Provides Implements a nearly complete wrapper for a stack frame.
"""
import pythoncom
from . import axdebug, expressions, gateways
from .util import RaiseNotImpl, _wrap, trace
# def trace(*args):
# pass
class EnumDebugStackFrames(gateways.EnumDebugStackFrames):
"""A class that given a debugger object, can return an enumerator
of DebugStackFrame objects.
"""
def __init__(self, debugger):
infos = []
frame = debugger.currentframe
# print("Stack check")
while frame:
# print(" Checking frame", frame.f_code.co_filename, frame.f_lineno-1, frame.f_trace)
# Get a DebugCodeContext for the stack frame. If we fail, then it
# is not debuggable, and therefore not worth displaying.
cc = debugger.codeContainerProvider.FromFileName(frame.f_code.co_filename)
if cc is not None:
try:
address = frame.f_locals["__axstack_address__"]
except KeyError:
# print("Couldn't find stack address for",frame.f_code.co_filename, frame.f_lineno-1)
# Use this one, even tho it is wrong :-(
address = axdebug.GetStackAddress()
frameInfo = (
DebugStackFrame(frame, frame.f_lineno - 1, cc),
address,
address + 1,
0,
None,
)
infos.append(frameInfo)
# print("- Kept!")
# else:
# print("- rejected")
frame = frame.f_back
gateways.EnumDebugStackFrames.__init__(self, infos, 0)
# def __del__(self):
# print("EnumDebugStackFrames dieing")
def Next(self, count):
return gateways.EnumDebugStackFrames.Next(self, count)
# def _query_interface_(self, iid):
# from win32com.util import IIDToInterfaceName
# print(f"EnumDebugStackFrames QI with {IIDToInterfaceName(iid)} ({iid})")
# return 0
def _wrap(self, obj):
# This enum returns a tuple, with 2 com objects in it.
obFrame, min, lim, fFinal, obFinal = obj
obFrame = _wrap(obFrame, axdebug.IID_IDebugStackFrame)
if obFinal:
obFinal = _wrap(obFinal, pythoncom.IID_IUnknown)
return obFrame, min, lim, fFinal, obFinal
class DebugStackFrame(gateways.DebugStackFrame):
def __init__(self, frame, lineno, codeContainer):
self.frame = frame
self.lineno = lineno
self.codeContainer = codeContainer
self.expressionContext = None
# def __del__(self):
# print("DSF dieing")
def _query_interface_(self, iid):
if iid == axdebug.IID_IDebugExpressionContext:
if self.expressionContext is None:
self.expressionContext = _wrap(
expressions.ExpressionContext(self.frame),
axdebug.IID_IDebugExpressionContext,
)
return self.expressionContext
# from win32com.util import IIDToInterfaceName
# print(f"DebugStackFrame QI with {IIDToInterfaceName(iid)} ({iid})")
return 0
#
# The following need implementation
def GetThread(self):
"""Returns the thread associated with this stack frame.
Result must be a IDebugApplicationThread
"""
RaiseNotImpl("GetThread")
def GetCodeContext(self):
offset = self.codeContainer.GetPositionOfLine(self.lineno)
return self.codeContainer.GetCodeContextAtPosition(offset)
#
# The following are usefully implemented
def GetDescriptionString(self, fLong):
filename = self.frame.f_code.co_filename
s = ""
if 0: # fLong:
s += filename
if self.frame.f_code.co_name:
s += self.frame.f_code.co_name
else:
s += "<lambda>"
return s
def GetLanguageString(self, fLong):
if fLong:
return "Python ActiveX Scripting Engine"
else:
return "Python"
def GetDebugProperty(self):
return _wrap(StackFrameDebugProperty(self.frame), axdebug.IID_IDebugProperty)
class DebugStackFrameSniffer:
_public_methods_ = ["EnumStackFrames"]
_com_interfaces_ = [axdebug.IID_IDebugStackFrameSniffer]
def __init__(self, debugger):
self.debugger = debugger
trace("DebugStackFrameSniffer instantiated")
# def __del__(self):
# print("DSFS dieing")
def EnumStackFrames(self):
trace("DebugStackFrameSniffer.EnumStackFrames called")
return _wrap(
EnumDebugStackFrames(self.debugger), axdebug.IID_IEnumDebugStackFrames
)
# A DebugProperty for a stack frame.
class StackFrameDebugProperty:
_com_interfaces_ = [axdebug.IID_IDebugProperty]
_public_methods_ = [
"GetPropertyInfo",
"GetExtendedInfo",
"SetValueAsString",
"EnumMembers",
"GetParent",
]
def __init__(self, frame):
self.frame = frame
def GetPropertyInfo(self, dwFieldSpec, nRadix):
RaiseNotImpl("StackFrameDebugProperty::GetPropertyInfo")
def GetExtendedInfo(self): ### Note - not in the framework.
RaiseNotImpl("StackFrameDebugProperty::GetExtendedInfo")
def SetValueAsString(self, value, radix):
#
RaiseNotImpl("DebugProperty::SetValueAsString")
def EnumMembers(self, dwFieldSpec, nRadix, iid):
print("EnumMembers", dwFieldSpec, nRadix, iid)
from . import expressions
return expressions.MakeEnumDebugProperty(
self.frame.f_locals, dwFieldSpec, nRadix, iid, self.frame
)
def GetParent(self):
# return IDebugProperty
RaiseNotImpl("DebugProperty::GetParent")

View File

@@ -0,0 +1,98 @@
# Utility function for wrapping objects. Centralising allows me to turn
# debugging on and off for the entire package in a single spot.
import os
import sys
import traceback
import win32api
import win32com.server.dispatcher
import win32com.server.policy
import win32com.server.util
import winerror
from win32com.server.exception import COMException
debugging = "DEBUG_AXDEBUG" in os.environ
def trace(*args):
if not debugging:
return
print(str(win32api.GetCurrentThreadId()) + ":", end=" ")
for arg in args:
print(arg, end=" ")
print()
# The AXDebugging implementation assumes that the returned COM pointers are in
# some cases identical. Eg, from a C++ perspective:
# p->GetSomeInterface( &p1 );
# p->GetSomeInterface( &p2 );
# p1==p2
# By default, this is _not_ true for Python.
# (Now this is only true for Document objects, and Python
# now does ensure this.
def _wrap(object, iid):
useDispatcher = win32com.server.policy.DispatcherWin32trace if debugging else None
return win32com.server.util.wrap(object, iid, useDispatcher=useDispatcher)
def RaiseNotImpl(who=None):
if who is not None:
print(f"********* Function {who} Raising E_NOTIMPL ************")
# Print a sort-of "traceback", dumping all the frames leading to here.
for frame, i in traceback.walk_stack(sys._getframe()):
print(f"File: {frame.f_code.co_filename}, Line: {frame.f_lineno}")
# and raise the exception for COM
raise COMException(scode=winerror.E_NOTIMPL)
class Dispatcher(win32com.server.dispatcher.DispatcherWin32trace):
def __init__(self, policyClass, object):
win32com.server.dispatcher.DispatcherTrace.__init__(self, policyClass, object)
import win32traceutil # Sets up everything.
# print(f"Object with win32trace dispatcher created ({object})")
def _QueryInterface_(self, iid):
rc = win32com.server.policy.DispatcherBase._QueryInterface_(self, iid)
# if not rc:
# self._trace_(f"in _QueryInterface_ with unsupported IID {IIDToInterfaceName(iid)} ({iid})\n")
return rc
def _Invoke_(self, dispid, lcid, wFlags, args):
print(
"In Invoke with",
dispid,
lcid,
wFlags,
args,
"with object",
self.policy._obj_,
)
try:
rc = win32com.server.policy.DispatcherBase._Invoke_(
self, dispid, lcid, wFlags, args
)
# print("Invoke of", dispid, "returning", rc)
return rc
except COMException:
t, v, tb = sys.exc_info()
tb = None # A cycle
scode = v.scode
try:
desc = f" ({v.description})"
except AttributeError:
desc = ""
print(f"*** Invoke of {dispid} raised COM exception 0x{scode:x}{desc}")
except:
print(f"*** Invoke of {dispid} failed:")
typ, val, tb = sys.exc_info()
import traceback
traceback.print_exception(typ, val, tb)
raise