1. 程式人生 > >[翻譯]-Linux上C++類庫的動態載入

[翻譯]-Linux上C++類庫的動態載入

摘要:本文是翻譯文章,主要介紹了執行時過載C++類庫的技術,包括了Linux上的庫動態載入介面、C++類庫的動態載入技術點及實現、自動載入技術等。最後給出了兩個應用案例及相關的原始碼。

關鍵字:動態載入,C++類,Linux

原文連結:http://porky.linuxjournal.com:8080/LJ/073/3687.html

推薦:

(翻譯)http://hi.baidu.com/clivestudio/item/fec21be454fd93aac00d75bf

1. 前言

一種可以讓開發者進行更靈活設計的技術。

Linux作業系統下的開發平臺提供了很好的環境:自帶豐富的測試工具,健壯的操作環境。令Linux引以為豪的是,它能適應各種程式語言。下面的說法,相信並不為過:對Linux開發者來,在所有的編譯語言中,選擇C作為開發語言是最多的。由此,像C++這樣的語言似乎經常不在Linux開發者的討論範圍內。

類庫的動態載入技術可以讓開發者的設計更靈活。類的動態載入可以使實現更具擴充套件性,而不犧牲魯棒性。

本文將設計一個簡單的應用,這個應用只有一個類:用於繪圖包的形狀類。我們將會看到,類的動態載入技術可以讓我們平滑地擴充套件功能——增加新的形狀類而不修改原有程式碼。

2. 多型

動態載入類的基本思想就是類的多型性。任何熟悉C++的人對此都不會陌生,這裡就只簡潔地說明一下。總的來說,多型,就是子類物件可以像父類物件的那樣表現。這就是OOP(object-oriented programming,面向物件)中眾所周知的“is a”關係。比如,下面的程式碼段中,類circle“is a”基類shape

的子類(見列表1),因而物件my_circle可以像shape物件一樣操作shape的成員函式draw

列表1 基類shape的標頭檔案

class shape {
public:
  void draw();
};
class circle : public shape { };
 
int main(int argc, char **argv){
  circle my_circle;
  my_circle.draw();
}

這包括所有的常規優點(如程式碼複用)以外,多型性的真正優勢在於draw宣告為虛擬函式或者純虛擬函式的時候,如下:

class shape{
public:
  virtual void draw()=0;
};
class circle : public shape {
public:
  void draw();
}

這裡circle定義了自己的draw函式,以確保circle物件有正確的行為。同樣,我們可以定義一些新的shape子類,並重寫draw函式。這樣,全部的子類都實現了shape的介面,進而可以建立一系列表現不同的物件,這些物件都呼叫同一個方法(呼叫draw成員函式)。下面是例子:

shape *shape_list[3];   // the array that will
                            // pointer to our shape objects
shape[0] = new circle;  // three types of shapes
shape[1] = new square;  // we have defined
shape[2] = new triangle;
for(int i = 0; i < 3; i++){
  shape_list[i].draw();
}

呼叫draw函式時,並不需要知道列表中物件的其他資訊;C++會正確地呼叫對應的draw函式。這種強大的技術可以讓我們的設計更靈活。現在我們可以通過繼承shape來實現我們期望的行為。這裡的關鍵是我們可以把介面(shape的原型)和實現分開。

雖然此技術相當強大,但如果我們想新增新的繼承函式的話,我們仍不得不重新編譯程式碼。如果能夠在執行時載入新類,那將會非常方便。而且,使用我們程式碼庫的人完全可以提供新的形狀類(重寫draw函式)而不必需要我們的原始程式碼。好訊息是,這是可能的,這也是本文討論的主題。

3. dlopen介面和類的動態載入

雖然Linux下還沒有直接的機制可以在執行時載入C++類,但是有一個直接在執行時載入C庫的機制:dl函式dlopendlsymdlerrordlclose。這組函式提供了訪問動態連結器ld的方法。完整的說明可以參考這些函式的man手冊,這裡僅簡要說明。

函式原型如下:

void *dlopen(const char * filename, int flag);
void *dlsym(void *handle, char*symbol);
const char *dlerror();
int dlclose(void *handle);

