简体   繁体   English

如何在不生成Shell到VB6中的regsvr32的情况下注册/注销DLL / OCX

[英]How to register/unregister DLL/OCXs without spawning a shell to regsvr32 in VB6

I need to create a VB6 program to register/unregister all dll/ocx files in a given folder (which could have up to 200 such files). 我需要创建一个VB6程序来注册/注销给定文件夹中的所有dll / ocx文件(该文件夹最多可以包含200个此类文件)。

This isn't a one-off task, and need to be run on hundreds of machines at different customer sites, hence the need for a tool to do it efficiently. 这不是一项一次性的任务,需要在不同客户位置的数百台计算机上运行,​​因此需要一种有效执行此任务的工具。 Shelling out to regsvr32.exe is out of the question here, so no need to go into any discussion on that approach. 在这里无法使用regsvr32.exe,因此无需对该方法进行任何讨论。

There is an example at http://support.microsoft.com/kb/173091 , but this requires hardcoding the name of the dll/ocx in a Declare statement, which makes it pretty much useless in our scenario. http://support.microsoft.com/kb/173091上有一个示例,但这需要在Declare语句中对dll / ocx的名称进行硬编码,这在我们的方案中几乎没有用。

I know the logical process goes along these lines: 我知道逻辑过程遵循以下原则:

  1. scan the directory and enumerate all dll/ocx files in it 扫描目录并枚举其中的所有dll / ocx文件
  2. for each such file: 对于每个这样的文件:

    2.1 call LoadLibrary to load it (exit if the call fails) 2.1调用LoadLibrary加载它(如果调用失败退出)

    2.2 call GetProcAddress to locate the function "DllRegisterServer" or "DllUnRegisterServer" in the current file (depending on the requested operation: register or unregister) 2.2调用GetProcAddress在当前文件中找到函数“ DllRegisterServer”或“ DllUnRegisterServer”(取决于请求的操作:注册还是注销)

    2.3 if the function is not found then skip the file; 2.3如果找不到该功能,则跳过文件; else: 其他:

    2.4 call the function to register/unregister the file 2.4调用函数注册/注销文件

    2.5 call FreeLibrary to unload the file 2.5调用FreeLibrary卸载文件

The problem is in step 2.4. 问题在步骤2.4中。 In C/C++ based Windows API code, i can simply call the function using the function pointer returned from GetProcAddress(), but in VB6, this seems to be a lot more complicated. 在基于C / C ++的Windows API代码中,我可以简单地使用从GetProcAddress()返回的函数指针来调用函数,但是在VB6中,这似乎要复杂得多。

I've seen two suggestions so far: 1) use CallWindowProc() 2) use CreateThread() 到目前为止,我已经看到了两个建议:1)使用CallWindowProc()2)使用CreateThread()

For 1) see: http://www.pcreview.co.uk/forums/using-callwindowproc-call-non-wndproc-functions-t2912253.html Someone went so far as to create a generic wrapper that can be used to call any API function pointer by pushing arguments onto the stack, see here http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=32873&lngWId=1 对于1),请参见: http : //www.pcreview.co.uk/forums/using-callwindowproc-call-non-wndproc-functions-t2912253.html有人甚至创建了一个可用于调用的通用包装器通过将参数推入堆栈来获取任何API函数指针,请参见此处http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=32873&lngWId=1

For 2), i lost the website that posted the original code, but it goes something like this: 对于2),我丢失了发布原始代码的网站,但是它像这样:

hMod      = LoadLibrary(sFilePath)
lProcAddr = GetProcAddress(hMod, "DllRegisterServer")
hThread   = CreateThread(ByVal 0&, 0&, ByVal lProcAddr, ByVal 0&, 0&, tid)
lWaitRes  = WaitForSingleObject(hThread, 30000)  'give it 30 seconds to finish

I know these Windows APIs quite well, but am not an expert on the inner workings of VB6 and why it can't just call a simple function pointer. 我非常了解这些Windows API,但不是VB6的内部运作专家以及为什么它不能仅调用简单的函数指针。 My questions here are: 我的问题是:

  1. Which of the two approaches is better (ie more efficient, reliable) I suspect 1) won't work if the VB code has just a Sub Main(), ie it runs in cmdline code with no GUI. 我怀疑这两种方法中的哪一种更好(即更有效,更可靠),我怀疑1)如果VB代码只有Sub Main(),即不能在没有GUI的cmdline代码中运行,则将无法工作。 But Creating a new thread just to call a function seems a bit of an overkill to me. 但是对于我来说,创建一个仅用于调用函数的新线程似乎有些过大。
  2. Is there a simpler way? 有没有更简单的方法? (and no, shelling out to regsvr32.exe is not acceptable here) (不,在这里不接受炮击到regsvr32.exe)

Thanks. 谢谢。

