Showing posts with label Path. Show all posts
Showing posts with label Path. Show all posts

Friday, 9 August 2019

Python MIDL Launcher

If you ever need worked with COM type libraries then you will have no doubt bumped in MIDL which is the Microsoft Interface Definition Language compiler. This is a command line tool but if you want to incorporate it into part of your build process you are going to need to shell or launch the process. I have given a C# launcher Midl here. Also I have given a windows batch file launcher here. Below I give a Python equivalent Midl launcher.

I find this code necessary because I need to alter the environment variables PATH and INCLUDE in order to get MIDL to work. The code below also goes on to load the newly create type library and then register it as this is the majority use case (feel free to omit if not appropriate).

import pythoncom

class MidlLauncher(object):
    @staticmethod
    def CompileTypelib(idlFullFileName):
        from distutils.dep_util import newer
        import os
        import subprocess
        try:
            tlb = os.path.splitext(idlFullFileName)[0] + '.tlb'
            if os.path.isfile(idlFullFileName): 

                if newer(idlFullFileName, tlb):

                    import subprocess, os
                    midl_env = os.environ.copy()
                    
                    midl_env["PATH"] = 'C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\;' + midl_env["PATH"]
                    mustInclude = ("C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.16299.0\\um\\;" + 
                                          "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.16299.0\\shared\\;" +
                                          "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;" )
                    if "INCLUDE" in midl_env:
                        midl_env["INCLUDE"] = mustInclude + midl_env["INCLUDE"]
                    else:
                        midl_env["INCLUDE"] = mustInclude 

                    midl = subprocess.Popen(["C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.16299.0\\x86\\midl.exe", idlFullFileName, "/tlb" , tlb],env=midl_env)
                    midl.wait()

                    print("Registering %s" % (tlb,))
                    tli = pythoncom.LoadTypeLib(tlb)
                    pythoncom.RegisterTypeLib(tli,tlb)
        except Exception as e:
            print("Error: " + str(e) + "\n")

Sunday, 24 February 2019

Command Line - Running Midl.exe from a batch file

Just a quick one. I have had cause to run the MIDL.exe compiler as I am researching types and type libraries. MIDL.exe can be fussy in that it expects some environmental variables to be set, PATH and INCLUDE, so it can pick up all the files it needs. This is a little frustrating if you forget its prerequisites so I have packaged the details into a batch file to be run from the command line.

There is not much to say about this except note how we can alter (append) the PATH and INCLUDE variables. Also we we can pass arguments to the batch file and then reference them with %1 %2 etc.


SET INCLUDE=C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\um\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\shared\;C:\Windows\Microsoft.NET\Framework\v4.0.30319
SET PATH=%PATH%C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\;

"C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86\midl.exe" %1 /tlb %2
pause
Exit

Here is an example of how to run it, one supplies the idl file first and the tlb (type library) file second.

n:\midl.bat "n:\MyTypeLib.idl" "n:\MyTypeLib.tlb"

I actually like this little batch file and I am beginning to wonder what else this vintage technology can offer.

Tuesday, 19 June 2018

SVG - VBA - Extracting Path Data

So in the last few posts I have been travelling towards a solution that allows code to scrape data from a Bank Of England PDF. I have got so far as to break up the PDF into separate SVG files. SVG files are easier to work with because they are Xml based.

XPath in VBA

So my first language is VBA and I can quickly give some test code to demonstrate the XPath logic before I delve into a Python solution

Sub TestXml()
    '*Tools->References->Microsoft XML, v6.0
    Dim xml As MSXML2.DOMDocument60
    Set xml = New MSXML2.DOMDocument60
    
    xml.setProperty "SelectionNamespaces", "xmlns:svg='http://www.w3.org/2000/svg'"
    xml.Load "C:\Users\Simon\Downloads\pdf_skunkworks\inflation-report-may-2018-page6.svg"
    
    Debug.Assert xml.parseError.ErrorCode = 0
    
    Dim xmlBluePaths As MSXML2.IXMLDOMNodeList
    Set xmlBluePaths = xml.SelectNodes("//svg:path[@style='fill:#19518b;fill-opacity:1;fill-rule:nonzero;stroke:none']")
    
    Debug.Assert xmlBluePaths.Length = 28
    
    Dim xmlRedPaths As MSXML2.IXMLDOMNodeList
    Set xmlRedPaths = xml.SelectNodes("//svg:path[@style='fill:#a80c3d;fill-opacity:1;fill-rule:nonzero;stroke:none']")
    
    Debug.Assert xmlRedPaths.Length = 28
    
    Dim xmlGreyPaths As MSXML2.IXMLDOMNodeList
    Set xmlGreyPaths = xml.SelectNodes("//svg:path[@style='fill:#a98b6e;fill-opacity:1;fill-rule:nonzero;stroke:none']")
    
    Debug.Assert xmlGreyPaths.Length = 28

    Dim xmlElement As MSXML2.IXMLDOMElement
    Set xmlElement = xmlBluePaths.Item(0)
    
    Debug.Print xmlElement.xml
    Debug.Print xmlElement.getAttribute("d")

End Sub

The next problem however is how to parse the path data which can be found in the d attribute of a path element, here is an example of an element...

<path xmlns="http://www.w3.org/2000/svg" id="path670" style="fill:#19518b;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 241.666,133.557 h 2.364 v -25.886 h -2.364 z"/>

Within that element one can see the path data packed into the d attribute...

m 241.666,133.557 h 2.364 v -25.886 h -2.364 z

So we need code to parse this path data. But I am not going to give that code in VBA, instead I have a Python library to show you, see next post.

Saturday, 2 June 2018

VBA - Windows API - Code to shorten path to DOS 8.3 format

So just setting up some environment variables and irritatingly they misbehave if they have spaces, converting from a long windows path to a DOS 8.3 format can help even though spaces are legal in DOS 8.3. Anyway, we can write a program to call the relevant windows API. Here is some code adapted from a VBA book by Michael Schwimmer. I ran this for my Python path and I got no spaces so I'm happy.


Option Explicit

'* https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" _
        (ByVal lpszLongPath As String, _
        ByVal lpszShortPath As String, _
        ByVal cchBuffer As Long) As Long


Function ShortenPath(ByVal sLongPath As String) As String
    '* adapted from Michael Schwimmer
    '* https://www.amazon.de/Excel-VBA-Lerntest-Einstieg-Anspruchsvolle-Master/dp/3827325250/ref=sr_1_1?s=software

    Dim lShortLen As Long
    lShortLen = GetShortPathName(sLongPath, 0, 0)  '* first call it with null to get length only

    Dim sBuffer As String
    sBuffer = String(lShortLen, 0)
    
    lShortLen = GetShortPathName(sLongPath, sBuffer, lShortLen) '* now call it a buffer
    
    If lShortLen > 0 Then
        ShortenPath = Trim(Left$(sBuffer, lShortLen))
    End If

End Function

Sub TestShortenPath()
    Debug.Print ShortenPath("c:\Program Files\")
    Debug.Print ShortenPath("c:\Program Files (x86)\")
    Debug.Print ShortenPath("C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64")
End Sub

Running the procedure TestShortenPath() gives the following output

c:\PROGRA~1\
c:\PROGRA~2\
C:\PROGRA~2\MICROS~4\Shared\PYTHON~1

Saturday, 5 May 2018

VBA - Windows - Code to see if exe is reachable with Path environment variable

I'm trying to run a program called midl.exe but it currently complains that it cannot find the program cl.exe which means I need to triage my Path environment variable so it can be reached. It occurred to me that it would be nice to mimic/predict Windows behaviour as it walks the directories of the Path environment variable. Luckily SO comes to the rescue and yields the PathFindOnPath windows api function.

We can write some client VBA code to call this WinApi function, here it is

Option Explicit

'// https://msdn.microsoft.com/en-us/library/bb773594%28VS.85%29.aspx
Declare Function PathFindOnPath Lib "SHLWAPI.DLL" Alias "PathFindOnPathA" (ByVal pszFile As String, ppszOtherDirs As String) As Long
'BOOL PathFindOnPath(
'  _Inout_  LPTSTR  pszFile,
'  _In_opt_ LPCTSTR *ppszOtherDirs
');

Function PathFindOnPathShim(ByVal pszFile As String, ByVal ppszOtherDirs As String, ByRef sResult As String) As Boolean
    Dim lRetval As Long, sBuffer As String
    
    sBuffer = Left$(pszFile & String$(256, vbNullChar), 256)
    
    lRetval = PathFindOnPath(sBuffer, ppszOtherDirs)
    If lRetval = 1 Then
        sResult = Mid$(sBuffer, 1, InStr(sBuffer, vbNullChar))
        PathFindOnPathShim = True
    Else
        sResult = ""
        PathFindOnPathShim = False
    End If
End Function


Sub TestPathFindOnPathShim()
    
    Dim sFile As String, sOtherDirs As String, sResult As String
    
    sFile = "cl.exe"
    sOtherDirs = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin"
    
    If PathFindOnPathShim(sFile, sOtherDirs, sResult) Then
        Debug.Print sResult
    Else
        Debug.Print "not reachable via %PATH%"
    End If

End Sub

Sub WritePathEnvToSheet()

    Dim sPath As String
    sPath = Environ$("PATH")
    
    Dim vSplit As Variant
    vSplit = VBA.Split(sPath, ";")
    
    Dim vPastable As Variant
    vPastable = Application.Transpose(vSplit)
    
    Sheet1.Cells(1, 1).Resize(UBound(vSplit) - LBound(vSplit) + 1, 1).Value2 = vPastable
    Stop


End Sub