2015-01-27 6 views
3

В VB6 (из-за требований клиента) мне нужно иметь возможность выполнять несколько экземпляров ActiveX EXE, которые я написал для загрузки файлов на несколько устройств через RS232.VB6 Несколько экземпляров объекта ActiveX

Я разработал тестовое приложение, которое, я думаю, отражает то, что мне нужно делать. Во-первых, ActiveX EXE, который имитирует процесс загрузки, называемый TClass. Этот ActiveX EXE вызывает события сообщить свой текущий прогресс в следующим образом:

TClass.exe (ActiveX EXE, Instancing = SingleUse, Threading Model = Thread на объект)

Option Explicit 

Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) 

Public Event Progress(Value As Long) 

Public SeedVal As Long 

Public Sub MultByTwo() 
    Dim i As Integer 
    Dim lVal As Long 

    lVal = SeedVal 

    For i = 0 To 10 
     Sleep (2000) 
     lVal = lVal * 2 
     RaiseEvent Progress(lVal) 
    Next i 

    Exit Sub 
End Sub 

Следующая класс обертку для создания экземпляра TClass и обрабатывать обратного вызова события (Прогресс), назовем его WClass (AxtiveX DLL, Instancing = MultiUse, квартира Каскадный):

Option Explicit 

Public WSeedVal As Long 
Public WResultVal As Long 

Private WithEvents MYF87 As TClass.TargetClass 

Private Sub Class_Initialize() 
    ' Set MYF87 = CreateObject("TClass.TargetClass") 
    Set MYF87 = New TClass.TargetClass 
End Sub 

Public Function Go() As Integer 
    MYF87.SeedVal = WSeedVal 
    MYF87.MultByTwo 
End Function 

Public Sub MYF87_Progress(Value As Long) 
    WResultVal = Value 
    DoEvents 
End Sub 

Public Function CloseUpShop() As Integer 
    Set MYF87 = Nothing 
End Function 

и, наконец, пользовательский интерфейс для создания экземпляра WClass. Это простая форма приложение:

Option Explicit 

Private lc1 As WClass.WrapperClass 
Private lc2 As WClass.WrapperClass 
Private lc3 As WClass.WrapperClass 
Private lc4 As WClass.WrapperClass 
Private lc5 As WClass.WrapperClass 

Private Sub cmd1_Click() 
    Set lc1 = CreateObject("WClass.WrapperClass") 
    lc1.WSeedVal = CInt(txt1.Text) 
    lc1.Go 
End Sub 

Private Sub cmd2_Click() 
    Set lc2 = CreateObject("WClass.WrapperClass") 
    lc2.WSeedVal = CInt(txt2.Text) 
    lc2.Go 
End Sub 

Private Sub cmd3_Click() 
    Set lc3 = CreateObject("WClass.WrapperClass") 
    lc3.WSeedVal = CInt(txt3.Text) 
    lc3.Go 
End Sub 

Private Sub cmd4_Click() 
    Set lc4 = CreateObject("WClass.WrapperClass") 
    lc4.WSeedVal = CInt(txt4.Text) 
    lc4.Go 
End Sub 

Private Sub cmd5_Click() 
    Set lc5 = CreateObject("WClass.WrapperClass") 
    lc5.WSeedVal = CInt(txt5.Text) 
    lc5.Go 
End Sub 

Private Sub Form_Load() 
    Timer1.Interval = 2000 
    Timer1.Enabled = True 
End Sub 

Private Sub Form_Unload(Cancel As Integer) 
    If Not lc1 Is Nothing Then 
     lc1.CloseUpShop 
     Set lc1 = Nothing 
    End If 
    If Not lc2 Is Nothing Then 
     lc2.CloseUpShop 
     Set lc2 = Nothing 
    End If 
    If Not lc3 Is Nothing Then 
     lc3.CloseUpShop 
     Set lc3 = Nothing 
    End If 
    If Not lc4 Is Nothing Then 
     lc4.CloseUpShop 
     Set lc4 = Nothing 
    End If 
    If Not lc5 Is Nothing Then 
     lc5.CloseUpShop 
     Set lc5 = Nothing 
    End If 
End Sub 