There is far more to properly installing ActiveX libraries than merely calling their self-registration entrypoints. 正确安装ActiveX库不仅仅需要调用它们的自注册入口点。 Create a proper installer instead. 而是创建一个适当的安装程序。 If any of these are shared libraries you risk doing great damage to other applications on the target systems. 如果其中任何一个是共享库,则有可能对目标系统上的其他应用程序造成很大的损害。

Alternatively, create isolated assemblies for these libraries so no registration is needed at all. 或者,为这些库创建隔离的程序集,因此根本不需要注册。

The path you propose sounds like a recipe for DLL Hell. 您建议的路径听起来像是DLL Hell的秘方。 This sort of hacking by vocational coders is just one reason why VB (and C++) has such a poor reputation. 职业编码人员的这种黑客攻击只是VB(和C ++)信誉如此差的一个原因。

Seriously, hire an installation professional. 认真聘请安装专业人员。

You can take this for what it's worth, and believe me I understand about decisions made by someone that thinks everything can be done in 2 clicks. 您可以将其视为有价值的东西,并且相信我,我理解某人做出的决定,认为所有事情都可以在2次单击中完成。 I wrote a utility for myself to do this years ago. 几年前,我为自己编写了一个实用程序。 It also uses a checkbox to optionally suppress any result messages. 它还使用复选框有选择地禁止显示任何结果消息。 This code uses a textbox for a specific file name or a wild card, creates an array of file names (with complete paths) and attempts to register all the files that match. 此代码使用文本框输入特定的文件名或通配符,创建文件名数组(具有完整路径),并尝试注册所有匹配的文件。 You don't see any other programs being launched. 您没有看到任何其他程序正在启动。

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Dim blnCancel As Boolean

Const WM_CLOSE = &H10
Const WM_QUIT = &H12
Const WM_SYSCOMMAND = &H112
Const WM_COMMAND = &H111
Const SC_CLOSE = &HF060


Private Sub btnRegister_Click()
    Dim aryDLL() As String
    Dim i As Integer
    Dim fName As String
    Dim fPath As String
    Dim Rtn As Double
    Dim hwnd As Long
    Dim strCommand As String

    On Error GoTo errbtnRegister

    Command2.Enabled = False
    Command1.Caption = "Cancel"

    Me.MousePointer = vbHourglass

    If CurDir$ <> ExtractPath(Text1.Text) Then
        If Left$(CurDir$, 1) <> Left$(ExtractPath(Text1.Text), 1) Then
            ChDrive (Left$(Text1.Text, 2))
        End If
        ChDir (ExtractPath(Text1.Text))
    End If

    fName = Dir$(ExtractFile(Text1.Text))

    If fName = "" Then
        MsgBox "There were no files found matching " & Text1.Text & ".", vbOKOnly + vbInformation, "Nothing To Do"
        CleanUp
        Exit Sub
    Else
        While fName > ""
            ReDim Preserve aryDLL(i)
            aryDLL(i) = fName
            fName = Dir$()
            i = i + 1

            If blnCancel = True Then
                CleanUp
                Exit Sub
            End If

        Wend

        i = 0   'reinitialize i
        Label1.Visible = True

        For i = LBound(aryDLL) To UBound(aryDLL)

            If blnCancel = True Then
                CleanUp
                Exit Sub
            End If

            Label1.Caption = "Current File   " & CStr(i + 1) & " of " & CStr(UBound(aryDLL) + 1)
            lblCurrFile.Caption = aryDLL(i)
            lblCurrFile.Refresh
            DoEvents
            If InStr(CurDir$, " ") Then
               strCommand = """" & CurDir$ & "\" & aryDLL(i) & """"
            Else
               strCommand = CurDir$ & "\" & aryDLL(i)
            End If
            If chkSilent.Value = vbChecked Then
                Rtn = Shell("regsvr32.exe /s " & strCommand, vbNormalNoFocus)
            Else
                Rtn = Shell("regsvr32.exe " & strCommand, vbNormalNoFocus)
            End If

            If UBound(aryDLL) > 0 Then
                If CInt(i / UBound(aryDLL)) * 100 <= 100 Then
                    ProgressBar1.Value = CInt(i / UBound(aryDLL) * 100)
                Else
                    ProgressBar1.Value = 100
                End If
            Else
                ProgressBar1.Value = 100
            End If

            Delay 5
            hwnd = FindWindow(vbNullString, "RegSvr32")
            'close the regsvr32 message window
            Rtn = SendMessage(hwnd, WM_COMMAND, SC_CLOSE, vbNull)

        Next i

    End If

    CleanUp
    Exit Sub

errbtnRegister:
    If Err.Number = 9 Then  'no files found
        MsgBox "There were no files found matching " & Text1.Text & ".", vbOKOnly + vbInformation, "Nothing To Do"
    Else
        MsgBox "There was an error registering " & Text1.Text & " files." & vbLf & vbLf _
            & "Error = " & CStr(Err.Number) & ", " & Err.Description, vbOKOnly + vbInformation, "Program Error"
    End If

    Exit Sub

