Golden Blonde 发表于 2022-10-16 23:59:17

【VB6】多线程讨论终结贴:使用ActiveX-EXE实现完美的多线程

如何在VB6里实现稳定多线程,一直是网上争论不休的话题。网上基本上有两派,第一派是通过修补VB程序的机器码和调用特定函数来实现多线程。但是经我测试,无论哪个版本、无论作者号称多么稳定,其实都是不稳定的。我前段时间做了个《坦克大战》,使用单线程时非常稳定,而使用网上大神们的“魔改多线程”时,玩不到20分钟程序就会卡死或崩溃。而第二派,就是使用ActiveX-EXE实现多线程。网上对ActiveX-EXE实现多线程的稳定性没有质疑,但是在其它方面有诸多指责,比如说在ActiveX-EXE程序里,句柄不能共享,全局变量不能用等等。其实这些指责里,一半是谣言,一半是误解(因为没有搞清楚ActiveX-EXE的多线程特性造成的)。

在讨论多线程之前,先讨论一下,VB里什么物件是全局的?所有人都会说:窗体对象是全局的,模块是全局的,全局变量和常量也是全局的。但是“全局物件”又有两层含义,第一层是,写在任何地方的代码都可以直接访问它们,第二层是,它们没有“分身”。这时有人要说了,你唠唠叨叨说这些废话跟多线程有什么关系呢?我必须指出的是,VB里没有任何“自带物件”在进程内是全局的,它们仅是在线程内全局。一旦新建了线程,并且当新线程访问这些全局物件时,所有的物件都会给访问它的线程创造一个“分身”。即使是模块里的全局函数也不例外。举一个例子:#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

long StaticTest()
{
      static long v = 0;
      return ++v;
}

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
      printf("[%ld]%ld\n",GetCurrentThreadId(),StaticTest());
      return 0;
}

int _cdecl main(int argc, char **argv)
{
      CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
      Sleep(500);
      CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
      Sleep(500);
      CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
      system("pause");
      return 0;
}这段C程序的三次输出,应该分别是1、2、3。但如果用VB的AvtiveX-EXE写同样的代码,它们的输出分别是1、1、1!因为正如我上面所说,虽然在代码里,StaticTest函数是全局的,但是ActiveX-EXE的每一个线程里,它们都有自己独立的StaticTest!看懂了上文,你就会明白为什么会有“ActiveX-EXE里连句柄都不能共享”的谣言:因为即使是全局变量,线程A根本就无法直接访问到线程B的全局变量。但是这个句柄只要产生,它在进程里就是有效的。你只要想办法把线程A产生的句柄值让线程B得知即可。这里面的方法太多了,小白用SaveSetting/GetSetting、驱动老哥用DOS头的废弃空间、学院派用文件映射,不管什么方法,能达成目标就行。此外,某个线程要访问其它线程的变量和窗口/类是可行的,但必须用指针,指针的值也可以同样用上述方法传递。

接下来解释在ActiveX-EXE里玩多线程的具体步骤。首先你要新建一个模块,写一个Sub Main。任何ActiveX-EXE的代码都从Sub Main开始执行,这点对于习惯了C语言的人都很好理解。在Sub Main里你要创建一个主窗口,让Sub Main的代码尽快结束。这一步是必须的,因为创建多个线程的代码在Sub Main里无效,因此接下来的代码只能写在窗口里。需要注意的是,由于任何线程在启动的时候都会执行一次Sub Main,因此你要在Sub Main里设置一个“事件”,防止你的初始化代码被重复执行。接下来就是关于多线程代码的核心步骤了:新建一个类模块,但这个类模块只是一个“药引”,它唯一的作用是在Class_Initialize里Load一个窗口(这个窗口被称为“多线程代码窗口”)。在“多线程代码窗口”里,你要放置一个Interval为1的Timer1,在Timer1_Timer里,先写一句Timer1.Enabled = False。在这句之后,才写你自己的代码。当线程执行完毕要退出时,简单一句Unload Me即可。Private Sub Timer1_Timer()
    Timer1.Enabled = False
    MsgBox CStr(App.ThreadID), , "tid"
    Unload Me
End Sub
当创建新线程的时候,只需要三行代码:Dim o As Object
Set o = CreateObject("app.class")
Set o = Nothing
'//其实你可以把这三行理解为:
'HANDLE h;
'h = CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
'CloseHandle(h);
最后稍微解释一下怎么让ActiveX-EXE正确运行,需要修改两个设置:


需要注意的是,多线程效果只有在编译为EXE后才有效:


完整DEMO代码:**** Hidden Message *****

0xAA55 发表于 2022-10-17 10:05:08

以前一直没有注意到 ActiveX-EXE 有“独立模式”“每个对象对应一个线程”这两个选项。

但“系统消息”有告诉过我“VB6的正常多线程应该是线程之间隔离的,互相之间不能访问到对方的数据。”

看样子你这样就是最“正规”的VB6创建多线程的方式,而且你也遇到了“每个新线程都要进入 Sub Main()”的现像。

祝你考试成功



系统消息 发表于 2022-10-18 10:55:14

我是第三派,使用Active.DLL来做多线程,因为VB6的Active.DLL支持被多线程调用,dll导出一个类模块作为线程入口即可,然后VC那边写个exe来执行CreateThread,然后每个线程里面各自创建一个类模块的对象实例并调用其入口方法就好了。

xiawan 发表于 2022-10-25 08:23:49

啥也不说了,感谢楼主分享哇!

二十六 发表于 2022-10-26 14:23:18

先感谢再看!感谢!!

dsm 发表于 2022-10-31 22:35:00

谢谢分享。

nmcfbgttyl 发表于 2022-11-18 15:14:01

用过的请发言

/u12456 发表于 2022-11-20 15:40:47

太吊了,虽然现在已经不用VB6了

W741 发表于 2022-11-21 09:48:57

每天拜一拜大佬,保佑我的技术突飞猛进

imr2013 发表于 2022-11-23 23:02:03

感谢楼主分享!

iamydp 发表于 2022-12-28 17:09:44

正好需要,下载看看

大保 发表于 2023-1-12 13:21:34

谢谢分享,下载虔诚膜拜!

cnHopeStudio 发表于 2023-1-14 14:32:02

感谢分享!
太有用了!

搬砖工 发表于 2023-2-18 22:28:42

回复参与。

搬砖工 发表于 2023-2-18 22:32:13

有试过这个吗?
https://github.com/thetrik/VbTrickThreading

Golden Blonde 发表于 2023-2-24 22:46:11

搬砖工 发表于 2023-2-18 22:32
有试过这个吗?
https://github.com/thetrik/VbTrickThreading

当然试过,我还用它来做过多线程的坦克大战,然而很明显,这货是不行的。每次运行不到10分钟,程序就会崩溃。

scdn2022 发表于 2023-4-7 23:22:35

谢谢分享

BillGhifun 发表于 2023-4-22 21:38:55

感谢分享

liudongmin1129 发表于 2023-4-23 18:05:22

感谢楼主分享!

rufeng001 发表于 2023-5-2 01:12:29

啥也不说了,感谢楼主分享哇!
页: [1] 2
查看完整版本: 【VB6】多线程讨论终结贴:使用ActiveX-EXE实现完美的多线程