apache module开发--c调com

     最近在公司作了一套算法---大数据量下求两点间的最短距离。之前是用c++写了个com让前台去(c#)调用,不过是基于C/S的,现在是公司需要将这个算法发布成服务,使得web也可以调用。一开始是考虑使用java调同样的com,这样我就不用做任何修改,但是同事说java调com不稳定。所以就考虑使用apache来发布服务,将算法打包成apache的一个module,于是开始着手研究。

一、apache module开发。

apache之前么有接触过,只是上学时用tomcat做过几个网页。对于module开发更是没有听说过,于是开始在网上各种找。相关方面的资料有,但是不是很多,而且都是大同小异并没有太多深入。更主要的是都是在linux下的开发,并且是基于c的。先不管这些,自己摸索着把环境搭起来,做个简单的例子测试一下。
apache可以在官网上下:http://httpd.apache.org/已经更新到2.4.4,不过我用的是,2.0版本的,只是测试用无所谓(2.0的系统进程是apache.exe,2.2就变成httpd.exe了。);我下载的是msi文件直接安装,不需要重新编译。生成模块文件(.so文件)还需要apxs。下面是摘录自http://man.chinaunix.net/newsoft/ApacheMenual_CN_2.2new/programs/apxs.html的一段介绍,我就不废话了。

apxs是一个为Apache HTTP服务器编译和安装扩展模块的工具,用于编译一个或多个源程序或目标代码文件为动态共享对象,使之可以用由mod_so提供的LoadModule指令在运行时加载到Apache服务器中。

因此,要使用这个扩展机制,你的平台必须支持DSO特性,而且Apache httpd必须内建了mod_so模块。apxs工具能自动探测是否具备这样的条件,你也可以自己用这个命令手动探测:

$ httpd -l

该命令的输出列表中应该有mod_so模块。如果所有这些条件均已具备,则可以很容易地借助apxs安装你自己的DSO模块以扩展Apache服务器的功能:

$ apxs -i -a -c mod_foo.c
gcc -fpic -DSHARED_MODULE -I/path/to/apache/include -c mod_foo.c
ld -Bshareable -o mod_foo.so mod_foo.o
cp mod_foo.so /path/to/apache/modules/mod_foo.so
chmod 755 /path/to/apache/modules/mod_foo.so
[activating module 'foo' in /path/to/apache/etc/httpd.conf]
$ apachectl restart
/path/to/apache/sbin/apachectl restart: httpd not running, trying to start
[Tue Mar 31 11:27:55 1998] [debug] mod_so.c(303): loaded module foo_module
/path/to/apache/sbin/apachectl restart: httpd started
$ _

其中的参数files可以是任何C源程序文件(.c)、目标代码文件(.o)、甚至是一个库(.a)。apxs工具会根据其后缀自动编译C源程序或者连接目标代码和库。但是,使用预编译的目标代码时,必须保证它们是地址独立代码(PIC),使之能被动态地加载。如果使用GCC编译,则应该使用 -fpic 参数;如果使用其他C编译器,则应该查阅其手册,为apxs使用相应的编译参数。


自己安装的apache 没有apxs,需要自己安装。下载:http://www.apachelounge.com/download/

安装 apxs
1.解压apxs.zip,如C:\apxs
2.打开命令提示符,切换当前目录到解压的路径C:\apxs
3.输入
perl Configure.pl --with-apache2=\Path\to\Apache2 --with-apache-prog=httpd.exe (apache2.0为apache.exe)
其中\Path\to\Apache2需要替换为Apache的安装路径
如果提示perl为不可执行程序的话,需要安装Perl,如Strawberry Perl(自己下一个就行)
4.切换到Apache安装目录下的bin文件夹中,输入apxs,如有解释apxs用法的文本出现,则表明安装完成。
最好将apxs所在目录加到环境变量里面,方便以后编译。

使用apxs在windows下编译模块
1.修改在apache下的build目录中config_vars.mk文件
将CC = gcc 的gcc改为cl.exe ,LD = g 的g 改为link.exe,CPP = gcc-E的gcc-E删掉
2.打开VS2008命令提示符(或者进入命令提示符,执行X:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat)
3.运行apxs -g -n helloworld(helloworld为模块名),会生成一个叫helloworld的目录和模板代码(一般放在命令提示符当前目录下),-g表示generate 产生代码,-n即name,指定模块的名称。此名称会在以后访问url中使用。
4.cd helloworld,进入helloworld目录,你会发现自动生成了一个mod_helloworld.c的源文件。也就是你要添加代码的地方。
5.运行apxs -c -i -a mod_helloworld.c libhttpd.lib 便会生成mod_helloworld.so、mod_helloworld.lib文件。并分别自动放到apache安装目录下的modules和lib目录下。
6.最后一步,需要修改conf目录下的httpd.conf文件,增加

  setHandler helloworld

重启apache服务,打开IE,输入:http://127.0.0.1/helloworld。就可以看到输出内容了。

自此,测试通过,下面进入主题。

二、在c中调用com

原本的com是封装的c++类,但是apache的默认源码是c的,不能识别类文件,如果是用c再重新写一遍实在太费劲了(里面用到很多c++的东西,需要修改很多)。于是想能不能用c++编译module,将源文件强行改为.cpp结果编译不了,尝试修改config_vars.mk文件也没有好的结果,而在《Apache 模块开发指南》中确实指出支持c++的,但是并没有告诉具体方法,在网上也没有找到相关资料(如果哪位大牛做过的,不妨通知小弟,谢谢)。忽然,灵光一闪直接用c调原来的com不就完了嘛。于是又在网上找如何用c调com,可是找到的结果是不能,很多论坛里面都说c调用不了com,这下有点心灰意冷。难道真要用c再写一遍么,我比较懒,不到万不得已真的不想这么做,我不信就找不到办法。在网上找到一篇博客给了我希望,http://www.oldlinux.org/oldlinux/viewthread.php?tid=7865。但是,这片博客里面也没有提到具体调用的方法,虽然给出了源代码,但是并不适用我这个项目。
按照他里面提的方法,自己先写了代码测试一下,(因为自己写的com,可以直接获得头文件,不需要是用midl.exe导出)。
这里,我以项目中用到的另一个com为例(des解密),com头文件
#ifdef __cplusplus
extern "C"{
#endif 

/* Forward Declarations */ 

#ifndef __IToolLab_FWD_DEFINED__
#define __IToolLab_FWD_DEFINED__
typedef interface IToolLab IToolLab;
#endif 	/* __IToolLab_FWD_DEFINED__ */

#ifdef __cplusplus
typedef class ToolLab ToolLab;
#else
typedef struct ToolLab ToolLab;
#endif /* __cplusplus */

#endif 	/* __ToolLab_FWD_DEFINED__ */
...

EXTERN_C const IID LIBID_PXGISLib;

EXTERN_C const IID IID_IToolLab;

#if defined(__cplusplus) && !defined(CINTERFACE)
    
    MIDL_INTERFACE("F12EC292-117C-4C1F-B346-FE2BDB542466")
    IToolLab : public IDispatch
    {
    public:
	...
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE DesDecryptEx( 
            /* [in] */ BSTR bstrInBuffer,
            /* [in] */ BSTR bstrLicense,
            /* [retval][out] */ BSTR __RPC_FAR *bstrOutBuffer) = 0;
       
        ...
    };
    
#else 	/* C style interface */

    typedef struct IToolLabVtbl
    {
        BEGIN_INTERFACE
        
        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )( 
            IToolLab __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);
        
        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )( 
            IToolLab __RPC_FAR * This);
        
        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )( 
            IToolLab __RPC_FAR * This);
	...
        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *DesDecryptEx )( 
            IToolLab __RPC_FAR * This,
            /* [in] */ BSTR bstrInBuffer,
            /* [in] */ BSTR bstrLicense,
            /* [retval][out] */ BSTR __RPC_FAR *bstrOutBuffer);
        ...
       
        END_INTERFACE
    } IToolLabVtbl;

    interface IToolLab
    {
        CONST_VTBL struct IToolLabVtbl __RPC_FAR *lpVtbl;
    };
