津津's profile溪流漫话BlogLists Tools Help

Blog


    8/5/2009

    Visual Studio 2010 及 C++ 0x、C# 4.0(二)

    前面说到 C++ 0x 的 Lambda 表达式,前几天我总是感觉它缺了点什么,但说不出来。
    现在觉得可能可以表达出来了。那就是,C++ 中没有明确的“委托”类型。
    (为了表达这个概念,又不与 C++ 中已有概念混淆,这里请允许我使用 C# 中的名词。)
     
    其实也不是没有,C++ 的函数指针其实就是形式上很精确的“委托”类型。虽然它归根结底却是一个模糊得不能再模糊的普通指针而已,但起码,一个这样的函数放在那里:

    void sort(int arr[], int size, bool (*comparer)(int, int));

    给人的信息是非常明确且严格的。
    但是如上篇所试验,不能像这样传入 Lambda 表达式:

    sort(arr, N, [](int a, int b){ return a >= b; });

    而只能先定义一个小函数,然后使用:

    bool greater_than(int a, int b)

    {

        return a >= b;

    }

    sort(arr, N, greater_than);

    但是 Lambda 的引入,不就是为了解决这种小函数的问题的吗?
    另外,如果把函数改为

    template <typename T>

    void sort(int elem[], int n, T comparer); 

    这样,虽然(可能)确实可以 sort(arr, N, [](int a, int b){ return a >= b; }); ,
    但是实际使用的时候,我却不能知道 comparer 到底要符合什么样的形式。
     
    而 C# 中,由于有明确的委托类型存在,这种问题也就不存在了:

    delegate bool BinaryIntComparer(int a, int b);

    void sort(int[] arr, BinaryIntComparer comparer);

    使用的时候:

    sort(arr, (int a, int b) => a >= b);

     
    所以,对于 C++ 0x 来说,作为语法糖的 Lambda,似乎还不够甜……
     
    ==============================华丽的分隔线==============================
    今天随意看了一下 C# 4.0 的特性。所谓的协变性和逆变性一下子还看不明白。。。(果然 C# 是用来玩设计的)
    动态特性倒是理解了一点点:
     

    class Plane

    {

        public void Fly()

        {

            Console.WriteLine("Plane flies.");

        }

    }

     

    class Car

    {

        public void Run()

        {

            Console.WriteLine("Car runs");

        }

    }

     

    class Bird

    {

        public void Fly()

        {

            Console.WriteLine("Bird flies.");

        }

    }

     

    class Program

    {

        static void LetItFly(dynamic thing)

        {

            try

            {

                thing.Fly();

            }

            catch

            {

                Console.WriteLine("Method \"Fly()\" does not exists.");

            }

        }

     

        static void Main(string[] args)

        {

            LetItFly(new Plane());

            LetItFly(new Car());

            LetItFly(new Bird());

        }

    }

     
    结果:
    Plane flies.
    Method "Fly()" does not exists.
    Bird flies.
     
    我们所折腾的就是被声明为 dynamic 类型的 thing。“thing.Fly();”中的句点后面可以随意写个方法名称,编译的时候不来检查,到运行的时候才检查。
    如果没有 dynamic,参数就要被声明为 object,然后根据反射特性,去查询 Fly 方法有没有……
    看起来 dynamic 只是把这一堆繁琐的过程省略了。
     
    至于网上牛人们说的动态语言静态语言大融合这高度上的东东,我暂时是没有体会到,大概是我动态语言没怎么玩过的缘故吧。。。呃……JavaScript 也是动态语言?我怎么没感觉呢。。。
     
     
     
    7/29/2009

    Visual Studio 2010 及 C++ 0x(一)

    我记得之前下过 Visual Studio 2010 CTP 的,昨晚翻来翻去找不着了,只好重新下过。最新版本 beta1 了。
    之前看到过 C++ 0x 的一些新特性,忍不住试一试。
     
    首先是 lambda 表达式。网上有很多介绍 C++ 0x 特性的文章,其中关于 lambda 表达式的文章几乎是千篇一律的

    std::for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    比较感兴趣的是 lambda 表达式本身的类型,也就是,上述 for_each 中的第三个参数该怎么声明?
     
    试验一:

    bool greater_than(int a, int b, bool (*comparer)(int, int))

    {

        return comparer(a, b);

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        bool b = greater_than(1, 2, [](int a, int b)->bool { return a > b; });

     

        return 0;

    }

     
    编译不过:
    Error 1 error C2664: 'greater_than' : cannot convert parameter 3 from '`anonymous-namespace'::<lambda0>' to 'bool (__cdecl *)(int,int)'
     
    可见 Lambda 表达式不能向下兼容到函数指针。
    看了一下 std::for_each 的定义,它的第三个参数是一个 functor。所以,
     
    试验二:

    template <typename Comparer>

    bool greater_than(int a, int b, Comparer compare)

    {

        return compare(a, b);

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        bool b = greater_than(1, 2, [](int a, int b)->bool { return a > b; });

     

        return 0;

    }

     
    编译通过,运行也正确。
     
    所以,似乎 Lambda 表达式的本质是 functor?
     
    有点怀疑能否进行严格的类型检查在上例中,故意传给 greator_than 一个不实现 operator()(int, int) 的对象,即
     
    试验三:

    template <typename Comparer>

    bool greater_than(int a, int b, Comparer compare)

    {

        return compare(a, b);

    }

     

    int _tmain(int argc, _TCHAR* argv[])

    {

        bool b = greater_than(1, 2, [](int a)->bool { return true; });

     

        return 0;

    }

     
    这里这个 Lambda 表达式只接受一个参数,意味着 Comparer 只实现了 operator()(int),而没有实现 operator()(int, int)。
     
    结果:
    Error 2 error C2064: term does not evaluate to a function taking 2 arguments
     
    突然醒悟了一下,觉得自己灰常灰常土了,C++ 的 functor 应该本来就是强类型的,支持编译期类型检查的吧?
    怪不得那些库都抛开了函数指针,而使用 functor 了。
     
    ==============================华丽的分隔线==============================
     
    强类型 enum 似乎 VS2010 里没有实现(据说 g++ 4.4 实现了)?怎么试怎么不行

    enum Enum1 : double

    {

        a1,

        b1,

        c1

    };

     

    enum class Enum1

    {

        a2,

        b2,

        c2

    };

     
    都通不过……
     
    [待续]
    3/7/2009

    Writing a Windows Service program

    Yesterday I searched the Internet for articles about how to write a Windows Service program. It is a pity that few of them explains main points clearly. Many of those articles give steps under .Net Framework, using C# or Managed C++. Some just show a class that helps in Windows Service programing. Only a few of them talk under Win32 SDK, that is what I want, yet they do not point out where should I put my task code. Finally, I find a sample code in http://hi.baidu.com/borland82/blog/item/b538341b1ea2f4fdae5133f7.html, and successfully wrote a simple Service. (BTW, this author's article is not clear, too). And today, I find articles in MSDN tell whatever we need in Windows Service programming, according to which I rewrote a sample. For other beginners and myself, I am recording something about this. Please excuse me that I wrote in English. In fact I hate English. But for some further reasons I want some attempts.
     
    A Service program of Windows is not that special as I imagined before. It could be a simple Win32 console Application. In my case here, I do it in a Win32 console Application. Open Visual Studio and create a Win32 Console Application in C++. This is my main() function:
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        // To support self-install and self-uninstall
        if (argc == 2)
        {
            _wcslwr(argv[1]);
            if (lstrcmp(argv[1], _T("/install")) == 0)
            {
                InstallService();
                return 0;
            }
            else if (lstrcmp(argv[1], _T("/uninstall")) == 0)
            {
                UninstallService();
                return 0;
            }
        }

        PrepareService();
        return 0;
    }
     
    Here I use command line arguments to support self-install and self-uninstall. Ignore them at this stage, look at PrepareService(). When we start a service in Microsoft Management Control (MMC), the system runs our executable file, just like executing an application manually. Our program will go to PrepareService() function. Turn to the function:
     
    void PrepareService()
    {
        SERVICE_TABLE_ENTRY ste[2];
        ste[0].lpServiceName = g_lpszServiceName;
        ste[0].lpServiceProc = ServiceMain;
        ste[1].lpServiceName = NULL;
        ste[1].lpServiceProc = NULL;

        StartServiceCtrlDispatcher(ste);
    }
     
    In this function we did nothing but calling StartServiceCtrlDispatcher(). It is an important function. I strongly recommend you to look up the function in MSDN. Follow what there said you will be able to write a Service. But now, please follow me.
     
    The function StartServiceCtrlDispatcher() need an array of SERVICE_TABLE_ENTRY to work. Each item in the array contain enough information to start a service, and the array should be end with a null item. We are to start only one service, so here we need an array of two elements, the last of which is set to null. There are two members in SERVICE_TABLE_ENTRY struct. The first one, lpServiceName describes the name of out service. (It will be ignored by the system Service Control Manager (SCM) if we set so-called "service type" to a speacial value. Just go ahead first.) Here I set it to g_lpszServiceName, which is a global variable defined as:
     
    TCHAR g_lpszServiceName[] = _T("MyService");
     
    We will use it several times. The other member ServiceProc should be point to a callback function, which is usually name ServiceMain(). After calling of StartServiceCtrlDispatcher(), the SCM will call ServiceMain() in a new thread, thus our service process continues.
     
    void WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
    {
        g_hServiceStatus = RegisterServiceCtrlHandlerEx(g_lpszServiceName, HandlerProc, NULL);

        // Here you could do some initializing work
        g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        g_Status.dwCurrentState = SERVICE_RUNNING;
        g_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;// | SERVICE_ACCEPT_PAUSE;
        g_Status.dwWin32ExitCode = NO_ERROR;
        g_Status.dwServiceSpecificExitCode = NO_ERROR;
        g_Status.dwCheckPoint = 0;
        g_Status.dwWaitHint = 0;
        
        if (!SetServiceStatus(g_hServiceStatus, &g_Status))
        {
            // Failed to set status to RUNNING
            return;
        }

        OnStart();
    }
     
    We should immediately call RegisterServiceCtrlHandlerEx() in ServiceMain(). Another callback function HandleProc will be register to SCM, to respond when the service is paused, or stopped, etc. The funtion RegisterServiceCtrlHandlerEx() returns a SERVICE_STATUS_HANDLE. It will be used in HanderProc(), so here I make it global.
     
    After that we are to tell the SCM that our service is started(running) by calling SetServiceStatus(). Before calling, you could do some initializing work. If initializing work fails, we instead told SCM that our service is still stopped. The SetServiceStatus() needs a SERVICE_STATUS struct. Pay attention to its first three members. I set the ServiceType to SERVICE_WIN32_OWN_PROCESS here, it means the service runs in its own process. Additionally, the SCM will ignore our service name. CurrentState tells the SCM the status of our service. Normally we set it SERVICE_RUNNING. The ControlsAccepted determines how the user could control the service. SERVICE_ACCEPT_STOP means user can stop the service, and SERVICE_ACCEPT_PAUSE means user can pause the service. Here I enabled SERVICE_ACCEPT_STOP but disabled SERVICE_ACCEPT_PAUSE. The rest members of SERVICE_STATUS is not so important. Besides, If your initializing work will last for more then one second, you should set the WaitHin member to a well-estimated value.
     
    Finally, our service is started, after calling SetServiceStatus() with SERVICE_RUNNING. If we have nothing to do at the moment, ServiceMain() will return and the thread ServiceMain in will terminate automatically. That means the service stops immediately after being started. Obviously, we write a Service not only to start and stop it. We need to do some work. So, where should we put our task code? It is an important thing we need to know. The answer is, HERE! Please take notice of what I said before, in PrepareService(). The ServiceMain is already in a new thread. We do not need to create another thread. To make the code more clear, I put task code in a separate function OnStart(). In the function we do our task and keep the thread running. Only when we need to stop our service, we could and should try to terminate this thread safely, that means let OnStart() and ServiceMain() return.
     
    Here is the function OnStart():
     
    void OnStart()
    {
        g_bRunning = TRUE;
        while (g_bRunning)
        {
            // Put task code here
           
            // I have really nothing to do at the moment, leaving it blank.
        }
    }
     
    I simply used a global variable to control the service thread. When the service need to be stopped, just turn it to FALSE and the thread will terminate.
     
    Last function we should put attention to is HandleProc():
     
    DWORD WINAPI HandlerProc(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
    {
        switch (dwControl)
        {
        case SERVICE_CONTROL_PAUSE:
            OnPause();
            g_Status.dwCurrentState = SERVICE_PAUSED;
            SetServiceStatus(g_hServiceStatus, &g_Status);
            break;
        case SERVICE_CONTROL_CONTINUE:
            OnContinue();
            g_Status.dwCurrentState = SERVICE_RUNNING;
            SetServiceStatus(g_hServiceStatus, &g_Status);
            break;

        case SERVICE_CONTROL_STOP:
            OnStop();
            g_Status.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(g_hServiceStatus, &g_Status);
            break;
        default:
            break;
        }
        return NO_ERROR;
    }
     
    The SCM calls this function when the service is paused/continued/stopped/... by the user. We should call SetServiceStatus() to notify the SCM that the status id changed successfully. Before that, we may need to do something to control our own task. I put them in separate functions too. For an example, I only respond to SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE and SERVICE_CONTROL_STOP. You can add or delete cases when needed. This service does not support pause/continue (reference to ServiceMain()), so here will never receives SERVICE_CONTROL_PAUSE and SERVICE_CONTROL_CONTINUE indeed.
     
    The rest three "smart" function is:
     
    void OnPause()
    {
        // Not supported
    }
    void OnContinue()
    {
        // Not supported
    }
    void OnStop()
    {
        // Set the variable to FALSE to terminate the service thread.
        g_bRunning = FALSE;
    }

    I have not told what we did in InstallService() and UninstallService(). First I believed I could directly write to the system register in HKLM\SYSTEM\CurrentControlSet\Services, but failed. In fact Windows provides APIs for installing services. Reference to MSDN articles "Installing a Service" and "Deleting a Service". There give sample codes. I just copied codes from there.
     
    It is time to go and test out service. Use "/install" to install our service, run services.msc, find our service in the list, and, start it.
     
    Sample Codes:
     
    2009-03-07 23:08