「白痴版」Mathematica連接C++教程

「白痴版」Mathematica連接C++教程

來自專欄 Mathematica 還能這樣玩34 人贊了文章

很沒用的前言

誒,坑……這個圖片生成完了之後沒看質量就刪掉了源碼……發現壓縮比率太高,故渣清了……湊活看吧……

為啥我說這個是「白痴」版本呢,因為我呢很懶,東西吧都是能用都行,瞎配置來配置去的我是頭大幹不來。以前也只是知道WSTP這個東西,似乎很厲害,可以連各種各樣的程序,但是不知道確切的如何使用,看過一些案例,兩端的配置和連接啥的都是佶屈聱牙,非人類可忍受的,故完全沒想過去玩。

但最近刷Tutorial Collection,有一本System Interfaces and Development:

Wolfram Mathematica Tutorial Collection: Systems Interfaces and Deployment?

library.wolfram.com

裡面有一塊講MathLink(老版本的WSTP)。我:「誒,為啥感覺超直接了,一點都不複雜啊!」故蠢蠢欲動。

這裡再廢話一句,Tutorial Collection真是神器,全刷完一遍真的就在Mathematica的很多很多重要方面都很強了……

但是自己配置了半天都沒配置起來,摸索了半天才搞出一個很方便的用Visual Studio秒速配置和鏈接的方法。故此文即來分享一下,畢竟Mathematica假如能很方便的鏈接C/C++,高性能計算和方便的各種處理兩個優點就能結合起來,我還能用十年了!這麼好的東西別浪費了啊!

正文開始

前面廢話好多,正文開始,別看長,大多是廢話,實際操作內容兩分鐘就搞定了……大家別怕蛤~

前期工作:

  1. 確認安裝了 Mathematica & Microsoft Visual Studio。版本基本無所謂,別太老了,假如老版本的Mathematica簡單的把所有的WSTP換成MathLink版本就好了,後面我也會在各種需要改變的地方用括弧說明的。
  2. 新建一個Mathematica文檔,和一個VS項目(項目選擇C/C++命令行程序)文件夾配置其實無所謂,但是我這裡設置成二者放在同一文件夾 MathLink Program 下,然後該文件夾下是Mathematica文件 mmaprog.nb 和 test 這個VS項目文件夾。

先不管Mathematica文件,那是最後的事情,在後面一段中主要是如何配置Visual Studio:

3. 在Mathematica裡面運行如下代碼打開一個文件夾,一直打開這個文件夾,一直會用哦~