#ifdef COBJMACROS
#define IToolLab_QueryInterface(This,riid,ppvObject)	\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IToolLab_AddRef(This)	\
    (This)->lpVtbl -> AddRef(This)

#define IToolLab_Release(This)	\
    (This)->lpVtbl -> Release(This)
	...
#define IToolLab_DesDecryptEx(This,bstrInBuffer,bstrLicense,bstrOutBuffer)	\
    (This)->lpVtbl -> DesDecryptEx(This,bstrInBuffer,bstrLicense,bstrOutBuffer)
	...

#endif /* COBJMACROS */


#endif 	/* C style interface */
	...
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IToolLab_DesDecryptEx_Proxy( 
    IToolLab __RPC_FAR * This,
    /* [in] */ BSTR bstrInBuffer,
    /* [in] */ BSTR bstrLicense,
    /* [retval][out] */ BSTR __RPC_FAR *bstrOutBuffer);


void __RPC_STUB IToolLab_DesDecryptEx_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);
	...

#endif 	/* __IToolLab_INTERFACE_DEFINED__ */

EXTERN_C const CLSID CLSID_ToolLab;
#ifdef __cplusplus
class DECLSPEC_UUID("103BBE93-8B87-4284-B5B7-1C787C6F68E3")
ToolLab;
#endif
#endif /* __PXGISLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif
#endif