Private Sub Timer1_Timer() 

    If Timer1.Enabled Then 
     Timer1.Enabled = False 

     If Not lc1 Is Nothing Then 
      txtRes1.Text = CStr(lc1.WResultVal) 
      txtRes1.Refresh 
     End If 

     If Not lc2 Is Nothing Then 
      txtRes2.Text = CStr(lc2.WResultVal) 
      txtRes2.Refresh 
     End If 

     If Not lc3 Is Nothing Then 
      txtRes3.Text = CStr(lc3.WResultVal) 
      txtRes3.Refresh 
     End If 

     If Not lc4 Is Nothing Then 
      txtRes4.Text = CStr(lc4.WResultVal) 
      txtRes4.Refresh 
     End If 

     If Not lc5 Is Nothing Then 
      txtRes5.Text = CStr(lc5.WResultVal) 
      txtRes5.Refresh 
     End If 

     Timer1.Interval = 2000 
     Timer1.Enabled = True 

    End If 

    DoEvents 

End Sub 

txt1, txt2, txt3, txt4 и txt5 являются элементами текста, которые обеспечивают начальное значение, что заканчивает получение передается TClass как свойство. txtRes1, txtRes2, txtRes3, txtRes4 и txtRes5 являются текстовыми элементами для хранения результатов TClass.MultByTwo, как сообщается через вызов RaiseEvent Progress(). cmd1, cmd2, cmd3, cmd4 и cmd5 привязаны к соответствующим вышеперечисленным функциям _Click и создают экземпляр WClass.WrapperClass и все происходит. У формы также есть объект Timer, называемый Timer1, который срабатывает каждые 2 секунды. Единственная цель этого - обновить пользовательский интерфейс из общедоступных свойств в WClass.

Я создал TClass для TClass.exe и WClass для WClass.dll и ссылался на WClass.dll из приложения пользовательского интерфейса. Когда я запускаю форму и нажимаю cmd1, первое, что я замечаю, это то, что Timer1_Timer больше не срабатывает, поэтому мой интерфейс никогда не обновляется. Во-вторых, если я нажму на cmd2, он загорится, но, похоже, заблокирует выполнение первого экземпляра.

Я провел пару дней, читая сообщения и инструкции по MSDN ... не повезло ... любая помощь была бы принята с благодарностью!

Спасибо!

Обновление: Я изменил класс оболочки WClass.dll, чтобы реализовать рекомендации по использованию функций обратного вызова. Смотрите ниже:

V2: WClass.dll (ActiveX DLL, потоковое, Instancing = MULTIUSE)

Option Explicit 

Public WSeedVal As Long 
Public WResultVal As Long 

Public Event WProgress(WResultVal As Long) 

Private WithEvents MyTimer As TimerLib.TimerEx 
Private WithEvents MYF87 As TClass.TargetClass 
Private gInterval As IntervalData 

Private Sub Class_Initialize() 
    Set MyTimer = CreateObject("TimerLib.TimerEx") 
    ' Set MyTimer = New TimerLib.TimerEx 

    Set MYF87 = CreateObject("TClass.TargetClass") 
    ' Set MYF87 = New TClass.TargetClass 
End Sub 

Public Function Go() As Integer 
    gInterval.Second = 1 
    MyTimer.IntervalInfo = gInterval 
    MyTimer.Enabled = True 
End Function 

Private Sub MyTimer_OnTimer() 
    MyTimer.Enabled = False 
    MYF87.SeedVal = WSeedVal 
    MYF87.MultByTwo 
End Sub 

Public Sub MYF87_Progress(Value As Long) 
    WResultVal = Value 
    RaiseEvent WProgress(WResultVal) 
    DoEvents 
End Sub 

Public Function CloseUpShop() As Integer 
    Set MYF87 = Nothing 
End Function 

необходимых изменения в UI Классе:

Option Explicit 

Private WithEvents lc1 As WClass.WrapperClass 
Private WithEvents lc2 As WClass.WrapperClass 
Private WithEvents lc3 As WClass.WrapperClass 
Private WithEvents lc4 As WClass.WrapperClass 
Private WithEvents lc5 As WClass.WrapperClass 

Private Sub cmd1_Click() 
    ' MsgBox ("Begin UI1.cmd1_Click") 
    Set lc1 = CreateObject("WClass.WrapperClass") 

    lc1.WSeedVal = CInt(txt1.Text) 
    lc1.Go 
    ' MsgBox ("End UI1.cmd1_Click") 