End Sub

You can use a delegator light-weight object if you have to implement it all in VB6. 如果必须在VB6中全部实现它,则可以使用委托者轻量级对象。

First you'll need a typelib with a custom interface with long retvals by its methods because methods of a VB6 "interface" class always return HRESULT and this will interfere with the delegator retvals (negative retvals will raise errors). 首先,您将需要一个具有自定义接口且具有long retval的自定义接口的typelib,因为VB6“接口”类的方法始终返回HRESULT ,这会干扰委托者的retval(负retval会引发错误)。

[
  uuid(a4d82779-ed39-437c-9f42-89048603a82b),
  version(1.0),
  helpstring("Delegator Typelib 1.0")
]
library DelegatorLib
{
    importlib("stdole2.tlb");

    [
      odl,
      uuid(fdb250f4-4175-444f-8a53-72ecfcaf8fd0),
      version(1.0),
    ]    
    interface IDelegator : IUnknown {
        long Call0([in] long pfn);
        long Call1([in] long pfn, [in] long A1);
        long Call2([in] long pfn, [in] long A1, [in] long A2);
        long Call3([in] long pfn, [in] long A1, [in] long A2, [in] long A3);
        long Call4([in] long pfn, [in] long A1, [in] long A2, [in] long A3, [in] long A4);
        long Call5([in] long pfn, [in] long A1, [in] long A2, [in] long A3, [in] long A4, [in] long A5);
        long Call6([in] long pfn, [in] long A1, [in] long A2, [in] long A3, [in] long A4, [in] long A5, [in] long A6);
        long Call7([in] long pfn, [in] long A1, [in] long A2, [in] long A3, [in] long A4, [in] long A5, [in] long A6, [in] long A7);
    };
}

Then in a standard module you can implement the light-weight object -- a very minimal implementation would be enough. 然后,在标准模块中,您可以实现轻量级对象-一个非常小的实现就足够了。

Option Explicit

'--- for VirtualQuery'
Private Const PAGE_EXECUTE_READWRITE            As Long = &H40

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long

Private m_aThunk(0 To 1)        As Long
Private m_aVtbl(0 To 9)         As Long

Public Type DelegatorData
    pVTable             As Long
End Type

Public Function InitDelegator(This As DelegatorData) As IDelegator
    Dim dwDummy         As Long
    Dim lIdx            As Long

    If m_aVtbl(0) = 0 Then
        m_aThunk(0) = &H51585859
        m_aThunk(1) = &H9090E0FF
        Call VirtualProtect(m_aThunk(0), 8, PAGE_EXECUTE_READWRITE, dwDummy)
        m_aVtbl(0) = pvAddr(AddressOf pvQueryInterface)
        m_aVtbl(1) = pvAddr(AddressOf pvAddRefRelease)
        m_aVtbl(2) = pvAddr(AddressOf pvAddRefRelease)
        For lIdx = 3 To 9
            m_aVtbl(lIdx) = VarPtr(m_aThunk(0))
        Next
    End If
    This.pVTable = VarPtr(m_aVtbl(0))
    Call CopyMemory(InitDelegator, VarPtr(This), 4)
End Function

Private Function pvAddr(ByVal lPtr As Long) As Long
    pvAddr = lPtr
End Function

Private Function pvQueryInterface(This As DelegatorData, ByVal riid As Long, pvObj As Long) As Long
    pvObj = VarPtr(This)
End Function

Private Function pvAddRefRelease(This As DelegatorData) As Long
    '--- do nothing
End Function

The thunk code is really the funny part, this is the assembly 笨拙的代码确实是有趣的部分,这是程序集

00401030 59                   pop         ecx
00401031 58                   pop         eax
00401032 58                   pop         eax
00401033 51                   push        ecx
00401034 FF E0                jmp         eax

So basicly it preserves return address in ecx , gets rid of the first argument ( this ) then pops and jumps to the second one ( pfn ) and leaves the rest of the arguments intact. 因此,从根本上说,它将返回地址保存在ecx ,摆脱了第一个参数( this ),然后弹出并跳转到第二个参数( pfn ),而其余参数则保持不变。 Then delegated function's epilog takes care of the stack (delegated function has to be in stdcall calling convention). 然后,委托函数的Epilog负责堆栈(委托函数必须在stdcall调用约定中)。

You can initialize a (singleton) delegator like this 您可以像这样初始化一个(单个)委托人

Private m_uDelegator        As DelegatorData
Private m_pDelegator        As IDelegator

Set m_pDelegator = InitDelegator(m_uDelegator)

And later use m_pDelegator var directly like this 然后像这样直接使用m_pDelegator var

ret = m_pDelegator.Call0(AddressOf Test)

No clean-up necessary. 无需清理。

The rest of the CallN functions use the same thunk but you can cut them out as you don't need these for your purposes. 其余的CallN函数使用相同的thunk,但是您可以切掉它们,因为您不需要这些目的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM