您的位置:首页 > 其它

VNC源码研究(二十四)vnc-4.0-winsrc版本之winvnc工程分析

2013-03-11 11:06 597 查看
转载请标明是引用于 http://blog.csdn.net/chenyujing1234


此工程编译出winvnc4.exe二进制文件。它得到的二进制是作为VNC服务端来使用的。

一、从main说起

int main(int argc, const char* argv[]) {
  int result = 0;

  try {
    // - Initialise the available loggers
    //freopen("\\\\drupe\\tjr\\WinVNC4.log","ab",stderr);
    //setbuf(stderr, 0);
    initStdIOLoggers();
    initFileLogger("C:\\temp\\WinVNC4.log");
    rfb::win32::initEventLogLogger(VNCServerService::Name);

    // - By default, just log errors to stderr
    logParams.setParam("*:stderr:0");

    // - Print program details and process the command line
    programInfo();
    processParams(argc, argv);

    // - Run the server if required
    if (runServer) {
      if (close_console) {
        vlog.info("closing console");
        if (!FreeConsole())
          vlog.info("unable to close console:%u", GetLastError());
      }

      network::TcpSocket::initTcpSockets();
      VNCServerWin32 server;

      if (runAsService) {
        printf("Starting Service-Mode VNC Server.\n");
        VNCServerService service(server);
		///>启动服务
        service.start();
        result = service.getStatus().dwWin32ExitCode;
      } else {
        printf("Starting User-Mode VNC Server.\n");
		///>启动server
       result = server.run();
      }
    }

    vlog.debug("WinVNC service destroyed");
  } catch (rdr::Exception& e) {
    try {
      vlog.error("Fatal Error: %s", e.str());
    } catch (...) {
      fprintf(stderr, "WinVNC: Fatal Error: %s\n", e.str());
    }
    if (!runAsService)
      MsgBox(0, TStr(e.str()), MB_ICONERROR | MB_OK);
  }

  vlog.debug("WinVNC process quitting");
  return result;
}
步一、打印信息设置

步二、打印程序详细信息

programInfo();

步三、处理命令行

processParams(argc, argv);
void
processParams(int argc, const char* argv[]) {
  for (int i=1; i<argc; i++) {
    try {

      if (strcasecmp(argv[i], "-connect") == 0) {
        runServer = false;
        CharArray host;
        if (i+1 < argc) {
          host.buf = strDup(argv[i+1]);
        } else {
          AddNewClientDialog ancd;
          if (ancd.showDialog())
            host.buf = strDup(ancd.getHostName());
        }
        if (host.buf) {
          HWND hwnd = FindWindow(0, _T("winvnc::IPC_Interface"));
          COPYDATASTRUCT copyData;
          copyData.dwData = 1; // *** AddNewClient
          copyData.cbData = strlen(host.buf);
          copyData.lpData = (void*)host.buf;
          i++;
          SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)©Data);
          printf("Sent connect request to VNC Server...\n");
        }
      } else if (strcasecmp(argv[i], "-disconnect") == 0) {
        HWND hwnd = FindWindow(0, _T("winvnc::IPC_Interface"));
        COPYDATASTRUCT copyData;
        copyData.dwData = 2; // *** DisconnectClients
        copyData.lpData = 0;
        copyData.cbData = 0;
        SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)©Data);
        printf("Sent disconnect request to VNC Server...\n");
        runServer = false;
      } else if (strcasecmp(argv[i], "-start") == 0) {
        printf("Attempting to start service...\n");
        runServer = false;
        if (rfb::win32::startService(VNCServerService::Name))
          printf("Started service successfully\n");
      } else if (strcasecmp(argv[i], "-stop") == 0) {
        printf("Attempting to stop service...\n");
        runServer = false;
        if (rfb::win32::stopService(VNCServerService::Name))
          printf("Stopped service successfully\n");
      } else if (strcasecmp(argv[i], "-status") == 0) {
        printf("Querying service status...\n");
        runServer = false;
        rfb::win32::printServiceStatus(VNCServerService::Name);

      } else if (strcasecmp(argv[i], "-service") == 0) {
        printf("Run in service mode\n");
        runAsService = true;

      } else if (strcasecmp(argv[i], "-register") == 0) {
        printf("Attempting to register service...\n");
        runServer = false;
        int j = i;
        i = argc;
        if (rfb::win32::registerService(VNCServerService::Name,
                                        _T("VNC Server Version 4"),
                                        argc-(j+1), &argv[j+1]))
          printf("Registered service successfully\n");
      } else if (strcasecmp(argv[i], "-unregister") == 0) {
        printf("Attempting to unregister service...\n");
        runServer = false;
        if (rfb::win32::unregisterService(VNCServerService::Name))
          printf("Unregistered service successfully\n");

      } else if (strcasecmp(argv[i], "-noconsole") == 0) {
        close_console = true;

      } else if ((strcasecmp(argv[i], "-help") == 0) ||
        (strcasecmp(argv[i], "--help") == 0) ||
        (strcasecmp(argv[i], "-h") == 0) ||
        (strcasecmp(argv[i], "/?") == 0)) {
        runServer = false;
        programUsage();
        break;

      } else {
        // Try to process <option>=<value>, or -<bool>
        if (Configuration::setParam(argv[i], true))
          continue;
        // Try to process -<option> <value>
        if ((argv[i][0] == '-') && (i+1 < argc)) {
          if (Configuration::setParam(&argv[i][1], argv[i+1], true)) {
            i++;
            continue;
          }
        }
        // Nope.  Show them usage and don't run the server
        runServer = false;
        programUsage();
        break;
      }

    } catch (rdr::Exception& e) {
      vlog.error(e.str());
    }
  }
}
(1)从命令行中提取出IP地址串,并组成把它放到COPYDATASTRUCT中。

