博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Django 源码阅读:url解析
阅读量:4299 次
发布时间:2019-05-27

本文共 21335 字,大约阅读时间需要 71 分钟。

一、写在前面

继上一篇博文,进一步分析Django URL解析过程。通过网上博客的学习以及对于源码阅读所做笔记。

邮箱地址:@gmail.com

个人博客:
CSDN博客:

二、写在前面

上一篇博文, run()函数,服务启动最后的动作是httpd.serve_forever,调用的是socketserver.BaseServer.serve_forever方法。该方法采用了selector网络模型进行等待数据。启动服务后服务器进入一个无限循环的状态,poll_interval用来设置循环间隔时间,默认为0.5秒。在这里先简单分析下Python 源码,上篇博文中这块的介绍没有好好的梳理。在这里进行补充下。

/usr/lib/python2.7/SocketServer.py:BaseServer.serve_forever()

class BaseServer:    timeout = None    def __init__(self, server_address, RequestHandlerClass):        # 初始化,传入构建一个Socket服务器所必需的address以及每个请求到达时的处理类        """Constructor.  May be extended, do not override."""        self.server_address = server_address        # RequestHandlerClass 注册 handle 函数,在finish_request 中实例化,调用用户定义的 handle 函数        self.RequestHandlerClass = RequestHandlerClass        self.__is_shut_down = threading.Event()        self.__shutdown_request = False            def server_activate(self):        """Called by constructor to activate the server.            May be overridden.          服务器激活,由构造函数调用以激活服务器。可能被重写。        """        pass                # 装饰器函数,重新启动被eintr中断的系统调用    def _eintr_retry(func, *args):        """restart a system call interrupted by EINTR"""        while True:            try:                return func(*args)            except (OSError, select.error) as e:                if e.args[0] != errno.EINTR:                    raise            def serve_forever(self, poll_interval=0.5):        """Handle one request at a time until shutdown.            Polls for shutdown every poll_interval seconds. Ignores        self.timeout. If you need to do periodic tasks, do them in        another thread.        """        self.__is_shut_down.clear()        try:            while not self.__shutdown_request:                # XXX: Consider using another file descriptor or                # connecting to the socket to wake this up instead of                # polling. Polling reduces our responsiveness to a                # shutdown request and wastes cpu at all other times.                # 调用 select 监视请求,处理 EINTR 异常                r, w, e = _eintr_retry(select.select, [self], [], [],                                       poll_interval)                if self in r:                 # 有请求进来                    self._handle_request_noblock()        finally:            self.__shutdown_request = False            self.__is_shut_down.set()    … …

SocketServer.py中的 BaseServer 和 BaseRequestHandler

Python为网络编程提高了更高级的封装。 提供了不少网络服务的类。它们的设计很优雅。Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。在构建网络服务的时候,Server 和 RequestHandler 并不是分开的,RequestHandler的实例对象在Server 内配合 Server工作。
主要几个Server关系如下:

+------------+| BaseServer |+------------+      |      v+-----------+        +------------------+| TCPServer |------->| UnixStreamServer |+-----------+        +------------------+      |      v+-----------+        +--------------------+| UDPServer |------->| UnixDatagramServer |+-----------+        +--------------------+

BaseServer 分析

BaseServer 通过__init__初始化,对外提供serve_forever和 handler_request方法, run()函数中httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)触发,其中httpd_cls是通过type()函数动态加载的WSGIServer类,这里httpd_cls表示:<class 'django.core.servers.basehttp.WSGIServer'>,继承自:<class 'django.core.servers.basehttp.WSGIServer'>,socketserver.ThreadingMixIn

init 初始化:

def __init__(self, server_address, RequestHandlerClass):    # 初始化,传入构建一个Socket服务器所必需的address以及每个请求到达时的处理类    """Constructor.  May be extended, do not override."""    self.server_address = server_address    # RequestHandlerClass 注册 handle 函数,在finish_request 中实例化,调用用户定义的 handle 函数    self.RequestHandlerClass = RequestHandlerClass    self.__is_shut_down = threading.Event()    self.__shutdown_request = False

