扫描 COM 引用以帮助生成 Reg-Free COM 清单
Scan for COM references to help generate Reg-Free COM manifests
我正在 InstallShield 2015 中将一个大型安装项目重新实施为 MSI 安装程序(它以前是来自更旧版本 InstallShield 的 InstallScript 安装程序)。我想做的许多改进之一是允许通过使用清单文件和遵循免注册 COM 激活模式以免注册方式解析所有 COM 引用。我认为这在所有情况下都是不可能的,因为我们有一些 MMC 管理单元,据我所知,它不能使用 reg-free COM,但我想尽可能接近。
挑战在于找出我需要创建的所有清单。我们提供数以千计的文件。我正在寻找可以帮助扫描文件以确定的实用程序:
- 哪些文件代表 COM 服务器,它们包含哪些 CLSID
- 哪些文件代表 COM 客户端,它们引用了哪些 CLSID
希望有了这些信息,我可以使用 InstallShield 的 Reg-Free COM 向导生成必要的清单文件。
有没有什么可以帮助解决这个问题的?如果还没有这样的实用程序,我将尝试通过以下方式编写自己的实用程序:
- 查看 COM 服务器中的一些已知 CLSID 并尝试识别它们周围的一些签名字节,这可能有助于我确定如何识别 COM 服务器中的 CLSID。
- 编写代码在我们所有的二进制文件中查找此签名并获取所有 CLSID
- 在我们所有的二进制文件中搜索已知的 CLSID,并假设那些来自服务器外部的代表客户端。
如果事实证明我们使用的是 ProgId 而不仅仅是 CLSID,我可能不得不调整我的策略,但我希望这将涵盖我们的大部分 COM 引用。
如果已经有任何可能对此有帮助的实用程序,我愿意使用它而不是自己编写。因此,我正在寻找有关自己编写此内容的任何提示 或 有关查找已完成此操作的提示。
在 How to read TLB (type libraries) of unmanaged code from C#?, which led me to the C# source code of a type library reading class at http://clrinterop.codeplex.com/releases/view/17579 的已删除答案的帮助下,我能够读取类型库以获取 CLSID 值(如果尝试加载类型库失败则跳过文件 - 只是尝试加载类型 a来自每个可执行文件的库)。然后我对所有可执行文件进行了简单搜索以查找那些 CLSID 值。我不确定结果有多完整,但至少它们看起来有点有效。
GetDirCLSIDRefs
函数是顶级函数,它 returns 一个可执行文件名列表,对于每个文件名,还有一个关联文件列表,其类型库包含文件引用的 CLSID。
Private Function GetDirCLSIDRefs(Directory As String, CLSIDs As KeyValuePair(Of Guid, String)()) As KeyValuePair(Of String, String())()
Dim result As New List(Of KeyValuePair(Of String, String()))
Dim entries = New System.IO.DirectoryInfo(Directory).GetFileSystemInfos()
For Each entry In entries
If TypeOf entry Is System.IO.FileInfo AndAlso _
(entry.Name.EndsWith(".exe") OrElse _
entry.Name.EndsWith(".dll") OrElse _
entry.Name.EndsWith(".ocx")) Then
Dim refs = ScanForCLSIDs(entry.FullName, CLSIDs)
If refs IsNot Nothing AndAlso refs.Length > 0 Then result.Add(New KeyValuePair(Of String, String())(entry.FullName, refs))
ElseIf TypeOf entry Is System.IO.DirectoryInfo Then
result.AddRange(GetDirCLSIDRefs(entry.FullName, CLSIDs))
End If
Next
Return result.ToArray()
End Function
Private Function GetDirCLSIDs(Directory As String) As KeyValuePair(Of Guid, String)()
Dim result As New List(Of KeyValuePair(Of Guid, String))
Dim entries = New System.IO.DirectoryInfo(Directory).GetFileSystemInfos()
For Each entry In entries
If TypeOf entry Is System.IO.FileInfo AndAlso _
(entry.Name.EndsWith(".exe") OrElse _
entry.Name.EndsWith(".dll") OrElse _
entry.Name.EndsWith(".ocx")) Then
For Each clsid In GetCLSIDs(entry.FullName)
result.Add(New KeyValuePair(Of Guid, String)(clsid, entry.FullName))
Next
ElseIf TypeOf entry Is System.IO.DirectoryInfo Then
result.AddRange(GetDirCLSIDs(entry.FullName))
End If
Next
Return result.ToArray()
End Function
Private Function GetCLSIDs(FileName As String) As Guid()
Dim tlb = TypeLibTypes.Interop.TypeLib.Load(FileName)
If tlb.GetTypeLib() Is Nothing Then Return {}
Dim result As New List(Of Guid)
Try
For typeIndex As Integer = 0 To tlb.GetTypeInfoCount() - 1
Using attr = tlb.GetTypeInfo(typeIndex).GetTypeAttr()
If (attr.wTypeFlags And TypeLibTypes.Interop.TYPEFLAGS.TYPEFLAG_FHIDDEN) = 0 _
AndAlso attr.typekind = TypeLibTypes.Interop.TYPEKIND.TKIND_COCLASS Then
result.Add(attr.Guid)
End If
End Using
Next
Catch ex As System.Runtime.InteropServices.COMException When ex.ErrorCode = &H80029C4A
Return {}
End Try
Return result.ToArray()
End Function
Private Function ScanForCLSIDs(FileName As String, CLSIDFiles As KeyValuePair(Of Guid, String)()) As String()
Dim content As Byte()
Using file As New System.IO.FileStream(FileName, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.ReadWrite)
content = DirectCast(Array.CreateInstance(GetType(Byte), CInt(file.Length)), Byte())
file.Read(content, 0, CInt(file.Length))
End Using
Dim result As New List(Of String)
For Each CLSIDFile In CLSIDFiles
If FindBytes(content, CLSIDFile.Key.ToByteArray()) >= 0 Then
If Not result.Contains(CLSIDFile.Value) Then result.Add(CLSIDFile.Value)
End If
Next
Return result.ToArray()
End Function
Private Function FindBytes(data As Byte(), pattern As Byte(), Optional start As Integer = 0) As Int32
If pattern.Length = 0 Then Return -1
If data.Length = 0 Then Return 0
Dim search = ProcessSearchPattern(pattern)
Dim i As Integer = start + pattern.Length - 1
Do While i < data.Length
Dim j As Integer = pattern.Length - 1
Do While j >= 0 AndAlso data(i) = pattern(j)
i -= 1
j -= 1
Loop
If j < 0 Then Return i + 1
i += Math.Max(search.Delta1(data(i)), search.Delta2(j))
Loop
Return -1
End Function
Private Class SearchPattern
Public Delta1 As Integer()
Public Delta2 As Integer()
End Class
Private Class PatternBytes
Private bytes As Byte()
Public Sub New(value As Byte())
bytes = value
End Sub
Public Overrides Function GetHashCode() As Integer
Dim i As Integer = 0
GetHashCode = 0
Do While i <= bytes.Length - 4
GetHashCode = GetHashCode Xor BitConverter.ToInt32(bytes, i)
i += 4
Loop
If i <= bytes.Length - 2 Then
GetHashCode = GetHashCode Xor BitConverter.ToInt16(bytes, i)
i += 2
End If
If i < bytes.Length Then
GetHashCode = GetHashCode Xor bytes(i)
End If
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim compare As Byte()
If TypeOf obj Is PatternBytes Then
compare = DirectCast(obj, PatternBytes).bytes
ElseIf TypeOf obj Is Byte() Then
compare = DirectCast(obj, Byte())
Else
Return False
End If
If compare.Length <> bytes.Length Then Return False
For i As Integer = 0 To bytes.Length - 1
If bytes(i) <> compare(i) Then Return False
Next
Return True
End Function
End Class
Private Function ProcessSearchPattern(pattern As Byte()) As SearchPattern
Static cache As New Dictionary(Of PatternBytes, SearchPattern)
ProcessSearchPattern = Nothing
Dim pb As New PatternBytes(pattern)
If cache.TryGetValue(pb, ProcessSearchPattern) Then Exit Function
ProcessSearchPattern = New SearchPattern()
Dim patLen = pattern.Length
ProcessSearchPattern.Delta1 = DirectCast(Array.CreateInstance(GetType(Integer), Byte.MaxValue + 1), Integer())
ProcessSearchPattern.Delta2 = DirectCast(Array.CreateInstance(GetType(Integer), patLen), Integer())
Dim c As Integer ' Cannot run a For loop to Byte.MaxValue in a Byte!
Dim i As Integer
For c = 0 To Byte.MaxValue
ProcessSearchPattern.Delta1(c) = patLen
Next
For i = 0 To patLen - 2
ProcessSearchPattern.Delta1(pattern(i)) = patLen - 1 - i
Next
Dim m As Integer
Dim lastPrefixIndex = patLen - 1
Dim isPrefix As Boolean = False
For i = patLen - 1 To 0 Step -1
isPrefix = True
If patLen - i - 2 > 0 Then
For m = 0 To patLen - i - 2
If pattern(m) <> pattern(m + i + 1) Then
isPrefix = False
Exit For
End If
Next
End If
If isPrefix Then lastPrefixIndex = i + 1
ProcessSearchPattern.Delta2(i) = lastPrefixIndex + (patLen - 1 - i)
Next
For i = 0 To patLen - 2
For m = 0 To i - 1
If pattern(i - m) = pattern(patLen - 1 - m) Then
Exit For
End If
If pattern(i - m) <> pattern(patLen - 1 - m) Then
ProcessSearchPattern.Delta2(patLen - 1 - m) = patLen - 1 - i + m
End If
Next
Next
cache.Add(pb, ProcessSearchPattern)
End Function
我正在 InstallShield 2015 中将一个大型安装项目重新实施为 MSI 安装程序(它以前是来自更旧版本 InstallShield 的 InstallScript 安装程序)。我想做的许多改进之一是允许通过使用清单文件和遵循免注册 COM 激活模式以免注册方式解析所有 COM 引用。我认为这在所有情况下都是不可能的,因为我们有一些 MMC 管理单元,据我所知,它不能使用 reg-free COM,但我想尽可能接近。
挑战在于找出我需要创建的所有清单。我们提供数以千计的文件。我正在寻找可以帮助扫描文件以确定的实用程序:
- 哪些文件代表 COM 服务器,它们包含哪些 CLSID
- 哪些文件代表 COM 客户端,它们引用了哪些 CLSID
希望有了这些信息,我可以使用 InstallShield 的 Reg-Free COM 向导生成必要的清单文件。
有没有什么可以帮助解决这个问题的?如果还没有这样的实用程序,我将尝试通过以下方式编写自己的实用程序:
- 查看 COM 服务器中的一些已知 CLSID 并尝试识别它们周围的一些签名字节,这可能有助于我确定如何识别 COM 服务器中的 CLSID。
- 编写代码在我们所有的二进制文件中查找此签名并获取所有 CLSID
- 在我们所有的二进制文件中搜索已知的 CLSID,并假设那些来自服务器外部的代表客户端。
如果事实证明我们使用的是 ProgId 而不仅仅是 CLSID,我可能不得不调整我的策略,但我希望这将涵盖我们的大部分 COM 引用。
如果已经有任何可能对此有帮助的实用程序,我愿意使用它而不是自己编写。因此,我正在寻找有关自己编写此内容的任何提示 或 有关查找已完成此操作的提示。
在 How to read TLB (type libraries) of unmanaged code from C#?, which led me to the C# source code of a type library reading class at http://clrinterop.codeplex.com/releases/view/17579 的已删除答案的帮助下,我能够读取类型库以获取 CLSID 值(如果尝试加载类型库失败则跳过文件 - 只是尝试加载类型 a来自每个可执行文件的库)。然后我对所有可执行文件进行了简单搜索以查找那些 CLSID 值。我不确定结果有多完整,但至少它们看起来有点有效。
GetDirCLSIDRefs
函数是顶级函数,它 returns 一个可执行文件名列表,对于每个文件名,还有一个关联文件列表,其类型库包含文件引用的 CLSID。
Private Function GetDirCLSIDRefs(Directory As String, CLSIDs As KeyValuePair(Of Guid, String)()) As KeyValuePair(Of String, String())()
Dim result As New List(Of KeyValuePair(Of String, String()))
Dim entries = New System.IO.DirectoryInfo(Directory).GetFileSystemInfos()
For Each entry In entries
If TypeOf entry Is System.IO.FileInfo AndAlso _
(entry.Name.EndsWith(".exe") OrElse _
entry.Name.EndsWith(".dll") OrElse _
entry.Name.EndsWith(".ocx")) Then
Dim refs = ScanForCLSIDs(entry.FullName, CLSIDs)
If refs IsNot Nothing AndAlso refs.Length > 0 Then result.Add(New KeyValuePair(Of String, String())(entry.FullName, refs))
ElseIf TypeOf entry Is System.IO.DirectoryInfo Then
result.AddRange(GetDirCLSIDRefs(entry.FullName, CLSIDs))
End If
Next
Return result.ToArray()
End Function
Private Function GetDirCLSIDs(Directory As String) As KeyValuePair(Of Guid, String)()
Dim result As New List(Of KeyValuePair(Of Guid, String))
Dim entries = New System.IO.DirectoryInfo(Directory).GetFileSystemInfos()
For Each entry In entries
If TypeOf entry Is System.IO.FileInfo AndAlso _
(entry.Name.EndsWith(".exe") OrElse _
entry.Name.EndsWith(".dll") OrElse _
entry.Name.EndsWith(".ocx")) Then
For Each clsid In GetCLSIDs(entry.FullName)
result.Add(New KeyValuePair(Of Guid, String)(clsid, entry.FullName))
Next
ElseIf TypeOf entry Is System.IO.DirectoryInfo Then
result.AddRange(GetDirCLSIDs(entry.FullName))
End If
Next
Return result.ToArray()
End Function
Private Function GetCLSIDs(FileName As String) As Guid()
Dim tlb = TypeLibTypes.Interop.TypeLib.Load(FileName)
If tlb.GetTypeLib() Is Nothing Then Return {}
Dim result As New List(Of Guid)
Try
For typeIndex As Integer = 0 To tlb.GetTypeInfoCount() - 1
Using attr = tlb.GetTypeInfo(typeIndex).GetTypeAttr()
If (attr.wTypeFlags And TypeLibTypes.Interop.TYPEFLAGS.TYPEFLAG_FHIDDEN) = 0 _
AndAlso attr.typekind = TypeLibTypes.Interop.TYPEKIND.TKIND_COCLASS Then
result.Add(attr.Guid)
End If
End Using
Next
Catch ex As System.Runtime.InteropServices.COMException When ex.ErrorCode = &H80029C4A
Return {}
End Try
Return result.ToArray()
End Function
Private Function ScanForCLSIDs(FileName As String, CLSIDFiles As KeyValuePair(Of Guid, String)()) As String()
Dim content As Byte()
Using file As New System.IO.FileStream(FileName, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.ReadWrite)
content = DirectCast(Array.CreateInstance(GetType(Byte), CInt(file.Length)), Byte())
file.Read(content, 0, CInt(file.Length))
End Using
Dim result As New List(Of String)
For Each CLSIDFile In CLSIDFiles
If FindBytes(content, CLSIDFile.Key.ToByteArray()) >= 0 Then
If Not result.Contains(CLSIDFile.Value) Then result.Add(CLSIDFile.Value)
End If
Next
Return result.ToArray()
End Function
Private Function FindBytes(data As Byte(), pattern As Byte(), Optional start As Integer = 0) As Int32
If pattern.Length = 0 Then Return -1
If data.Length = 0 Then Return 0
Dim search = ProcessSearchPattern(pattern)
Dim i As Integer = start + pattern.Length - 1
Do While i < data.Length
Dim j As Integer = pattern.Length - 1
Do While j >= 0 AndAlso data(i) = pattern(j)
i -= 1
j -= 1
Loop
If j < 0 Then Return i + 1
i += Math.Max(search.Delta1(data(i)), search.Delta2(j))
Loop
Return -1
End Function
Private Class SearchPattern
Public Delta1 As Integer()
Public Delta2 As Integer()
End Class
Private Class PatternBytes
Private bytes As Byte()
Public Sub New(value As Byte())
bytes = value
End Sub
Public Overrides Function GetHashCode() As Integer
Dim i As Integer = 0
GetHashCode = 0
Do While i <= bytes.Length - 4
GetHashCode = GetHashCode Xor BitConverter.ToInt32(bytes, i)
i += 4
Loop
If i <= bytes.Length - 2 Then
GetHashCode = GetHashCode Xor BitConverter.ToInt16(bytes, i)
i += 2
End If
If i < bytes.Length Then
GetHashCode = GetHashCode Xor bytes(i)
End If
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim compare As Byte()
If TypeOf obj Is PatternBytes Then
compare = DirectCast(obj, PatternBytes).bytes
ElseIf TypeOf obj Is Byte() Then
compare = DirectCast(obj, Byte())
Else
Return False
End If
If compare.Length <> bytes.Length Then Return False
For i As Integer = 0 To bytes.Length - 1
If bytes(i) <> compare(i) Then Return False
Next
Return True
End Function
End Class
Private Function ProcessSearchPattern(pattern As Byte()) As SearchPattern
Static cache As New Dictionary(Of PatternBytes, SearchPattern)
ProcessSearchPattern = Nothing
Dim pb As New PatternBytes(pattern)
If cache.TryGetValue(pb, ProcessSearchPattern) Then Exit Function
ProcessSearchPattern = New SearchPattern()
Dim patLen = pattern.Length
ProcessSearchPattern.Delta1 = DirectCast(Array.CreateInstance(GetType(Integer), Byte.MaxValue + 1), Integer())
ProcessSearchPattern.Delta2 = DirectCast(Array.CreateInstance(GetType(Integer), patLen), Integer())
Dim c As Integer ' Cannot run a For loop to Byte.MaxValue in a Byte!
Dim i As Integer
For c = 0 To Byte.MaxValue
ProcessSearchPattern.Delta1(c) = patLen
Next
For i = 0 To patLen - 2
ProcessSearchPattern.Delta1(pattern(i)) = patLen - 1 - i
Next
Dim m As Integer
Dim lastPrefixIndex = patLen - 1
Dim isPrefix As Boolean = False
For i = patLen - 1 To 0 Step -1
isPrefix = True
If patLen - i - 2 > 0 Then
For m = 0 To patLen - i - 2
If pattern(m) <> pattern(m + i + 1) Then
isPrefix = False
Exit For
End If
Next
End If
If isPrefix Then lastPrefixIndex = i + 1
ProcessSearchPattern.Delta2(i) = lastPrefixIndex + (patLen - 1 - i)
Next
For i = 0 To patLen - 2
For m = 0 To i - 1
If pattern(i - m) = pattern(patLen - 1 - m) Then
Exit For
End If
If pattern(i - m) <> pattern(patLen - 1 - m) Then
ProcessSearchPattern.Delta2(patLen - 1 - m) = patLen - 1 - i + m
End If
Next
Next
cache.Add(pb, ProcessSearchPattern)
End Function