找到窗口_T("winvnc::IPC_Interface"),将得到的COPYDATASTRUCT数据发送到窗口中。

因为在服务端此窗口的创建是在 “第一个构造函数:”, 而此时窗口一定是找不到的,那么还有一种情况可以找到就是VNC服务端与VNC客户端并存时就会出现。

这是因为VNC客户端也会创建此窗口。

那么可以说,这时的发送WM_COPYDATA其实是发送给客户端的。

if (host.buf) {
          HWND hwnd = FindWindow(0, _T("winvnc::IPC_Interface"));
          COPYDATASTRUCT copyData;
          copyData.dwData = 1; // *** AddNewClient
          copyData.cbData = strlen(host.buf);
          copyData.lpData = (void*)host.buf;
          i++;
          SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)©Data);
          printf("Sent connect request to VNC Server...\n");
        }


针对此WM_COPYDATA的响应是:

(2)

(3)

(4)

(5)

(6)

(7)

步四、启动服务

有两种方式可以选择:服务方式、用户模式(!!!!!!!!最重要)

if (runAsService) {
        printf("Starting Service-Mode VNC Server.\n");
        VNCServerService service(server);
		///>启动服务
        service.start();
        result = service.getStatus().dwWin32ExitCode;
      } else {
        printf("Starting User-Mode VNC Server.\n");
		///>启动server
       result = server.run();
      }
启动时得保证 runServer == true;

runServer的初始值是static bool runServer = true;,当我们有指定命令行时,此变量会变为false。

如在 “步三、处理命令行" ,命令行是“-connect”、"-disconnect"、“-start”、"-stop"、"-status"、"-register"、"-unregister"、"-help"

意思是如果指定了命令行,那么是不会启动服务的。

如果是要关闭console界面那么调用系统API

if (!FreeConsole())
来实现

(1)以服务方式启动客户端