__init__源码很简单。主要作用是创建server对象,并初始化server地址和处理请求的class。熟悉socket编程应该很清楚,server_address是一个包含主机和端口的元组。

serve_forever

创建了server对象之后,使用server对象开启一个无限循环。

def serve_forever(self, poll_interval=0.5):    """Handle one request at a time until shutdown.    Polls for shutdown every poll_interval seconds. Ignores    self.timeout. If you need to do periodic tasks, do them in    another thread.    """    self.__is_shut_down.clear()    try:        while not self.__shutdown_request:            # XXX: Consider using another file descriptor or            # connecting to the socket to wake this up instead of            # polling. Polling reduces our responsiveness to a            # shutdown request and wastes cpu at all other times.            # 调用 select 监视请求,处理 EINTR 异常            r, w, e = _eintr_retry(select.select, [self], [], [],                                   poll_interval)            if self in r:                # 有请求进来                self._handle_request_noblock()    finally:        self.__shutdown_request = False        self.__is_shut_down.set()

serve_forever接受一个参数poll_interval,用于表示select轮询的时间。然后进入一个无限循环,调用select方式进行网络IO的监听。

如果select函数返回,表示有IO连接或数据,那么将会调用_handle_request_noblock方法。

_handle_request_noblock

def _handle_request_noblock(self):    """Handle one request, without blocking.    I assume that select.select has returned that the socket is    readable before this function was called, so there should be    no risk of blocking in get_request().    处理一个请求,不会阻塞。 我假设select.select返回了在调用此函数之前套接字是可读的,因此get_request()中不应存在阻塞的风险。    """    try:        # 接收请求 accept,get_request 由子类实现,一般为接收请求,返回 socket        request, client_address = self.get_request()    except socket.error:        return    if self.verify_request(request, client_address):        try:            # BaseServer.process_request 中有 BaseRequestHandler 的回调动作,实例化用户定义的 handler, __init__ 中完成对 handle() 的调用            self.process_request(request, client_address)        except:            self.handle_error(request, client_address)            # 关闭连接            self.shutdown_request(request)    else:        self.shutdown_request(request)

_handle_request_noblock方法即开始处理一个请求,并且是非阻塞。该方法通过get_request方法获取连接,具体的实现在其子类。一旦得到了连接,调用verify_request方法验证请求。验证通过,即调用process_request处理请求。如果中途出现错误,则调用handle_error处理错误,以及shutdown_request结束连接。

get_request

在这里,调用的是子类class TCPServer(BaseServer)中的get_request。TCPServer 继承了BaseServer,初始化的时候,进行了socket套接字的创建。

get_request:

该类最重要的方法就是 get_request。该方法进行返回socket对象的请求连接。

def get_request(self):    """Get the request and client address from the socket.    May be overridden.    从套接字获取请求和客户端地址。可以覆盖。    """    return self.socket.accept()

get_request方法是在BaseServer基类中的_handle_request_noblock中调用,从那里里传入套接字对象获取的连接信息。如果是UDPServer,这里获取的就是UDP连接。

此外,TCPServer还提供了一个 fileno 方法,提供给基类的select调用返回文件描述符。

verify_request

def verify_request(self, request, client_address):    """Verify the request.  May be overridden.    Return True if we should proceed with this request.    验证请求。 可以覆盖。如果我们应该继续这个请求,则返回True。    """    return True

该方法对request进行验证,通常会被子类重写。简单的返回True即可,然后进入process_request方法处理请求。

process_request

在这里调用的是在ThreadingMixIn类定义的process_request函数,并不是BaseServer类中的process_request。

class ThreadingMixIn:    """Mix-in class to handle each request in a new thread.用于处理新线程中的每个请求的混合类。"""    # Decides how threads will act upon termination of the    # main process    # 决定线程在主进程终止时的操作方式    daemon_threads = False    def process_request_thread(self, request, client_address):        """Same as in BaseServer but as a thread.        In addition, exception handling is done here.            与BaseServer相同,但这里作为线程。此外,此处完成异常处理。        """        try:            self.finish_request(request, client_address)            self.shutdown_request(request)        except:            self.handle_error(request, client_address)            self.shutdown_request(request)    def process_request(self, request, client_address):        """Start a new thread to process the request.启动一个新线程来处理请求"""        t = threading.Thread(target = self.process_request_thread,                             args = (request, client_address))        t.daemon = self.daemon_threads        t.start()

process_request方法是mixin的入口,MixIn子类通过重写该方法,进行多线程或多进程的配置。调用finish_request完成请求的处理,同时调用shutdown_request结束请求。

finish_request

def finish_request(self, request, client_address):    """Finish one request by instantiating RequestHandlerClass.    通过实例化RequestHandlerClass完成一个请求。    """    self.RequestHandlerClass(request, client_address, self)

finish_request方法将会处理完毕请求。创建RequestHandlerClass对象,并通过RequestHandlerClass做具体的处理。

其中self.RequestHandlerClass(request, client_address, self):在_init_()中定义为self.RequestHandlerClass = RequestHandlerClass

在这里,RequestHandlerClass参数是上一博文中,在:

django.core.servers.basehttp.py run()函数(1)中定义的WSGIRequestHandler类,在(2)中进行初始化,既调用BaseServer中的_init_()。

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):    server_address = (addr, port)    if threading:        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, server_cls), {}) # (1)这里,动态加载类,这里httpd_cls:
,继承自:
,socketserver.ThreadingMixIn else: httpd_cls = server_cls httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) #(2)这里,参数WSGIRequestHandler:
… …

因此,RequestHandlerClass是由django.core.servers.basehttp.py: def run() -> httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)传递过来的参数WSGIRequestHandler:django.core.servers.basehttp.WSGIRequestHandler。

也就是说当执行self.RequestHandler(request, client_address, self)时等同于执行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)

WSGIRequestHandler

django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)
在这里,初始化WSGIRequestHandler的时候可以看到并没有__init__()函数,查看继承关系,
django.core.servers.basehttp.WSGIRequestHandler ->
simple_server.WSGIRequestHandler ->
BaseHTTPServer.BaseHTTPRequestHandler ->
SocketServer.StreamRequestHandler ->
SocketServer.BaseRequestHandler
在这里即调用BaseRequestHandler类_init_ 完成对 handle() 的调用。

BaseRequestHandler 分析

所有requestHandler都继承BaseRequestHandler基类。

class BaseRequestHandler:    def __init__(self, request, client_address, server):        self.request = request # 客户端请求对象,
self.client_address = client_address # 客户端地址,('192.168.31.178', 50029) self.server = server # 服务端 self.setup() # 调用子类的setup()函数,处理socket连接 try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass

BaseRequestHandler是请求处理程序类的基类,该类会处理每一个请求。

_init_()为每个要处理的请求实例化该类。 初始化对象的时候,设置请求request对象、client_address和server,然后调用setup方法,子类会重写该方法,用于处理socket连接,接下来的将是handler和finish方法。 要实现特定服务,需要做的就是派生一个定义handle()方法的类。所有对请求的处理,都可以重写handler方法。

可以简单的理解为:setup()是处理前的初始化操作,handle()是处理请求,finish()是清理操作。

setup(self)

设置完请求request对象、client_address和server后,接着执行self.setup(),该函数在子类StreamRequestHandler实现。

StreamRequestHandler。它继承了BaseRequestHandler。基类的setup方法和finish方法被它重写,用于通过连接实现缓存文件的读写操作。

class StreamRequestHandler(BaseRequestHandler):    """    rfile、wfile的默认缓冲区大小。我们默认将rfile设为buffered,否则对于大数据(每个字节的getc()调用)会很慢;我们将wfile设为无缓冲,因为(a)通常在写入()之后,我们想要读取并且需要刷新该行;(b)对未缓冲文件的大写入通常由stdio优化。    """     # 为流式套接字定义rfile和wfile    rbufsize = -1    wbufsize = 0        # 应用于请求套接字的超时(如果不是None)。    timeout = None         # 如果为True,则禁用此套接字的nagle算法。     # 仅在wbufsize!= 0时使用,以避免小数据包。    disable_nagle_algorithm = False    def setup(self):        self.connection = self.request        if self.timeout is not None:            self.connection.settimeout(self.timeout)        if self.disable_nagle_algorithm:            self.connection.setsockopt(socket.IPPROTO_TCP,                                       socket.TCP_NODELAY, True)        self.rfile = self.connection.makefile('rb', self.rbufsize) # 一个类似文件的对象,可以用来接收客户端的数据        self.wfile = self.connection.makefile('wb', self.wbufsize) # 一个类似文件的对象,可以向客户端返回数据    def finish(self):        if not self.wfile.closed:            try:                self.wfile.flush()            except socket.error:                # 此处可能发生最终套接字错误,例如本地错误ECONNABORTED。                pass        self.wfile.close()        self.rfile.close()