于是傻不拉几按照c++方式走了一遍
h = CoInitialize(NULL);
if (FAILED(h))
{
	return -1;
}
h = CoCreateInstance(CLSID_ToolLab,NULL,1,IID_IToolLab,(void **)&lab);
if(FAILED(h))
{
	return -1;
}
BSTR res = lab->DesDecryptEx(cipher,L"TJPxdlGisLicense");
lab->Release();
CoUninitialize();

发现根本编译不过去,仔细研究了一些头文件,发现头文件本身就提供了c调用的方法。将interface和class都定义成了struct,并重新定义了一个结构体 IToolLabVtbl,并将其作为IToolLab的一个成员。这里的interface即struct,在中有定义。
    interface IToolLab
    {
        CONST_VTBL struct IToolLabVtbl __RPC_FAR *lpVtbl;
    };
于是,修改代码,下面是写的一个函数的全部代码
//解密
int decrpt_text(char * cipher_text,char **plain_text)
{
	IToolLab* lab = NULL;
	HRESULT h;
	BSTR res,cipher;
	size_t len,num;
	wchar_t * wct = NULL;
	len = strlen(cipher_text)+1;
	wct = (wchar_t *)malloc(len*sizeof(wchar_t));
	mbstowcs_s(&num,wct,len,cipher_text,_TRUNCATE);
	cipher = SysAllocString(wct);
	h = CoInitialize(NULL);
	if (FAILED(h))
	{
		return -1;
	}
	h = CoCreateInstance(&CLSID_ToolLab,NULL,1,&IID_IToolLab,(void **)&lab);
	if(FAILED(h))
	{
		return -1;
	}
	lab->lpVtbl->DesDecryptEx(lab,cipher,L"TJPxdlGisLicense",&res);
	SysFreeString(cipher);
	free(wct);
	wct = res;
	len = wcslen(wct) + 1;
	*plain_text = (char*)malloc(len);
	wcstombs_s(&num,*plain_text,len,wct,_TRUNCATE);
	(*plain_text)[len - 1] = '\0';
	lab->lpVtbl->Release(lab);

	CoUninitialize();
	return 1;
}
编译,还是错误,但是已经不是语法错误。是因为c调用CoInitialize()需要库文件ole32.lib,因为我还用到了SysFreeString()所以还需要库文件oleaut32.lib.重新编译
apxs -c -i -a  -Wl,Zi -Wc,/DEBUG -I "头文件所在目录" mod_helloworld.c ole32.lib oleaut32.lib libhttpd.lib

-c :编译; 
-i :将生成的so文件放到module目录下;
-a:自动增加一个LoadModule行到httpd.conf文件中,以激活此模块,或者,如果此行已经存在,则启用之。
-I:添加自定义目录,这是头文件所在目录.
-Wc:compiler-flags
此选项用于向编译命令  libtool --mode=compile 中附加 compiler-flags ,以增加编译器特有的选项。
-Wl:linker-flags
此选项用于向连接命令  libtool --mode=link 中附加 linker-flags ,以增加连接器特有的选项.
libhttpd.lib是每次编译必须的,会加载apache本身的函数。
-Wl,Zi -Wc,/DEBUG 是启动调试模式,可以在vs2008中跟踪调试。只需要用vs打开源文件,
选择-Debug--attach to process --选择Apache.exe(ID号大的)
如果是2.2以上版本选择httpd.exe(ID号小的))。然后,在IE中输入:http://127.0.0.1/helloworld即可以进到断点中。

自此就把问题都解决了,第一次发博客,有问题还请大家指正,我会细心修改,谢谢大家。

你可能感兴趣的:(apache,Apache,C,apxs,com,dll)