End Sub 

Public Sub lc1_WProgress(WResultVal As Long) 
    txtRes1.Text = CStr(WResultVal) 
    txtRes1.Refresh 

    DoEvents 
End Sub 

Private Sub cmd2_Click() 
    Set lc2 = CreateObject("WClass.WrapperClass") 
    lc2.WSeedVal = CInt(txt2.Text) 
    lc2.Go 
End Sub 

Public Sub lc2_WProgress(WResultVal As Long) 
    txtRes2.Text = CStr(WResultVal) 
    txtRes2.Refresh 

    DoEvents 
End Sub 

Private Sub cmd3_Click() 
    Set lc3 = CreateObject("WClass.WrapperClass") 
    lc3.WSeedVal = CInt(txt3.Text) 
    lc3.Go 
End Sub 

Public Sub lc3_WProgress(WResultVal As Long) 
    txtRes3.Text = CStr(WResultVal) 
    txtRes3.Refresh 

    DoEvents 
End Sub 

Private Sub cmd4_Click() 
    Set lc4 = CreateObject("WClass.WrapperClass") 
    lc4.WSeedVal = CInt(txt4.Text) 
    lc4.Go 
End Sub 

Public Sub lc4_WProgress(WResultVal As Long) 
    txtRes4.Text = CStr(WResultVal) 
    txtRes4.Refresh 

    DoEvents 
End Sub 

Private Sub cmd5_Click() 
    Set lc5 = CreateObject("WClass.WrapperClass") 
    lc5.WSeedVal = CInt(txt5.Text) 
    lc5.Go 
End Sub 

Public Sub lc5_WProgress(WResultVal As Long) 
    txtRes5.Text = CStr(WResultVal) 
    txtRes5.Refresh 

    DoEvents 
End Sub 

Private Sub Form_Load() 
    ' Timer1.Interval = 2000 
    ' Timer1.Enabled = True 
    Timer1.Enabled = False 
End Sub 

Private Sub Form_Unload(Cancel As Integer) 
    If Not lc1 Is Nothing Then 
     lc1.CloseUpShop 
     Set lc1 = Nothing 
    End If 
    If Not lc2 Is Nothing Then 
     lc2.CloseUpShop 
     Set lc2 = Nothing 
    End If 
    If Not lc3 Is Nothing Then 
     lc3.CloseUpShop 
     Set lc3 = Nothing 
    End If 
    If Not lc4 Is Nothing Then 
     lc4.CloseUpShop 
     Set lc4 = Nothing 
    End If 
    If Not lc5 Is Nothing Then 
     lc5.CloseUpShop 
     Set lc5 = Nothing 
    End If 
End Sub 

я все еще вижу такое же поведение. .. Нажмите cmd1, затем я вижу, что результаты начинаются в txtRes1. Нажмите cmd2, результат прекратит обновление в txtRes1 и txtRes2 до тех пор, пока он не завершится, а затем обновления txtRes1.

Я бы не ожидал, что это сработает в отладчике VB6, так как оно однопоточно, но создает исполняемый файл и запускает этот исполняемый файл, который все еще производит эти же результаты.

Я также попытался изменить способ создания моего TClass (Новый или CreateObject) - никакой разницы не заметил. Я также пытался использовать New и CreateObject() при создании WClass тоже ... все еще не делая то, что я хотел бы сделать ...

+1

Вы можете выполнить все три файла сразу, если вы измените exe от singleuse до multiuse. Вам не нужно работать с скомпилированной версией, пока вы не перейдете на SingleUse на exe, и вам никогда не придется работать с dll. – BobRodes

ответ

3

Поскольку вы проделали такую ​​приятную работу, задав свой вопрос, сделав его довольно простым, чтобы все наладить, я потратил немного времени, обманывая это. Во-первых, ваши DLL и EXE работают нормально. Ваша проблема заключается в том, что ваше решение Timer для обработки обновлений экрана отправило вас вниз по кроличьей дыре.