VNCServerWin32 server;

      if (runAsService) {
        printf("Starting Service-Mode VNC Server.\n");
        VNCServerService service(server);
		///>启动服务
        service.start();
        result = service.getStatus().dwWin32ExitCode;


(2)以用户模式启动客户端服务

VNCServerWin32 server;

result = server.run();
///>服务器启动
int VNCServerWin32::run() {
  { Lock l(runLock);
    hostThread = Thread::self();
    runServer = true;
  }

  // - Register for notification of configuration changes
  ///>注册表设置
  if (isServiceProcess())
    config.setKey(HKEY_LOCAL_MACHINE, RegConfigPath);
  else
    config.setKey(HKEY_CURRENT_USER, RegConfigPath);
  config.setNotifyThread(Thread::self(), VNCM_REG_CHANGED);

  // - Create the tray icon if possible
  STrayIconThread trayIcon(*this, IDI_ICON, IDI_CONNECTED, IDR_TRAY);

  DWORD result = 0;
  try {
    // - Create some managed listening sockets
	  ///>创建监听socket
    ManagedListener rfb(&sockMgr, &vncServer);
    ManagedListener http(&sockMgr, httpServer);

    // - Continue to operate until WM_QUIT is processed
    MSG msg;
    do {
      // -=- Make sure we're listening on the right ports.
		///>设置监听端口
      rfb.setPort(port_number, localHost);
      http.setPort(http_port, localHost);

      // -=- Update the Java viewer's web page port number.
      httpServer->setRFBport(rfb.sock ? port_number : 0);

      // -=- Update the TCP address filter for both ports, if open.
      CharArray pattern;
      pattern.buf = hosts.getData();
      if (!localHost) {
        rfb.setFilter(pattern.buf);
        http.setFilter(pattern.buf);
      }

      // - If there is a listening port then add the address to the
      //   tray icon's tool-tip text.
      {
        const TCHAR* prefix = isServiceProcess() ?
          _T("VNC Server (Service):") : _T("VNC Server (User):");

        std::list<char*> addrs;
        if (rfb.sock)
          rfb.sock->getMyAddresses(&addrs);
        else
          addrs.push_front(strDup("Not accepting connections"));

        std::list<char*>::iterator i, next_i;
        int length = _tcslen(prefix)+1;
        for (i=addrs.begin(); i!= addrs.end(); i++)
          length += strlen(*i) + 1;

        TCharArray toolTip(length);
        _tcscpy(toolTip.buf, prefix);
        for (i=addrs.begin(); i!= addrs.end(); i=next_i) {
          next_i = i; next_i ++;
          TCharArray addr = *i;    // Assumes ownership of string
          _tcscat(toolTip.buf, addr.buf);
          if (next_i != addrs.end())
            _tcscat(toolTip.buf, _T(","));
        }
        trayIcon.setToolTip(toolTip.buf);
      }

      vlog.debug("Entering message loop");

      // - Run the server until the registry changes, or we're told to quit
	  ///>进入socket监听,进入消息循环
      while (sockMgr.getMessage(&msg, NULL, 0, 0)) {
        if (msg.hwnd == 0) {
          if (msg.message == VNCM_REG_CHANGED)
            break;
          if (msg.message == VNCM_COMMAND)
            doCommand();
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }

    } while ((msg.message != WM_QUIT) || runServer);

    vlog.debug("Server exited cleanly");
  } catch (rdr::SystemException &s) {
    vlog.error(s.str());
    result = s.err;
  } catch (rdr::Exception &e) {
    vlog.error(e.str());
  }

  { Lock l(runLock);
    runServer = false;
    hostThread = 0;
  }

  return result;
}
(2、1)注册表设置,并向线程发送NCM_REG_CHANGED消息

if (isServiceProcess())
    config.setKey(HKEY_LOCAL_MACHINE, RegConfigPath);
  else
    config.setKey(HKEY_CURRENT_USER, RegConfigPath);
  config.setNotifyThread(Thread::self(), VNCM_REG_CHANGED);
发送的消息最后是在 “(2、4)进入一个while的操作,直到收到WM_QUIT后才退出” 中被收到的

(2、2)创建 tray icon

// - Create the tray icon if possible
  STrayIconThread trayIcon(*this, IDI_ICON, IDI_CONNECTED, IDR_TRAY);


创建时会调用STrayIconThread的构造函数:

STrayIconThread::STrayIconThread(VNCServerWin32& sm, UINT inactiveIcon_, UINT activeIcon_, UINT menu_)
: server(sm), inactiveIcon(inactiveIcon_), activeIcon(activeIcon_), menu(menu_),
  windowHandle(0), runTrayIcon(true) {
  start();
}
因为STrayIconThread继承自Thread,所以这里的start其实会调用到STrayIconThread::run()

void STrayIconThread::run() {
  while (runTrayIcon) {
    if (rfb::win32::desktopChangeRequired() && 
      !rfb::win32::changeDesktop())
      Sleep(2000);

    STrayIcon icon(*this);
    windowHandle = icon.getHandle();

    MSG msg;
    while (runTrayIcon && ::GetMessage(&msg, 0, 0, 0) > 0) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    windowHandle = 0;
  }
}
(2、2、1)

if (rfb::win32::desktopChangeRequired() && 
			!rfb::win32::changeDesktop())
			Sleep(2000);


(2、2、2)创建STrayIcon,并获得它的窗口句柄。

STrayIcon icon(*this);
		windowHandle = icon.getHandle();
STrayIcon继承自TrayIcon, TrayIcon继承自MsgWindow

class winvnc::STrayIcon : public TrayIcon
class TrayIcon : public MsgWindow
class MsgWindow {
    public:
      MsgWindow(const TCHAR* _name);
      virtual ~MsgWindow();

      const TCHAR* getName() {return name.buf;}
      HWND getHandle() const {return handle;}

      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);

    protected:
      TCharArray name;
      HWND handle;
    };


创建STrayIcon时会调用到三个构造函数:

第一个构造函数:

MsgWindow的构造函数就是创建一个窗口,窗口名称为"VNCTray",大小为10*10;

MsgWindow::MsgWindow(const TCHAR* name_) : name(tstrDup(name_)), handle(0) {
  vlog.debug("creating window \"%s\"", (const char*)CStr(name.buf));
  handle = CreateWindow((const TCHAR*)baseClass.classAtom, name.buf, WS_OVERLAPPED,
    0, 0, 10, 10, 0, 0, baseClass.instance, this);
  if (!handle) {
    throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError());
  }
  vlog.debug("created window \"%s\" (%x)", (const char*)CStr(name.buf), handle);
}


