分享 卡萨布兰卡,让您发霉的老代码重新发亮吧

xhj6 · 2014年01月25日 · 最后由 jiang_plus 回复于 2014年01月25日 · 4614 次阅读

前因

很多年前,我有个师兄,姓唐名朝,用 Delphi 开了一套单机软件,叫“智能中医诊断系统”,就是用户输入自己的症状,他那个软件就分析这些症状,有针对性的再问用户几个问题,最后给出一两个方子。当年他开发出来后,放到高交会上去展示,效果很好,他大喜,立马定价 888 元一套,结果买了大半年都只买了几套出去,房租又贵,只好关了公司重新打工去了。

前些天刚好本地有个单位叫我给他们的 DLL 算法程序弄一下压力测试,做的过程中突然想到了我那不太走运的师兄,如果他那套程序搬上网会怎么样呢?很有技术含量的啊,算法都用了一大堆,在网上一定会大发特发的吧?光是买春药广告都不得了。。。

卡萨布兰卡

去年,微软开源了代码为 Casablanca 的 C++ REST SDK,目的主要是为了让 C++ 编程时更加方便的消费 RESTful 服务。但最近它新增了一项功能:New experimental features such as HTTP Listener library,正是这项功能的出现,我认为它会成为一个比较具有潜力的项目,简单的说,利用 Casablanca,可以搭建起一个原生代码与云计算服务之间的双向桥梁,轻轻松松的把那些用 C、C++、Delphi 甚至是 VB 写的单机程序转变成 Web 服务,让单机时代的优秀创意在云计算时代重新闪亮!

感受一下

准备 DLL(假设说它就是您的过时代码)

作为示例,我们开发一个 DLL,这个 DLL 调用 OpenCV 的图形处理功能将一张彩色图片转换为灰度图,代码如下,很简单:

#include "stdafx.h"
#include "TestFuncDLL.h"
#include <stdexcept>
#include <opencv\cv.h>
#include <opencv\highgui.h>

using namespace cv;
using namespace std;

namespace TestFuncs{
    int MyTestFuncs::count = 0;

    int MyTestFuncs::Gray(char* input, char* output){
        Mat image;

        image = imread(input, 1);
        if (!image.data){
            printf(" No image data \n ");
            return -1;
        }

        Mat gray_image;
        cvtColor(image, gray_image, CV_BGR2GRAY);
        imwrite(output, gray_image);

        return 0;
    }
}

具体步骤为:

  1. 打开 VS 2013,新建一个 Win32 应用,在应用程序设置那一步选择“DLL”,建立一个 DLL 工程。
  2. 使用 NuGet 程序包管理器,安装 OpenCV 支持。
  3. 创建头文件和源文件,注意要加入#define _CRT_SECURE_NO_DEPRECATE,不然链接 OpenCV 时会报错。
  4. 编译,DLL 完工。

这里值得一提的是NuGet,这玩意儿用起来比 gem 还方便,搜索程序包,然后点安装,就完事了。如果不看看 Ruby 之外的东西,真不知道各家的技术都这么好用了。

C++ Web Service

不废话,直接上代码。不到 100 行就可以把一个原生代码函数开放成一个 json web service,您还等什么呢,压箱子底的老旧代码都拿出来吧!

这是侦听程序:

#include "stdafx.h"
#include "TestListener.h"
#include "TestFuncDLL.h"
#include <iostream>
#include <string>

using namespace std;

namespace TestService{
    TestListener::TestListener(const http::uri& url) : m_litenser(http_listener(url))
    {
        m_litenser.support(methods::GET, tr1::bind(&TestListener::handle_get, this, tr1::placeholders::_1));
        m_litenser.support(methods::POST, tr1::bind(&TestListener::handle_post, this, tr1::placeholders::_1));

        try{
            m_litenser.open().then([&](){ wcout << "Test listener started." << endl; }).wait();
            while (true);
        }
        catch (exception const & e){
            wcout << e.what() << endl;
        }
    }

    void TestListener::handle_get(http_request request){
        utility::string_t greeting = U("您好!您现在访问的是测试引擎...");
        json::value::field_map answer;
        answer.push_back(make_pair(json::value::string(U("greeting")), json::value::string(greeting)));
        request.reply(status_codes::OK, json::value::object(answer));
    }

    int wCharToChar(const wchar_t *orig, char *nstring){
        size_t origsize = wcslen(orig) + 1;
        size_t convertedChars = 0;
        wcstombs_s(&convertedChars, nstring, origsize, orig, _TRUNCATE);
        return 0;
    }


    void handle_request(http_request request,
        function<void(json::value &, json::value::field_map &)> action)
    {
        json::value::field_map answer;

        request
            .extract_json()
            .then([&answer, &action](pplx::task<json::value> task) {
            try
            {
                auto & jvalue = task.get();

                if (!jvalue.is_null())
                {
                    action(jvalue, answer);
                }
            }
            catch (http_exception const & e)
            {
                wcout << e.what() << endl;
            }
        }).wait();

        request.reply(status_codes::OK, json::value::object(answer));
    }

    void TestListener::handle_post(http_request request){

        handle_request(
            request,
            [](json::value & jvalue, json::value::field_map & answer)
        {
            char input[1000];
            char output[1000];

            for (auto const & e : jvalue)
            {
                if (e.first.is_string() && e.second.is_string())
                {
                    auto key = e.first.as_string();
                    auto value = e.second.as_string();
                    if (key == U("input")){
                        wCharToChar(value.c_str(), input);
                    }
                    if (key == U("output")){
                        wCharToChar(value.c_str(), output);
                    }
                }
            }

            if (strlen(input) > 0 && strlen(output) > 0){
                int result = TestFuncs::MyTestFuncs::Gray(input, output);
            }   
        });
    }
}

这是主程序:

// TestWebService.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "MainListerner.h"
#include "TestListener.h"
#include <thread>

using namespace std;

void mainListener(){
    TestService::MainListener listener(L"http://localhost/welcome");
}

void testListener(){
    TestService::TestListener listener(L"http://localhost/test");
}

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "启动测试引擎服务..." << endl;
    thread t_main(mainListener);
    thread t_test(testListener);
    t_main.join();
    t_test.join();
}

在主项目中,除了要添加 DLL 引用外,同样,也用 NuGet 添加 c++ rest sdk 支持。

这么点代码,它完成了什么功能

TestFuncDLL.dll 完成的功能很简单,就是调用 OpenCV 将一张彩色图片转换为灰色图片,计算量肯定比你们的程序小很多。

TestWebService.exe 是封装好了的程序,它提供两个接口:http://localhost/welcomehttp://localhost/testhttp://localhost/welcome,其中 只接受 GET 请求,/test 接口接受 GET 和 POST 请求。

GET 请求均只是简单的返回一个欢迎字符串,比如 welcome 返回 {"greeting":"您好!中科院重庆分院美妆引擎正在为您服务..."}。

/test 接口接受 json 数据的 POST 请求,json 数据格式为:{"input": "源图片路径", "output": "输出图片路径"},显然,这里只考虑了本机访问的情况,如果外网访问,这两个参数应是“地址”而不是“路径”。

平台支持

由于 Casablanca 目前还只是实验性的,目前只支持 Windows 7, 8 和 Linux,其中 Linux 下不支持 https、认证等功能。

新代码用得上吗

当然也用得上,比如在计算机视觉、图形图像等领域,C++ 被大量使用,这些代码除了以传统的中间件(如 ICE 等)的形式整合到线上外,Casablanca 的出现,我们可以有了另一种比较轻便、廉价的选择。

疑问

最后,我有一个疑问,相比 Casablanca 把原生代码封装成 WebService,还有一种更加简单的方案,就是直接用 Ruby/Python/.NET 等调用 DLL。是啊,直接调用 DLL 就完了嘛,需要 卡萨布兰卡 吗?需要吗,不需要吗?我怎么本能感觉用语言直接调用 DLL 不太靠谱呢?

各位分析一下,直接调用是不是真的不靠谱,哪点不靠谱了,还是仅仅只是我自己想复杂了?

前期我会直接调 exe,如果可以的话

需要 登录 后方可回复, 如果你还没有账号请 注册新账号