setup

setup判断了是否使用nagle算法。然后设置对应的连接属性。最重要的就是创建了一个可读(rfile)和一个可写(wfile)的“文件”对象,他们实际上并不是创建了文件,而是封装了读取数据和发送数据的操作,抽象成为对文件的操作。可以理解为 self.rfile 就是读取客户端数据的对象,它有一些方法可以读取数据。self.wfile则是用来发送数据给客户端的对象。后面的操作,客户端数据到来会被写入缓冲区可读,需要向客户端发送数据的时候,只需要向可写的文件中write数据即可。

finish

在finish()中清理了对应的文件对象,同时wfile关闭之前需要刷新,将一些没有向客户端返回数据数据返回掉。

def finish(self):    if not self.wfile.closed:        # wfile关闭之前需要刷新        try:            self.wfile.flush()        except socket.error:            pass    self.wfile.close()    self.rfile.close()

handle()

django.core.servers.basehttp.handle()

handle(),调用子类django.core.servers.basehttp.WSGIRequestHandler中的handle()函数,主要处理的是HTTP是否保持连接的问题,如果保持连接就持续处理客户请求,否则就结束,最后使用shutdown来关闭socket的功能。

handle_one_request()比较重要语句:

def handle(self):    self.close_connection = True    self.handle_one_request()    while not self.close_connection:        # 连接没有关闭,循环处理每一个请求        self.handle_one_request()    try:        """        使用shutdown来关闭socket的功能        SHUT_RDWR:关闭读写,即不能使用send/write/recv/read等        SHUT_RD:关闭读,即不能使用read/recv等        SHUT_WR:关闭写功能,即不能使用send/write等        除此之外,还将缓冲区中的内容清空        """        self.connection.shutdown(socket.SHUT_WR)    except (socket.error, AttributeError):        pass

handle_one_request()

def handle_one_request(self):    """Copy of WSGIRequestHandler.handle() but with different ServerHandler    WSGIRequestHandler.handle()的副本,但具有不同的ServerHandler    """    # 读取第一行raw_requestline,一般格式应该是:COMMAND PATH VERSION\r\n;解析请求。这里:'GET /i18n/js/horizon+openstack_dashboard/ HTTP/1.1\r\n'    self.raw_requestline = self.rfile.readline(65537)    if len(self.raw_requestline) > 65536:        # 如果解析的请求地址长度大于65536个字节,发送和记录一个414错误回复给client。        self.requestline = ''        self.request_version = ''        self.command = ''        self.send_error(414)        return    # 解析请求(内部)。请求应存储在self.raw_requestline中; 结果在self.command,self.path,self.request_version和self.headers中。    # 返回True表示成功,False表示失败; 失败时,会发回错误。    if not self.parse_request():        return    # 初始化一个服务器处理程序,初始化动作在父类中实现    # ServerHandler() ->    # django.core.servers.basehttp.ServerHandler()->    # wsgiref.simple_server.ServerHandler() ->    # wsgiref.handlers.SimpleHandler().__init__()    # python重定向sys.stdin、sys.stdout和sys.stderr:标准输入、标准输出和错误输出    # handler:
handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) # self:
handler.request_handler = self # backpointer for logging & connection closing 用于记录和连接关闭的backpointer handler.run(self.server.get_app())

其中,self.parse_request(),既调用父类中的BaseHTTPServer.BaseHTTPRequestHandler.parse_request

(),解析请求(内部)。需要解析的请求存储在self.raw_requestline中; 结果在self.command,self.path,self.request_version和self.headers中。返回True表示成功,False表示失败; 失败时,会发回错误。
command:是一个(区分大小写)关键字,如GET或POST。
path:是一个包含请求的路径信息的字符串。应该是字符串“HTTP / 1.0”或“HTTP / 1.1”。使用URL编码方案进行编码(使用%xx表示带有十六进制代码xx的ASCII字符。
request_version:包括请求的HTTP协议版本号的字符串,样例:‘HTTP/1.0’。
headers:headers是包含头信息的mimetools.Message(或派生类)的实例;

HTTP 请求(request)

http 请求分为三个部分:
1. 第一行:请求类型、地址和版本号
2. 头部信息:HTTP header
3. 数据部分
标准的 HTTP 请求是:

GET / HTTP/1.1Host: cizixs.comUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateConnection: keep-aliveIf-Modified-Since: Thu, 25 Feb 2016 16:00:57 GMTCache-Control: max-age=0

标准的 HTTP 响应头部:

HTTP/1.1 304 Not ModifiedServer: GitHub.comDate: Thu, 24 Mar 2016 06:21:25 GMTLast-Modified: Thu, 25 Feb 2016 16:00:57 GMTaccess-control-allow-origin: *Expires: Thu, 24 Mar 2016 06:31:25 GMTCache-Control: max-age=600X-GitHub-Request-Id: 3AF60A59:7CE3:1C889201:56F38765 data...

parse_request()

def parse_request(self)    self.command = None  # set in case of error on the first line    # 默认请求版本。这只会影响响应,直到请求行被解析为止,因此它主要决定客户端在发送请求格式错误时返回什么。大多数web服务器默认为HTTP 0.9,即不发送状态行。    self.request_version = version = self.default_request_version    # 关闭连接设置为真    self.close_connection = 1    # 原始请求行    requestline = self.raw_requestline    # 删除 requestline 字符串末尾的指定字符,这里表示换行    requestline = requestline.rstrip('\r\n')    self.requestline = requestline    # 默认一空格分割请求行    words = requestline.split()    if len(words) == 3:        command, path, version = words        if version[:5] != 'HTTP/':            # 如果请求版本不是HTTP/开头,则返回400            self.send_error(400, "Bad request version (%r)" % version)            return False        try:            # 例如version=HTTP/1.1,则返回1.1            base_version_number = version.split('/', 1)[1]            # 返回['1', '1']            version_number = base_version_number.split(".")            # RFC 2145 3.1节规定只能有一个'.' 和主要和次要数字必须被视为单独的整数;            if len(version_number) != 2:                # 如果包含版本好的数字少于两个则抛出异常                raise ValueError            # 类型转换(1, 1)                version_number = int(version_number[0]), int(version_number[1])        except (ValueError, IndexError):            self.send_error(400, "Bad request version (%r)" % version)            return False        # 如果版本好大于等于(1, 1),以及支持的HTTP协议版本大于等于"HTTP/1.1",则表示请求的版本号正确,关闭连接设置为假。        if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":            self.close_connection = 0        if version_number >= (2, 0):            self.send_error(505,                      "Invalid HTTP Version (%s)" % base_version_number)            return False    elif len(words) == 2:        # 请求第一行2个,即“请求类型”、“地址”和“版本号”中缺少一个,则返回400错误。        command, path = words        self.close_connection = 1        if command != 'GET':            self.send_error(400,                            "Bad HTTP/0.9 request type (%r)" % command)            return False    elif not words:        return False    else:        self.send_error(400, "Bad request syntax (%r)" % requestline)        return False    # 如果http请求第一行为:GET / HTTP/1.1,则:self.command=GET, self.path=/, self.request_version=HTTP/1.1    self.command, self.path, self.request_version = command, path, version    # 检查标题并查找连接指令    self.headers = self.MessageClass(self.rfile, 0)    # 请求头中查找“Connection: keep-alive”    conntype = self.headers.get('Connection', "")    if conntype.lower() == 'close':        # 关闭请求连接        self.close_connection = 1    elif (conntype.lower() == 'keep-alive' and          self.protocol_version >= "HTTP/1.1"):          # 保持请求连接        self.close_connection = 0    return True

handle_one_request()最后执行到handler.run(self.server.get_app())

这里包含两个动作:self.server.get_app()、handler.run()

self.server.get_app(),/usr/lib/python2.7/wsgiref/simple_server.py:WSGIServer().get_app()

def get_app(self):    # 
return self.application

返回一个StaticFilesHandler类对象,继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。

这里请求的地址为:/i18n/js/horizon+openstack_dashboard/看上去是一个静态文件。

handler.run(),/usr/lib/python2.7/wsgiref/handlers.py(76):BaseHandler.run()

def run(self, application):    """Invoke the application调用应用程序"""    try:        # 为一个请求设置环境,包括输入、错误输出。        self.setup_environ()        # 调用:/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/handlers.py(62)__call__()        self.result = application(self.environ, self.start_response)        # 发送任何可迭代数据,然后关闭self和iterable        self.finish_response()    except:        try:            self.handle_error()        except:            # If we get an error handling an error, just give up already!            self.close()            raise   # ...and let the actual server figure it out.

self.result = application(self.environ, self.start_response) ->

/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/handlers.py(62)StaticFilesHandler(WSGIHandler)
.call() ->
/usr/local/lib/python2.7/dist-packages/django/core/handlers/wsgi.py(153)WSGIHandler(base.BaseHandler).call()

def __call__(self, environ, start_response):    set_script_prefix(get_script_name(environ))    signals.request_started.send(sender=self.__class__, environ=environ)# 向接受通知的注册者发送通知    request = self.request_class(environ)# 调用WSGIRequest实例化请求    response = self.get_response(request) # 调用处理方法处理request,返回给定HttpRequest的HttpResponse对象    response._handler_class = self.__class__# 设置_handler_class 为当前处理的类    status = '%d %s' % (response.status_code, response.reason_phrase)# 相应结果的状态码和对应描述    # 获取响应的响应头部信息,获取响应的cookie信息    response_headers = [        *response.items(),        *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),    ]    start_response(status, response_headers)# 设置响应的响应头部信息    if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):# 判断响应中是否有文件传输        response = environ['wsgi.file_wrapper'](response.file_to_stream)    return response# 返回处理结果

调用处理方法处理request,返回给定HttpRequest的HttpResponse对象。

def get_response(self, request):    """Return an HttpResponse object for the given HttpRequest.返回给定HttpRequest的HttpResponse对象。"""    # Setup default url resolver for this thread为此线程设置默认URL解析程序    set_urlconf(settings.ROOT_URLCONF)# 设置url配置    # 调用settings.MIDDLEWARE中配置的中间件进行处理,    response = self._middleware_chain(request)    response._closable_objects.append(request)    if response.status_code >= 400:        log_response(            '%s: %s', response.reason_phrase, request.path,            response=response,            request=request,        )    return response

至此,简单的分析完成,后续随着自己的理解将进一步补充说明。

转载地址:http://utxws.baihongyu.com/

你可能感兴趣的文章
C# 中IntPtr 与 string,数组互转
查看>>
mysql 8.0.19 winx64安装及修改初始密码
查看>>
mysql安装问题汇总
查看>>
mysql数据库迁移到另一台电脑上
查看>>
winform中UI设计分辨率问题
查看>>
QT之在QML中使用C++类和对象的两种方式
查看>>
Qt消息机制与window程序消息的对比分析
查看>>
QT中处理不同Windows(窗体中的)消息
查看>>
Qt自定义事件实现及子线程向主线程传送事件消息
查看>>
Qt消息机制和事件概述(一)
查看>>
c#winform控件过多卡顿问题解决方案
查看>>
c#中将WM_CLOSE消息发送到没有窗口的进程的方法
查看>>
C# 重写WndProc及发送消息
查看>>
c# 中重载WndProc,实现重写“最小化”自定义功能的方法
查看>>
子类继承父类后调用virtual函数问题(base.函数名)
查看>>
使用函数指针实现父类函数调用子类函数的两种方式
查看>>
C#中如何获取鼠标及控件的位置坐标(相对于屏幕)
查看>>
windows系统-汇编语言的语法是“操作码+操作数”
查看>>
windows系统-汇编语言与机器码的关系
查看>>
C#操作SQLite数据库增、删、改、查 实例
查看>>