Linux:使用rpcgen實現64位程序調用32位庫函數

摘要:本文介紹使用rpcgent實現64位程序調用32位庫函數的方法,並給出樣例代碼。服務器

個人問題

個人程序運行在64位Linux系統上,須要使用一個從外部得到的共享庫中的函數,這個共享庫是32位的,沒法得到源代碼或64位共享庫。網絡

我對Linux系統和程序的瞭解是:tcp

  1. 64位程序只能調用64位共享庫,32位程序只能調用32位共享庫。
  2. 64位程序不能運行在32位系統上,32位程序能夠運行在64位系統上。

解決這個問題有兩個方法:ide

  1. 把程序編譯爲32位,這樣就可使用32位共享庫。但個人程序也使用了其它64位共享庫,把它們都換成32位共享庫不合適。
  2. 實現一個32位的中間程序(它調用32位共享庫),個人64位程序與32位中間程序進行通訊,從而實現調用32位共享庫。本文介紹這種方法的實現。

實現方法

我要實現兩個程序:函數

  1. 32位的中間程序:它是服務端,接受客戶端的請求,而後調用32位共享庫,並將執行結果返回給客戶端。
  2. 64位的主程序:它是客戶端,經過向服務端發起請求並得到迴應,從而實現調用32位共享庫功能的目的。

我使用的方法是RPC(Remote Procedure Call )。
在Linux中,rpcgen命令行工具使這個工做至關簡單,咱們只須要編寫少許調用庫函數所需的代碼,就能實現上述功能,rpcgent爲咱們自動生成程序間通訊所需的代碼。而且,服務端和客戶端能夠運行在一臺機器上,也能夠運行在網絡的不一樣機器上。工具

關於rpcgen的簡單例子和說明,下面兩篇資料是很好的參考:
http://blog.csdn.net/hj19870806/article/details/8185604
《rpcgen Programming Guide》ui

本文再也不重複參考中的簡單例子,而是給出一個接近實際的例子,其實也很簡單,供各位參考。.net

實現實例

客戶端要調用下面兩個庫函數,這兩個函數有多個入參和返回值。命令行

// 頭文件 retrieve.h 
// 庫文件 libretrieve.so (32位)
int RetrieveByContent(          // 返回: 整數
        char *model,            // 輸入: 字符串  
        char *content,          // 輸入: 字符串 
        int  threshhold );      // 輸入: 整數
    
// 頭文件 classify.h
// 庫文件 libclassify.so (32位)
char* ClassifyByContent(        // 返回: 字符串
        char* model,            // 輸入: 字符串
        char* content       );  // 輸入: 字符串

用RPC語言編寫test.x文件,內容以下:code

struct retrievePara {
    string  model<>;         // <>表示不限制長度的字符串
    string  content<>;
    int     threshold;
};

struct classifyPara {
    string  model<>;
    string  content<>;
};

program TESTPROG {
    version TESTVERS {
        int     RetrieveByContent( retrievePara ) = 1;
        string  ClassifyByContent( classifyPara ) = 2;
    } = 1;
} = 101;

執行下列命令:
| 命令 | 生成文件 | 功能 |
| ----- | ----- | ----- |
| rpcgen test.x | test.h test_xdr.c test_clnt.c test_svc.c | 生成RPC通訊所需源文件 |
| rpcgen -Sc -o test_clnt_func.c test.x | test_clnt_func.c | 生成客戶端樣例程序的源文件 |
| rpcgen -Ss -o test_svc_func.c test.x | test_svc_func.c | 生成服務端樣例程序的源文件 |


修改客戶端 樣例代碼:

#include "test.h"

/************************************************************/
/*                                                          */
/************************************************************/
void testprog_1( char *host )
{
    CLIENT   *clnt;
    int      *result_1;
    retrievePara  retrievebycontent_1_arg;
    char     **result_2;
    classifyPara  classifybycontent_1_arg;

    /************************************************************/
    //
    /************************************************************/
    #ifndef DEBUG
    // 若是參數數據不少(例如字符串很長),則使用 "tcp",由於 "udp" 可能產生錯誤。
    clnt = clnt_create (host, TESTPROG, TESTVERS, "udp");
    if (clnt == NULL) {
        clnt_pcreateerror (host);
        exit (1);
    }
    #endif  /* DEBUG */

    /************************************************************/
    // call RetrieveByContent()
    /************************************************************/
    retrievebycontent_1_arg.modelName   = "RetrieveByContent Modele";
    retrievebycontent_1_arg.content     = "RetrieveByContent Content";
    retrievebycontent_1_arg.threshhold  = 55;

    result_1 = retrievebycontent_1( &retrievebycontent_1_arg, clnt );
    if ( result_1 == (int *) NULL ) 
    {
        clnt_perror( clnt, "call RetrieveByContent failed" );
    }

    printf( "RetrieveByContent return : %d\n", *result_1 );

    /************************************************************/
    // call ClassifyByContent
    /************************************************************/
    classifybycontent_1_arg.modelName = "classifybycontent Model";
    classifybycontent_1_arg.content   = "classifybycontent Content";

    result_2 = classifybycontent_1( &classifybycontent_1_arg, clnt );
    if ( result_2 == (char **) NULL ) 
    {
        clnt_perror (clnt, "call ClassifyByContent failed");
    }

    printf( "ClassifyByContent return : %s\n", *result_2 );        

    /************************************************************/
    //
    /************************************************************/
    #ifndef DEBUG
    clnt_destroy (clnt);
    #endif   /* DEBUG */
}

/************************************************************/
/* 主程序                                                    */
/************************************************************/
int main( int argc, char *argv[] )
{
    char *host = "127.0.0.1";    // 服務端程序所在機器的IP地址
    testprog_1( host );
    exit (0);
}

修改服務端樣例代碼:

#include "test.h"

#include "retrieve.h"   // 共享庫的頭文件
#include "classify.h"   // 共享庫的頭文件

/*******************************************************************/
/* 調用: RetrieveByContent                                          */
/*******************************************************************/
int * retrievebycontent_1_svc( retrievePara *argp, struct svc_req *rqstp )
{
    static int  result;

    // insert server code here
    printf( "Call RetrieveByContent :\n" );
    printf( "Model      : %s\n", argp->modelName );
    printf( "Content    : %s\n", argp->content   );
    printf( "Threshhold : %d\n", argp->threshold );

    int iRet = 0;
    iRet = RetrieveByContent( argp->modelName,  
                              argp->content, 
                              argp->threshold );
    printf( "Return %d\n", iRet );
    result = iRet;
    
    return &result;
}

/*******************************************************************/
/* 調用 ClassifyByContent                                          */
/*******************************************************************/
char ** classifybycontent_1_svc(classifyPara *argp, struct svc_req *rqstp)
{
    static char * result;

    // insert server code here
    printf( "Call ClassifyByContent :\n" );
    printf( "Model    : %s\n", argp->modelName );
    printf( "content  : %s\n", argp->content   );

    char *s = NULL;
    s = ClassifyByContent( argp->modelName, argp->content );
    printf( "Return %s\n", s );
    result = s;

    return &result;
}

在64位系統上編譯客戶端和服務端程序:
gcc -Wall -o test_client test_clnt_func. test_clnt.c test.xdr.c
gcc -m32 -Wall -o test_server test_svc_func.c test_svc.c test_clnt.c test_xdr.c -L . -l retrieve -l classify

用file命令能夠看出:
test_client 是64位程序
test_server是32位程序

運行程序前,系統須要安裝portmap,不然程序不能執行成功。
Ubuntu上的安裝命令是:sudo apt-get install portmap

先運行服務端程序,再運行客戶端程序,根據輸出信息能夠判斷庫函數調用成功。

結束

客戶端能夠調用自定義函數,服務器實現自定義函數,自定義函數實現中可能調用多個32位庫函數,從而爲客戶端提供更高級的功能。

對於上面生成的服務端程序,不能同時運行多個服務端程序,運行第二個會使第一個失效。若是但願同時運行多個服務端,那麼客戶端怎麼找到對應的服務端呢?
能夠利用TESTVERS,只要客戶端和服務端使用相同的值就能對應起來。
客戶端: clnt = clnt_create (host, TESTPROG, TESTVERS, "udp");
服務端:修改編譯生成的文件test_svc.c,使TESTVERS的值與客戶端的相同。

關於Linux上32/64位程序,參見個人另外一篇文章:
《Linux:32/64位程序(應用程序、共享庫、內核模塊)》

相關文章
相關標籤/搜索