窗口类名为(const TCHAR*)baseClass.classAtom; _T("rfb::win32::MsgWindowClass")

baseClass是类MsgWindowClass,它用来管理窗口类。

MsgWindowClass::MsgWindowClass() : classAtom(0) {
  WNDCLASS wndClass;
  wndClass.style = 0;
  wndClass.lpfnWndProc = MsgWindowProc;
  wndClass.cbClsExtra = 0;
  wndClass.cbWndExtra = 0;
  wndClass.hInstance = instance = GetModuleHandle(0);
  wndClass.hIcon = 0;
  wndClass.hCursor = 0;
  wndClass.hbrBackground = 0;
  wndClass.lpszMenuName = 0;
  wndClass.lpszClassName = _T("rfb::win32::MsgWindowClass");
  classAtom = RegisterClass(&wndClass);
  if (!classAtom) {
    throw rdr::SystemException("unable to register MsgWindow window class", GetLastError());
  }
}
特别重要的是它的窗口消息处理函数MsgWindowProc。
如果消息是在创建窗口时,即CreateWindows时,会收到消息WM_CREATE,此时把(long)((CREATESTRUCT*)lParam)->lpCreateParams,即MsgWindow的this指针作为

窗口数据存起来。之后的MsgWindowsProc中对各消息的处理就是取出窗口中的数据,强转为MsgWindows后,调用它的processMessage 函数,

因为MsgWindows类中的processMessage成员是虚函数,所以如果继承类有去重载它,那么是只会调用了继承类的processMessage函数。如:

STrayIcon的成员processMessage.

LRESULT CALLBACK MsgWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  LRESULT result;

  if (msg == WM_CREATE)
    SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
  else if (msg == WM_DESTROY)
    SetWindowLong(wnd, GWL_USERDATA, 0);
  MsgWindow* _this = (MsgWindow*) GetWindowLong(wnd, GWL_USERDATA);
  if (!_this) {
    vlog.info("null _this in %x, message %x", wnd, msg);
    return SafeDefWindowProc(wnd, msg, wParam, lParam);
  }

  try {
    result = _this->processMessage(msg, wParam, lParam);
  } catch (rdr::Exception& e) {
    vlog.error("untrapped: %s", e.str());
  }

  return result;
};


第二个构造函数:

TrayIcon() : MsgWindow(_T("VNCTray")) {
#ifdef NOTIFYICONDATA_V1_SIZE
        nid.cbSize = NOTIFYICONDATA_V1_SIZE;
#else
        nid.cbSize = sizeof(NOTIFYICONDATA);
#endif

        nid.hWnd = getHandle();
        nid.uID = 0;
        nid.hIcon = 0;
        nid.uFlags = NIF_ICON | NIF_MESSAGE;
        nid.uCallbackMessage = WM_USER;
      }


第三个构造函数:STrayIcon构造函数

STrayIcon(STrayIconThread& t) : thread(t),
    vncConfig(_T("vncconfig.exe"), isServiceProcess() ? _T("-noconsole -service") : _T("-noconsole")),
    vncConnect(_T("winvnc4.exe"), _T("-connect")) {

    // ***
    SetWindowText(getHandle(), _T("winvnc::IPC_Interface"));
    // ***

    SetTimer(getHandle(), 1, 3000, 0);
    PostMessage(getHandle(), WM_TIMER, 1, 0);
    PostMessage(getHandle(), WM_SET_TOOLTIP, 0, 0);
  }