Во-первых, событие таймера никогда не срабатывает, если таймер не включен, поэтому бесполезно проверять свойство Enabled внутри обработчика событий. Затем, когда вы вызываете DoEvents, он только сбрасывает очередь событий для текущего объекта. Таким образом, вызов DoEvents в MYF87_Progress не запускает ваше событие Timer. Поэтому неверно, что событие Timer не срабатывает; что все ваши события таймера складываются в очередь событий формы, и все они выполняются сразу же после выполнения DLL. Этот дизайн, как вы находите, не работает, и даже если вы поймете, как его исправить, у вас будет нечто похожее на грузовик Джеда Клампетта.

Лучше всего добавить событие Progress в свою DLL, поднимите его из вашего обработчика MYF87_Progress и дайте вашей форме обработать его. (Я предполагаю, что причиной вашей DLL-оболочки является то, что у вас есть больше вещей, которые нужно вставлять в нее, что должно идти только в одном месте, в противном случае я бы предложил упростить ваш дизайн, просто нажав форму на EXE.) Вызовите DoEvents в обработчике формы, чтобы обновить экран.

Далее эта реализация кричит для управляющих массивов. Вы можете поместить каждую из ваших командных кнопок, каждый из ваших наборов из пяти текстовых полей и каждый из ваших экземпляров DLL в массив. Это значительно упростит работу, которую вы должны выполнить. На самом деле, весь код формы в значительной степени сводится к этому (плюс обработчик событий я уже упоминал):

Option Explicit 

Private lc(4) As WClass.WrapperClass 

Private Sub cmd_Click(Index As Integer) 
    Set lc(Index) = CreateObject("WClass.WrapperClass") 
    With lc(Index) 
     .WSeedVal = CInt(txt(Index).Text) 
     .Go 
     txtRes(Index).Text = CStr(.WResultVal) 
    End With 
End Sub 

Этот код покажет конечный результат каждый раз, когда вы нажимаете на кнопку, а не держать обновляя текстовые поля каждый раз, когда есть изменения, отправленные с вашего EXE. Для этого вам нужно будет вставить эту логику событий. Я оставлю это вам, поскольку вы, похоже, знаете, как это сделать.

Предположим, что у вас есть все, и отправьте сообщение, если у вас есть проблемы.

p. s. для создания контрольного массива просто сделайте все элементы управления в массиве одинаковыми, и установите свойство Index равным 0, 1, 2, 3 и т. д.

p. п. s. Я забыл, что вы не можете помещать WithEvents в массив объектов. Я собираюсь возиться с этим и посмотреть, есть ли способ получить объекты в массиве, но может быть необходимо иметь отдельные переменные, как у вас есть сейчас.

+0

BobRodes, благодарю вас за ответ. В фактическом приложении я являюсь массивом UDT, а экземпляр DLL является одним из членов UDT. Это был быстрый и грязный интерфейс для разработки связи между тремя компонентами. Более подробное тестирование с моей стороны показало, что когда я запускаю второй экземпляр, первый счетчик фактически останавливается, а затем возобновляется после завершения второго счетчика. Я не считаю, что это стекирование событий, потому что, если я отлаживаю пользовательский интерфейс, lc1.WResultVal не изменился. Больше играть. Кроме того, первоначальная среда представляет собой одноядерный процессор. Я буду тестировать свой Core i7 и обновлять. –

+0

Уточнение: то, что я сказал, относится ко всем событиям для одного нажатия кнопки. Вы говорите о нескольких нажатиях кнопок, и очередь событий имеет время, чтобы сбросить время, которое вы нажимаете на эти кнопки. Таким образом, заказ - это событие клика, все ваши события отчета, все ваши события таймера, если вы просто нажимаете одну кнопку. Если вы нажмете еще одну кнопку, то тот же порядок событий для второй кнопки будет смешаться с событиями из первого, в зависимости от того, когда они срабатывают по отношению к вашему другому материалу. (...) – BobRodes

+0

Если вы решите, что это не стабильная архитектура, я бы предложил вам использовать код, который я дал вам в качестве отправной точки, за исключением того, что вам придется взять массив lc и написать их все, как вы их , (Тем не менее, вы все равно должны использовать массивы для всех своих элементов управления.) Чтобы этого избежать, вам нужно будет использовать обратные вызовы, и я не хочу усложнять ситуацию, сообщая вам, как это сделать, пока вы не решите сбросить свою концепцию таймера , :) – BobRodes

Смежные вопросы