dlopen函式通過檔名filename開啟so檔案,檔案中的符號通過dlsym函式讀取。引數flag可以取下面的值:RTLD_LAZYRTLD_NOW。如果flag設定為RTLD_LAZY,那麼dlopen會不解析任何符號就返回。如果flag設定為RTLD_NOW,那麼dlopen會嘗試解析檔案中所有未定義的符號。如果出現解析錯誤,函式呼叫失敗,返回NULL。dlerror解析失敗的原因。dlsym函式用於讀取庫中函式(或其他符號)的指標。handle是指向被引用項的指標,symbol是被引用項在實際儲存檔案中的字串名字。

假如可以通過這些函式訪問C庫裡的函式,要樣才能利用它們訪問C++庫?要達到這個目的,有幾個問題需要解決。一個是必須能夠定位到目標庫函式的符號。由於C和C++檔案中儲存的符號是不一樣的,導致解決這個問題會比看起來麻煩一些。另一個問題是要如何建立目標類的物件?最後,訪問這些物件的便捷方法又是什麼?下面將反過來回答這三個問題。

因為並不知道動態載入的類的型別,在程式碼中應該如何訪問?答案的訣竅在於前面提到的多型。可以通過基類提供的公用介面來使用新類的功能。延續上面的例子,新的shape類會重寫draw函式,並在實際的物件中正確呼叫自己的draw函式。

好,現在可以通過基類的指標訪問子類的物件了。怎樣在開始的地方建立這些物件?除了知道它們可以呼叫shape的介面外,並不知道目標類的其他相關資訊。舉個例子,假如動態地載入了一個庫,這個庫中有個類hexapod,如果不能提前知道類名,不能這樣寫:

shape *my_shape = new hexapod;

解決辦法是,主程式不負責建立物件,至少不是直接建立。庫中提供的shape子類必須提供一個建立物件的方法。這個可以用工廠類來實現,就像工廠模式那樣,或者直接呼叫一個函式建立。為了簡化說明,這裡直接用一個函式來建立。所有形狀類的函式返回值都一樣:

shape *maker();

maker函式沒有引數,返回構建成功的物件指標。針對前面的hexapod類,maker函式如下:

shape *maker(){
  return new hexapod;
}

使用new來建立新的物件是絕對合法的,因為maker函式和hexapod定義是在同一個檔案中。

現在,先用dlopen載入一個庫,然後用dlsym得到對應類的maker函式指標。使用這個指標建立對應類的物件。例,假如想要動態連結庫libnewshapes.so,並應用其中的hexapod類。可以像這樣子進行:

void *hndl =dlopen("libnewshapes.so", RTLD_NOW);
if(hndl == NULL){
  cerr << dlerror() << endl;
  exit(-1);
}
void *mkr = dlsym(hndl,"maker");

指向maker的指標必須是void*型別的,因為dlsym返回的就是這個型別。現在,可以通過呼叫mkr建立hexapod類的物件。

shape *my_shape = static_cast<shape*()>(mkr)();

呼叫時要記得將mkr的返回強制轉換成shape*型別。

讀到這裡,可能有讀者發現程式碼有個問題:呼叫dlsym可能失敗了,無法解析“maker”。這個問題根源在於C++為了實現過載,把函式名改了,因而庫中的maker函式可能變了。可以通過解析改名的規則來找到改過名的符號,不過有一個更簡單的方法。只需要用extern "C"標識來告訴編譯器使用C風格的連結包,如下所示:

列表 2. 類circle的標頭檔案和原始檔

#ifndef __CIRCLE_H
#define __CIRCLE_H
#include "shape.hh"
class circle : public shape {
public:
  void draw();
};
#endif // __CIRCLE_H
 
 
#include <iostream> #include"circle.hh"
void circle::draw(){
  // simple ascii circle
  cout << "\n";
  cout << "     ****\n";
  cout << "    *      *\n";
  cout << "   *        *\n";
  cout << "   *        *\n";
  cout << "   *        *\n";
  cout << "    *      *\n";
  cout << "     ****\n";
  cout << "\n";
}
extern "C" {
shape *maker(){
  return new circle;
}
class proxy {
public:
  proxy(){
     // register the maker with the factory
     factory["circle"] = maker;
  }
};
// our one instance of the proxy
proxy p;
}

4. 自動註冊

可以將maker函式載入到一個專門存放maker函式的陣列中。不過,用更靈活的關聯陣列來儲存maker函式在某些情況下或許會更有用。可以用標準模板庫(Standard Template Library , STL)中的map類,通過鍵關聯到maker函式,就能根據鍵值來訪問。例如,給各個類命名,並通過名字來呼叫合適的maker函式。本例中,建立如下對映:

typedef shape *maker_ptr();
map <string, maker_ptr> factory;

現在,要建立一個指定的形狀時,可以通過形狀的名稱呼叫合適的maker函式:

shape *my_shape = factory[

還可以把這種技術擴充套件得更靈活。載入類的maker函式時,與其顯式地賦值,何不讓類的設計者來做這個事?稍微費點心思,讓工廠方法自動註冊maker函式,這樣能讓類設計者自由確定名字。(這裡有兩個忠告:所有的鍵型別必須相同,絕對不能出現重複的鍵。)

完成這個工作的第一步是,每個形狀庫必須包含maker函式,並且每次開啟時,都呼叫這個函式。(通過dlopen的man手冊可以知道,如果庫定義匯出了一個_init函式,那麼庫每次開啟的時候都會呼叫它。這看起來是一個註冊maker函式的好地方,不幸的是目前在linux系統上不能正常工作。問題在於一個標準的連結物件檔案crt.o匯出了一個__init函式,這就導致了衝突。)至此,我們延續了這個函式名,這個機制仍正常工作。我個人贊成摒棄那種方法,更希望採用一開啟庫就能註冊maker函式的方法。這種方法就是Jim Beveridge引入的著名的“自注冊物件”(參考文獻)。

可以建立一個代理類單例用於註冊maker函式。註冊過程會在類的構建函式中進行,因而只需要建立一個代理類例項來處理這件事。這個類的原型如下:

class proxy {
public:
  proxy(){
     factory["shape name"] = maker;
  }
};

這裡假設工廠例項是主程式裡定義匯出的全域性map例項。使用gcc/egcs,可以設定rdynamic選項來強制主程式匯出能夠用dlopen函式載入的符號表。

下一步,只定義一個代理例項:

proxy p;

現在,開啟庫時,傳引數RTLD_NOW給dlopen函式,p就會被例項化,並註冊目標的maker函式。如果想建立一個circle,可以這樣呼叫circle的maker函式:

shape *my_circle =factory["circle"];

自動註冊處理是非常強大的,利用這個功能設計出的主程式也能支援資訊缺乏的類。例如,主程式動態載入了某個形狀庫之後,通過工廠例項裡註冊的鍵,就能夠建立一個形狀選擇選單。使用者從選單列表中選擇"circle",程式就立刻能呼叫正確的maker函式。這裡,circle類已經支援shape的API,並且maker函式也正確定義了,所以主程式根本不需要其它的資訊。

列表 3. 類Square的標頭檔案和原始檔

#ifndef __SQUARE_H
#define __SQUARE_H
#include "shape.hh"
class square : public shape {
public:
  void draw();
};
#endif // __SQUARE_H
 
 
#include <iostream>
#include "square.hh"
void square::draw(){
  // simple ascii square
  cout << "\n";
  cout << "   *********\n";
  cout << "    *       *\n";
  cout << "    *       *\n";
  cout << "    *       *\n";
  cout << "    *       *\n";
  cout << "   *********\n";
  cout << "\n";
}
extern "C" {
shape *maker(){
  return new square;
}
class proxy {
public:
  proxy(){
     // register the maker with the factory
     factory["square"] = maker;
  }
};
// our one instance of the proxy
proxy p;
}

到這裡,把列表1到列表5放到一起,相關的概念已經清晰了。列表1的shape類是所有形狀類的基類。列表2和列表3分別是circle類和square類的原始碼,它們支援庫的動態載入。

列表 4. 主程式程式碼,呼叫可以動態載入的類circle和類square

#include <iostream>
#include <map>
#include <list>
#include <vector>
#include <string>
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include "shape.hh"
// size of buffer for reading indirectory entries
static unsigned int BUF_SIZE = 1024;
// our global factory for making shapes
map<string, maker_t *,less<string> > factory;
int main(int argc, char **argv){
  FILE *dl;   // handle to readdirectory
  char *command_str = "ls *.so"; // command
               // string to get dynamic libnames
  char in_buf[BUF_SIZE]; // input buffer for lib
                          // names
  list<void *> dl_list; // list to hold handles
                               // for dynamiclibs
  list<void *>::iterator itr;
vector<string> shape_names;  // vector of shape
               // types used to build menu
  list<shape *> shape_list; // list of shape
               // objects we create
  list<shape *>::iterator sitr;
map<string, maker_t *,less<string> >::iterator fitr;
  // get the names of all the dynamic libs (.so
              // files) in the current dir
  dl = popen(command_str, "r");
  if(!dl){
     perror("popen");
     exit(-1);
  }
  void *dlib;
  char name[1024];
  while(fgets(in_buf, BUF_SIZE, dl)){
     // trim off the whitespace
     char *ws = strpbrk(in_buf, " \t\n");
     if(ws) *ws = '\0';
     // append ./ to the front of the lib name
     sprintf(name, "./%s", in_buf);
     dlib = dlopen(name, RTLD_NOW);
     if(dlib == NULL){
        cerr << dlerror() << endl;
        exit(-1);
     }
     // add the handle to our list
     dl_list.insert(dl_list.end(), dlib);
  }
  int i = 0;
  // create an array of the shape names
  for(fitr=factory.begin(); fitr!=factory.end();
       fitr++){
     shape_names.insert(shape_names.end(),
       fitr->first);
     i++;
  }
  int choice;
  // create a menu of possible shapes to create and let the user make some
  while(1){
     i = 1;
     for(fitr=factory.begin();
           fitr!=factory.end(); fitr++){
        cout << i << " - Create " << fitr->first
            << endl;
        i++;
     }
     cout << i << " - Draw created shapes\n";
     i++; i
     cout << i << " - Exit\n";
     cout << "> ";
     cin >> choice;
     if(choice == i){
        // destroy any shapes we created
        for(sitr=shape_list.begin();
              sitr!=shape_list.end();sitr++){
            delete *sitr;
        }
        // close all the dynamic libs we opened
        for(itr=dl_list.begin(); itr!=dl_list.end(); itr++){
           dlclose(*itr);
        }
        exit(1);
     }
     if(choice == i - 1){
        // draw the shapes
        for(sitr=shape_list.begin();
              sitr!=shape_list.end();sitr++){
            (*sitr)->draw();
        }
     }
     if(choice > 0 && choice < i - 1){
        // add the appropriate shape to the shape list
        shape_list.insert(shape_list.end(),
            factory[shape_names[choice-1]]());
     }
  }
}

列表4是可擴充套件的動態載入庫主程式。程式掃描當前目錄下的所有so檔案(庫檔案)並開啟。這些庫會用主程式提供的全域性工廠物件註冊自身的maker函式。然後,主程式根據庫註冊過的名字動態地建立形狀選單給使用者。通過選單,使用者可以建立各種形狀,畫形狀,或直接退出程式。列表5是用於編譯專案的Makefile。

列表 5.Makefile

CC = g++
LIBS = -ldl
.cc .o:
  $(CC) -ggdb -c $<
default:
  make testdcl
OBJS = testdcl.o
testdcl: testdcl.o
  $(CC) -rdynamic -o testdcl testdcl.o $(LIBS)
libcircle.so:  circle.o
  g++ -shared -Wl,-soname,libcircle.so -o libcircle.so circle.o
libsquare.so:  square.o
  g++ -shared -Wl,-soname,libsquare.so -o libsquare.so square.o
all: testdcl libcircle.so libsquare.so
clean:
  rm -f *.so *.o testdcl

5. 應用案例

最近,本人有用到了這個技術的兩個案例。第一個案例,開發移動物體的模擬器。需要在不能訪問主要原始碼的情況下,讓使用者新增移動物體的新型別。要完成這個功能,定義了一個entity基礎類,這個類提供了模擬移動物體的所有介面。一個簡化版的entity定義如下:

class entity {
private:
  float xyz[3];  // position of theobject
public:
  activate(float)=0; // tell the object to move
  render()=0;  // tell the object todraw itself
};

所有的entity都至少有三維座標,都可以畫出自身。大部分entity除了位置之外還有其他的狀態變數,同時也不僅僅只有activate函式和render函式,但是這些並不能通過entity介面訪問。

可以根據使用者期望的動作來定義新的entity型別。執行時,程式載入子目錄Entity下的所有庫,使它們在模擬的過程能被呼叫。

第二個案例是最近的專案,建立一個能載入/儲存多種圖片格式的庫。這個庫要可擴充套件的,因此我們新建了一個image_handler基類用於載入和儲存圖片。

class image_handler{
public:
  virtual Image loadImage(char *)=0;
  virtual int saveImage(char *, Image &)=0;
};

image_handler有兩個公有函式,分別用於載入和儲存圖片。Image類是庫中圖片的基本型別,可以訪問圖片的資料成員和一些基本的圖片操作函式。

在這個案例中,不關心不同型別的image_handler類的多個物件,只要求image_handler類物件可以載入和儲存對應型別的圖片。因而只需在庫中建立這個handler的單例,而不是給每個handler註冊一個maker函式,建立的這個handler指標會註冊到一個全域性對映裡。這個全域性對映也不是工廠物件,更像是一個普通的圖片載入/儲存操作器。這裡把檔案字尾(tiff, jpg等)作為鍵。一種圖片格式可能會有多種檔名字尾(如tiff,TIFF),因此每個handler可能會在全域性對映中註冊多次,一個字尾一次。

解析出檔案字尾後,這個庫能夠讓主程式簡單地通過來呼叫正確的處理函式進行圖片的載入和儲存。

使用這個庫,主程式可以根據檔案字尾呼叫適當的處理函式載入或儲存圖片。

map <string, handler,less<string>> handler_map;
char *filename ="flower.tiff";
char ext[MAX_EXT_LEN];
howEverYouWantToParseTheExtensions(filename,ext);
// after parsing"flower.tiff" ext = "tiff"
Image img1 =handler_map[ext]->loadImage(filename);
// process data here
handler_map[ext]->saveImage(filename,img1);

6. 結論

利用類的動態載入技術可以實現更具擴充套件性更強健的程式碼。只要設計出考慮周全的類,運用動態載入類的技術,就能夠把擴充套件程式碼的實用方法提供給使用者。

參考文獻

[1].             Design Patterns:Elements of Reusable Object-Oriented Software, E. Gamma, R. Helm, R. Johnson,J. Vlissides and G. Booch, Addison-Wesley, Reading, Massachusetts.

[2].             "Self-RegisteringObjects in C++", J. Beveridge, Dr. Dobb's Journal, pp. 38-41, August 1998,Vol. 23, Issue 8.

作者介紹

James Norton spent most of his adult life avoiding real life byhiding out in school, first at Florida State University and then at TulaneUniversity. The good life ended when he was awarded a Ph.D. in ElectricalEngineering through what could only have been some sort of clerical error. Hecurrently does research and systems development for Newsreal, Inc. He can bereached by e-mail at [email protected]

相關推薦

[翻譯]-LinuxC++動態載入

摘要:本文是翻譯文章,主要介紹了執行時過載C++類庫的技術,包括了Linux上的庫動態載入介面、C++類庫的動態載入技術點及實現、自動載入技術等。最後給出了兩個應用案例及相關的原始碼。 關鍵字:動態載入,C++類,Linux 原文連結:http://porky.lin

LinuxC語言標準數學函式的引用

       eclipse安裝了CDT外掛之後就可以在上面編輯、編譯、連結、執行C/C++程式了,但是不同於gcc編譯器的純命令列操作,eclipse上基本上都是通過圖形化介面實現的,只需要進行簡單的設定就可以實現特定的功能。函式庫通常可以靜態連結庫(*.a檔案)和動態

C#導入c++ dll報找不到dll文件 masm32調用c++

dll sca masm32 ++ 時也 類型 dumpbin exports 另一個   最近需要在C#下調用一個c++ dll庫,不管怎樣dllimport就是報錯找不到該dll文件,路徑、函數名稱、參數、dllimport參數逐個檢查確認無誤也無濟於事,無奈想用其他語

由於C++版本不同導致的OpenCV編譯鏈接錯誤

c++類 庫文件 它的 int down error: string ray 無法 太長不看版:GCC4和GCC5使用的C++標準庫下,string的名字不一樣,導致鏈接錯誤。 之前在Ubuntu下使用OpenCV的時候一切正常。後來再次編譯的時候,連接器提示有些庫函數找不

C++-動態內存分配 大發彩_票平臺開發

lin 原型 def 顯示 指向 自己的 clu 增加 sse 大發彩_票平臺開發 地址一:【hubawl.com】狐霸源碼論壇地址二:【bbscherry.com】 類和動態內存分配 動態內存和類 C++在分配內存時是讓程序在運行時決定內存分配,而不是在編譯時決定。

c++stl

把c++視訊全部又看了一遍,總結了stl類庫 總結如下: vector: vector向量中,可以用[x][y]表示第x-1位裡的第y-1個元素; s[i] //  直接以下標方式訪問容器中的元素 s.front() //  返回首元素 s.back() // 

Nuget釋出屬於自己的C#

前期必備 Visual Studio 2017 要安裝 nuget.exe CLI,從 nuget.org 官網下載,將 .exe 檔案儲存到合適的資料夾 申請 API 金鑰 登入你的 nuget.org 帳戶,或建立一個帳戶(如果你還沒有帳戶)。

linux下封裝函式——動態.so和靜態.a(程式碼實現及連結方式)

在linux環境下的連結庫分為靜態連結庫(.a庫)和動態連結庫(.so庫),其作用是把C程式編譯好做成一種可執行連結檔案,主程式檔案呼叫這些程式的函式介面是可以使用a庫或so庫,在主程式中只需要include含有庫中提供的函式介面宣告的標頭檔案即可。所以學會如何

Java呼叫C#

原帖地址:https://bbs.csdn.net/topics/390624108   總體分三步走: 準備一個 C# 類庫 (dll) 編寫一個java 類 編譯 java 類並打包成jar,即可

c語言——linuxc開發編譯tips

vim編輯 gcc編譯 gdb除錯 vim小技巧:shift+v再按下箭頭可以選中多行 y複製多行,d刪除多行 c語言執行步驟: 預處理->編譯->連結 預處理gcc -E main.c -o main.i(標頭檔案替換,巨集替換,刪除註釋)

C#框架程式設計動態載入模組(一)

本文系原創,轉載請註明出處: 在之前分享的部落格中,我已經實現了一個靜態載入的小框架,這個框架的模組已經在程式碼中確定,一旦生成程式,模組將無法改變。但在實際應用的大型專案中,我們更傾向於使用動態載入模組的框架,這樣對於專案的移植更加靈活和方便,因此今天我就來實現這個效

C#框架程式設計動態載入模組(二)

本文系原創,轉載請註明出處: 在上一篇部落格中,我完成了介面的設計部分,下面我接著來講具體的程式碼實現。先來看模組配置頁面的實現,看程式碼: private void LoadItem() { string sq

linux刪除mysql中的所有表

刪除庫中所有的表: 1:進入資料庫    use kk222; 2:使用以下命令後,會得到很多刪除語句(直接執行得到的刪除語句)    select concat('drop table ',table_name,';') from information

【譯】12. Java反射——動態載入和重新載入

博主最近比較忙,爭取每週翻譯四篇。等不急的請移步原文網頁。  =====================================================================================

Ext4.2.1學習歷程之二:自定義動態載入

原文出處   http://blog.itpub.net/28562677/viewspace-1067421/ -------------------------------------------------------------- 在些extjs類的定義時有必要簡單

linux安裝libpng以及zlib

hello ,大家好,我是jordy,一隻空著的杯子;歡迎各位朋友光臨我的部落格,多多溝通 ,我的QQ :   1760282809   363232564(一)下載libpng的庫:我下載的是libpng-1.5.8 的安裝包libpng-1.5.8.tar.xz 的包注意

C#:ini檔案操作

C#類庫 ——ini檔案操作類 1.類庫介紹     在開發應用軟體時,ini檔案常用於軟體的相關配置,以下為ini檔案的相關結構及示例;ini檔案具有節(section)和鍵(key)兩個層級,節用”[]”包含,然後下一行為對應鍵名以及鍵值,

linuxc++ 標準的安裝

1 install   sudo apt-get install libstdc++6 libstdc++6-4.2-doc   sudo apt-get install stl-manual 2. using   man c++intro   man std

Native C++藉助CLR動態載入並呼叫.NET程式集

Native C++程式碼和託管.NET程式碼互操作並不是什麼難事, 資料也很多, 但是有些方法複雜繁瑣, 本文介紹了一種簡單的可行、支援動態載入的基於CLR的互動方法. 1.首先是動態載入目標程式集和類: try { auto assembly = Ass

JNI方法呼叫C++

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!