在STrayIcon的构造函数中:

首先构造两对象

LaunchProcess vncConfig;
  LaunchProcess vncConnect;
作用:在窗口收到消息WM_COMMAND,命令为ID_CONNECT时就连接服务端:

CurrentUserToken token;
          if (token.isValid())
            vncConnect.start(isServiceProcess() ? (HANDLE)token : 0);
          else
            vlog.error("Options: unknown current user");
void LaunchProcess::start(HANDLE userToken) {
  if (procInfo.hProcess && (WaitForSingleObject(procInfo.hProcess, 0) != WAIT_OBJECT_0))
    return;
  await();

  // - Create storage for the process startup information
  STARTUPINFO sinfo;
  memset(&sinfo, 0, sizeof(sinfo));
  sinfo.cb = sizeof(sinfo);

  // - Concoct a suitable command-line
  TCharArray exePath;
  if (!tstrContains(exeName.buf, _T('\\'))) {
    ModuleFileName filename;
    TCharArray path; splitPath(filename.buf, &path.buf, 0);
    exePath.buf = new TCHAR[_tcslen(path.buf) + _tcslen(exeName.buf) + 2];
    _stprintf(exePath.buf, _T("%s\\%s"), path.buf, exeName.buf);
  } else {
    exePath.buf = tstrDup(exeName.buf);
  }

  // - Start the VNC server
  // Note: We specify the exe's precise path in the ApplicationName parameter,
  //       AND include the name as the first part of the CommandLine parameter,
  //       because CreateProcess doesn't make ApplicationName argv[0] in C programs.
  TCharArray cmdLine(_tcslen(exeName.buf) + 3 + _tcslen(params.buf) + 1);
  _stprintf(cmdLine.buf, _T("\"%s\" %s"), exeName.buf, params.buf);
#ifdef _DEBUG
  DWORD flags = CREATE_NEW_CONSOLE;
#else
  DWORD flags = CREATE_NO_WINDOW;
#endif
  BOOL success;
  if (userToken)
    success = CreateProcessAsUser(userToken, exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo);
  else
    success = CreateProcess(exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo);
  if (!success)
    throw rdr::SystemException("unable to launch process", GetLastError());

  // Wait for it to finish initialising
  WaitForInputIdle(procInfo.hProcess, 15000);
}