SystemOpen[$InstallationDirectory <> SystemFiles\Links\WSTP\DeveloperKit"]

因為我們等下寫32位的程序,且我在Windows下(別的系統就進對應系統名的文件夾下就可以),所以進入Windows文件夾(如果64位就進入Windows-x86-64),這個然後在文件夾底下找到SystemAdditions->wstp32i4,拷貝到C:WindowsSystem32下就可以了(如果是64位則拷貝到C:WindowsSysWOW64)。

(假如是MathLink則是:SystemOpen[$InstallationDirectory <> SystemFiles\Links\MathLink\DeveloperKit"] 其它同理)

4. 配置VS環境(別的IDE其實我猜也沒啥大不同……)進入剛剛DeveloperKit文件夾下的CompilerAdditions項,將裡面的wsprep.exe(mprep.exe),wstp.h(mathlink.h)和wstp32i4m.lib(ml32i4m.lib)拷貝到VS項目文件夾(不是解決方案文件夾!)下,就是有別的源碼的那個地方。然後在該文件夾下新建f.tm和tmgen.cpp。

萬事俱備!打開VS項目,把f.tm, tmgen.cpp, wstp32i4m, wstp.h一股腦往解決方案資源管理器裡面拽就是了。完成後右鍵 項目->屬性->生成事件->生成前事件->命令行 然後輸入:

wsprep f.tm -o temp.tmp echo ^#include^ ^"stdafx^.h^" > tmgen.cpp type temp.tmp >> tmgen.cpp del temp.tmp

(假如是別的IDE可以直接寫 wsprep f.tm -o tmgen.cpp ,有預編譯頭的才需要寫成上述形式)(MathLink就把wsprep改成mprep就可以了)

好!配置完全完成了!現在可以開始寫代碼了!

代碼也很簡單,其實就是四件事:

a. C端寫好要用的函數代碼

b. 寫好介面代碼f.tm

c. 編譯出.exe

d. Mathematica 端Install,開用!

我們用一個最簡單的例子:輸入x是整數,輸出它的兩倍——也是個整數,函數名就叫f好了。

a. C端

#include "stdafx.h" //這個是預編譯頭,沒有預編譯頭的話刪掉就行#include "wstp.h"int f(int x) {return 2*x;}int main(int argc, char *argv[]) {return WSMain(argc, argv);}

(假如是MathLink就是mathlink.h,和MLMain,後面所有WS開頭的函數都是替換成ML開頭即可,就不再贅述了。)

對你沒看錯!WSTP的C埠配置就是這麼簡單!根本沒有那些隨便一寫就是幾百行的介面代碼的繁瑣感!不禁感嘆,還是Mathematica一貫的風格啊!

b. 介面f.tm

有人想,這個是會很噁心了吧,不!也很簡單!

:Begin::Function: f:Pattern: f[x_Integer]:Arguments: {x}:ArgumentTypes: {Integer}:ReturnType: Integer:End:

簡單來說:

1. :Function: 聲明函數名稱

2. :Pattern: 聲明Mathematica端的模式——這個可以是任意樣子的Mathematica Pattern哦!

3. :Arguments: 有什麼參量

4. :ArgumentTypes: C端的參量類型

5. :ReturnType: C端返回類型

這些有了就可以了!別的錦上添花的後面說,先讓大家用起來是正道!

c. Mathematica端:

更簡單。。。

Install[exe所在目錄],按照我的文件夾配置的話就是:

Install[NotebookDirectory[] <> "mltest\Release\mltest.exe"]

即可正常使用~(注意自己調整一下Release還是Debug文件夾哦~)

我們來測試一下:

f[1]

=>2

great!

好,到此為止你就會寫最簡單的Mathematica單向調用C的程序了,然後咱們來討論下稍微複雜點的內容:

其實這段我很想寫一句:其實直接查那本參考材料就可以,不過我認為簡單常用的內容我還是寫下吧:

C端:萬一我想傳列表,傳Mathematica表達式怎麼辦???

f.tm介面:能不能更給力一點?XD

C:當然可以,輸入的時候就列表的形式是int*, long兩個組合成一個整數列表,輸出時使用函數MLPutInteger32List(stdlink,int*a,length)

即可,對應的f.tm中輸入類型要寫成IntegerList,輸出類型寫成Manual。輸出表達式的時候使用函數MLEvaluateString。

樣例:輸入一個整數列表,乘二後輸出。

f.tm如下:

:Begin::Function: f:Pattern: f[x:{___Integer}]:Arguments: {x}:ArgumentTypes: {IntegerList}:ReturnType: Manual:End:

C代碼如下:

#include "stdafx.h" //這個是預編譯頭,沒有預編譯頭的話刪掉就行#include "wstp.h"#include <cstdlib>void f(int* x, long len){ int* tmp = (int*)calloc(len,sizeof(int)); int sum = 0; for (int i = 0; i < len; i++) tmp[i] = x[i] * 2; WSPutInteger32List(stdlink, tmp, len);}int main(int argc, char *argv[]){ return WSMain(argc, argv); }

f[{1,2,3,4}]

=>{2,4,6,8}

這裡只有一點需要額外注意,只要返回格式不是啥簡單的int或者double,都直接寫Manual就對了,輸入假如需要輸入表達式啥的複雜內容也類似。

是不是超簡單超方便了!

表達式的話就用字元形式輸出或者拆成一個個頭和一個個函數體輸出,也很簡單~一般後者更加合適。這裡不再給出完整代碼了,給出C的片段:

WSPutFunction(stdlink,"List",2);WSPutInteger32List(stdlink,listpointer1,length)WSPutReal64(stdlink,1.0)

這裡還有一個Tutorial Collection中給出的小trick,假如想寫入一個未知長度的列表怎麼辦?——那就用這種方式寫:List[Sequence[1, Sequences [2,Sequence[]]]]

f.tm介面:別的重要的關鍵字其實也就是 :Evaluate: ,簡單來說,定義函數前後還可以干點事情例如最一般的,寫函數包的時候都會用

:Evaluate: BeginPackage["test`"]:Evaluate: f::usage="test function.":Evaluate: Begin["`Private`"]:Begin::Function: f:Pattern: f[x:{___Integer}]:Arguments: {x}:ArgumentTypes: {IntegerList}:ReturnType: Manual:End::Evaluate: End[]:Evaluate: EndPackage[]

當然還有個東西值得提一嘴,就是關於例如不定長函數參數啊,複雜輸入內容啊等等的處理,簡單來說就是把參數表裡的東西空著,在參數列表裡面加入一個Manual,再後期Get進來罷了,函數呢就是把前面的Put改成Get。說的混亂,代碼一看便知:

樣例:輸入f[乘數,函數名[整數參量組]]輸出函數名的每個字元ASCII碼+1作用於參量組*乘數上:

f[3,test[1,2]]=>uftu[3, 6]

f.tm如下:

:Begin::Function: f:Pattern: f[x_Integer,expr_]:Arguments: {x,expr}:ArgumentTypes: {Integer,Manual}:ReturnType: Manual:End:

C代碼如下:

#include "stdafx.h" //這個是預編譯頭,沒有預編譯頭的話刪掉就行#include "wstp.h"#include <cstdlib>void f(int mult){ const char* sym = (const char*)calloc(10, sizeof(int)); int count = 0; WSGetFunction(stdlink, &sym, &count);//get function name & argument count int* num = (int*)calloc(count, sizeof(int)); for (int i = 0; i < count; i++) { WSGetInteger32(stdlink, &num[i]);//get arguments num[i] *= mult; } char symv[10] = ""; for (int i = 0; i < 10; i++)//ASCII碼+1 { if (sym[i] == ) break; symv[i]=sym[i]+1; } WSPutFunction(stdlink, symv, count);//output function name for (int i = 0; i < count; i++) WSPutInteger32(stdlink, num[i]);//output arguments WSReleaseSymbol(stdlink, sym);}int main(int argc, char *argv[]){ return WSMain(argc, argv); }

即使是這麼複雜的例子在介面上所需要畫得時間也是很很很短啊!基本上無痛結合Mathematica和C/C++,完美!

寫到這裡突然發現一個很重要的問題……就是雖然我標題寫的是C++,用的是VS中的C++,但是寫的都是C代碼……為了展現我還是有基本的C++知識的,而非瞎胡扯,我就裝模作樣的寫一個智障類來……展示……下……#捂臉#

案例:累加器,初始值為0,每次輸出為之前所有輸入之和。輸入233則清0。

f.tm:

:Begin::Function: f:Pattern: f[x_Integer]:Arguments: {x}:ArgumentTypes: {Integer}:ReturnType: Integer:End:

C++代碼(嗯這次是C++):

#include "stdafx.h" //這個是預編譯頭,沒有預編譯頭的話刪掉就行#include "wstp.h"class stupid{private: int val = 0; int add(int); int clr();public: int overall(int);};int stupid::add(int x) { return (val += x); }int stupid::clr() { return (val = 0); }int stupid::overall(int x){ return x != 233 ? add(x) : clr(); }stupid stupidity{};int f(int x) { return stupidity.overall(x); }int main(int argc, char *argv[]){ return WSMain(argc, argv); }

運行結果:

f[1]

=>2

f[2]

=>3

f[233]

=>0

f[4]

=>4

假如非要說不能有全局變數就用單例模式,但是沒必要&我懶……

以上就是所有值得在這麼短一篇文章中提的關於Mathematica調用C/C++有關的內容,別的就是直接要用的時候查Tutorial Collection就好了~

請看到這裡的童鞋務必在下面評論區中回復一個「WSTP寫起來真簡單!」帶節奏233333


下面則是關於C/C++調用Mathematica的內容。

其實就以下幾步:

PutFunction的時候Put出去一個被EvaluatePacket包裹著的函數

WSPutFunction(stdlink, "EvaluatePacket", 1)

然後寫完要發的代碼

WSEndPacket(stdlink)

再確認是否算好了

WSCheckFunction(stdlink,"ReturnPacket",&n)

最後得到結果

WSGetInteger32(stdlink,&ans)

真的不是我寫累了不想寫了,真真的是沒內容太簡單了啊!

給個案例完事滾蛋:

搞一個數,輸入它乘二,預處理的時候乘二,C裡面乘二,返回Mathematica乘二,再扔到C裡面乘二,最後返回Mathematica乘二(……我也是真有聊)

f.tm:

:Begin::Function: f:Pattern: f[x_Integer]:Arguments: {x*2}:ArgumentTypes: {Integer}:ReturnType: Integer:End:

C代碼:

#include "stdafx.h" //這個是預編譯頭,沒有預編譯頭的話刪掉就行#include "wstp.h"int f(int x){ int temp = x * 2; WSPutFunction(stdlink, "EvaluatePacket", 1); WSPutFunction(stdlink, "Times", 2); WSPutInteger32(stdlink, temp); WSPutInteger32(stdlink, 2); WSEndPacket(stdlink); long n = 0; WSCheckFunction(stdlink, "ReturnPacket", &n); WSGetInteger32(stdlink, &temp); temp *= 2; return temp;}int main(int argc, char *argv[]){ return WSMain(argc, argv); }

測試一下:

f[1*2]*2

=>64

emmm看起來挺對!完事滾蛋!~

希望這個對大家有幫助啦,畢竟C++能彌補Mathematica至少不算擅長的高性能計算,而Mathematica則好在各種方面介面極其方便,寫起來代碼也是極端極可讀!二者互補簡直完美啊!是不是!!!!

最後的最後來個廣告……我覺得我要解釋下來龍去脈……是因為呢軟體中心很貼心的送了我一套Mathematica的撲克牌和一個WolframAlpha的Spikey摺紙,正好是我很眼紅的(雖然我有很多自製的各種Spikey(下次可以隨便找個地方發下圖,超好看的!),但是正版的真沒有!),所以就吃人嘴軟,拿人手短了……謝謝中科院軟體中心的小禮物啦~

所以:歡迎大家到中科院軟體中心去購買正版的Mathematica!(價格似乎還算可以,反正我是那裡買的……)聯繫方式:


推薦閱讀:

「mijia米家」,雷軍讓MUJI粉的心裡受到了一萬點傷害!!
站在IPO的邊緣,映客的生死時速!
數據醜聞之後,Facebook發布了一場史上最低凋、甚至無聊的F8大會
霍金的最後一篇論文發布了,他認為世界沒有我們想像中那麼無序
Scratch軟體下載

TAG:科技 | 計算機科學 | WolframMathematica |