然后,设置窗口名称为_T("winvnc::IPC_Interface"

然后开启定时器1,每3S触发一次;

最后给窗口发送WM_TIMER与WM_SET_TOOLTIP消息;

作用:WM_TIMER用于刷新图标状态

如果桌面不是输入桌面,那么就把图标关闭,如果(“当前桌面和名称”与"输入桌面名称“是否一致来判断);

如果桌面还是存在那么更新当前的图标状态。(激活或非激活)

if (rfb::win32::desktopChangeRequired()) {
        SendMessage(getHandle(), WM_CLOSE, 0, 0);
        return 0;
      }
      setIcon(thread.server.isServerInUse() ? thread.activeIcon : thread.inactiveIcon);
      return 0;
WM_SET_TOOLTIP用于设置”工具提示信息“

case WM_SET_TOOLTIP:
      {
        rfb::Lock l(thread.lock);
        if (thread.toolTip.buf)
          setToolTip(thread.toolTip.buf);
      }


(2、2、3)消息循环

MSG msg;
		while (runTrayIcon && ::GetMessage(&msg, 0, 0, 0) > 0) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}


(2、3)创建监听socket

ManagedListener rfb(&sockMgr, &vncServer);
    ManagedListener http(&sockMgr, httpServer);


(2、4)进入一个while的操作,直到收到WM_QUIT后才退出

MSG msg;
do {
      // 第一件事:设置监听端口
      // 第二件事:Update the Java viewer's web page port number
	  //           Update the TCP address filter for both ports, if open.
	  //
	  // 第三件事:如果有一个正在监听的端口,那么就增加地址到icon的tooltip(提示信息)
	  //
      // - Run the server until the registry changes, or we're told to quit
	  ///>进入socket监听,进入消息循环
      while (sockMgr.getMessage(&msg, NULL, 0, 0)) {
        if (msg.hwnd == 0) {
          if (msg.message == VNCM_REG_CHANGED)
            break;
          if (msg.message == VNCM_COMMAND)
            doCommand();
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
} while ((msg.message != WM_QUIT) || runServer);


这里最重要的是 sockMgr.getMessage(&msg, NULL, 0, 0 函数!!!!!!!!





三、扩展知识

1、TLSAlloc()

缩写为TLS。进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保;
多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。

如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static
memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。
线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。

Win32

方法一:每个线程创建时系统给它分配一个LPVOID指针数组(叫做TLS数组),这个数组从C编程角度是隐藏着的不能直接访问,需要通过一些C
API函数调用访问。首先定义些DWORD线程全局变量或函数静态变量,准备作为各个线程访问自己的TLS数组的索引变量。一个线程使用TLS时,第一步在线程内调用TlsAlloc()函数,为一个TLS数组索引变量与这个线程的TLS数组的某个槽(slot)关联起来,例如获得一个索引变量:

global_dwTLSindex=TLSAlloc();

注意,此步之后,当前线程实际上访问的是这个TLS数组索引变量的线程内的拷贝版本。也就说,不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是不同DWORD值。其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。C/C++原本没有直接定义线程局部静态变量的机制,所以在如此大费周折。

第二步,为当前线程动态分配一块内存区域(使用LocalAlloc()函数调用),然后把指向这块内存区域的指针放入TLS数组相应的槽中(使用TlsValue()函数调用)。

第三步,在当前线程的任何函数内,都可以通过TLS数组的索引变量,使用TlsGetValue()函数得到上一步的那块内存区域的指针,然后就可以进行内存区域的读写操作了。这就实现了在一个线程内部这个范围处处可访问的变量。

最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),然后从TLS数组中放弃对应的槽(使用TlsFree()函数)。

在VNC-4.0-winsrc开源代码中看到,它用了TSL的方法:

(1)定义

static DWORD threadStorage = TlsAlloc();
为一个TLS数组索引变量与这个线程的TLS数组的某个槽(slot)关联起来,例如获得一个索引变量:

(2)设置值

WINBASEAPI
BOOL
WINAPI
TlsSetValue(
    __in     DWORD dwTlsIndex,
    __in_opt LPVOID lpTlsValue
    );


DWORD WINAPI
Thread::threadProc(LPVOID lpParameter) {
  Thread* thread = (Thread*) lpParameter;
  TlsSetValue(threadStorage, thread);
  logAction(thread, "started");
  try {
    thread->run();
    logAction(thread, "stopped");
  } catch (rdr::Exception& e) {
    logError(thread, e.str());
  }
  bool deleteThread = false;
  {
    Lock l(thread->mutex);
    thread->state = ThreadStopped;
    thread->sig->signal();
    deleteThread = thread->deleteAfterRun;
  }
  if (deleteThread)
    delete thread;
  return 0;
}


(3)获得值

WINBASEAPI
LPVOID
WINAPI
TlsGetValue(
    __in DWORD dwTlsIndex
    );


Thread*
Thread::self() {
  Thread* thread = (Thread*) TlsGetValue(threadStorage);
  if (!thread) {
    thread = new Thread(GetCurrentThread(), GetCurrentThreadId());
    TlsSetValue(threadStorage, thread);
  }
  return thread;
}


2、__declspec(thread)的使用



#include <stdio.h>
#include <assert.h>

// 这就是两个线程都要访问的变量
__declspec(thread) int g_nData = 0;

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	g_nData = 5;
	// 辅线程睡眠100ms,保证主线程的g_nData = 10;语句执行成功
	Sleep(100);
	TCHAR szMsg[100] = {0};
	wsprintf(szMsg, L"Auxi thread, g_nData:%d\n", g_nData);
	MessageBox(NULL, szMsg, L"AuxiThread", MB_ICONINFORMATION);
	return 0;
}

int main()
{
	DWORD dwId;
	// 创建线程,并立即启动它
	HANDLE hThread = CreateThread(NULL, 1024, ThreadProc, NULL, 0, &dwId);
	assert(hThread);
	// 主线程睡50ms,保证辅线程的g_nData = 5语句执行成功。
	Sleep(50);
	g_nData = 10;
	
	TCHAR szMsg[100] = {0};
	wsprintf(szMsg, L"Result %d\n", g_nData);
	MessageBox(NULL, szMsg, L"MainThread", MB_ICONINFORMATION);

	return 0;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: