C#: 星座星盤計算算法

前一篇提到計算八卦五行的算法,這裏要跟大家分享一個星座星盤的算法。你們可能覺得筆者怎麼開始研究這些玄幻的東西了,確實筆者覺得有一些真的是很扯,不過筆者的目的是爲了研究大數據。好了,說到星盤筆者發現新浪星座有個很不錯的星盤解說的http://astro.sina.com.cn/pc/zodiac.html大家可以試一下,筆者就覺得分析出來的星盤那個圖很不錯看起來很專業,圖裏面的也是數據格式的而不是底下文字描述的。後來想想,星座這種東西應該國外比國內更多研究,於是就搜了一些英文資料。(這裏也鼓勵大家多學一門外語總是好的)。居然無意中發現新浪星盤用的就是國外的Astrolog,還是開源的,從版本上看是5.416,而這個Astrolog是一直有更新的,目前已經是6.10版本了。新浪還有不少收費佔卜的,筆者沒試過,因爲這並不是筆者看重的。


不敢獨享,跟大家分享一下,官網是http://www.astrolog.org/可以下載到源代碼,是C++的,很明顯大家猜筆者會把這個改成C#版的,更重要的是,改成中文版的。哈哈,筆者也這麼認爲的,不過代碼量太大,就不重寫了,另外由於裏面有不少C的寫法比如sprintf要支持中文比較麻煩,而且已經有人做過中文版軟件,這不是我目的,所以打包成dll讓C#調用,中文部分根據輸出在C#代碼處理會比較可行。

在代碼之前,筆者先分享一些基本知識,首先是12星座。



其次是星和行星



上面的太陽(Sun)和月亮(Moon)就不必解釋了,其他的行星都給出了英文名、符號和解釋。網絡的圖沒有冥王星英文是(Pluto)。


好了開始代碼部分,首先我們要添加用於C#訪問的函數,

#define API_EXPORT __declspec(dllexport)

先這裏做個預定義,後面每個函數都要用到。使用功能之前要先給astrolog程序來做個初始化

EXTERN_C API_EXPORT void __stdcall init()
{
is.S = stdout;
FProcessSwitchFile(DEFAULT_INFOFILE, NULL);
is.fSzPersist = fTrue;
}


初始化好了就可以調用實質的函數來生成星圖,保存星圖到文件夾,並把右側數據信息保存到字符串來返回

EXTERN_C API_EXPORT wchar_t* __stdcall ProcessAstrolog(wchar_t* filepath, int year, int month,int day,double time)
{
AllInfo = (char*)malloc(5000 * sizeof(char));


if (AllInfo == NULL) return NULL;


char* result;

int i=0,j=0;
ciCore = { month, day, year, time, 0.0, 8.0, 122.19984, 47.36584, "", "" };
char *v0 = "astrolog";
char *v1 = "-Xo";
char *v2 = w2c(filepath);
if (v2 == NULL) return NULL;
char * vs[] = { v0, v1,v2 };
i= FProcessSwitches(3, vs);

Action();


sprintf(result, "%s", AllInfo);
free(v2);
v2 = NULL;
free(AllInfo);
AllInfo = NULL;


return c2w( result);
}

EXTERN_C API_EXPORT int __stdcall freeSMem(wchar_t* intptr)
{
free(intptr);
intptr = NULL;
return 1;
}

這裏面還涉及到C#的char是用2個字節來存儲的而在C++裏面char是用1個字節來存儲的,從這裏大家也可以理解爲什麼筆者放棄在C++裏面直接漢化輸出了。既然有這麼個gap,我們就需要做轉換工作,C#裏面的char對應着C++裏面的wchar_t,傳進來之後要把高位的扔掉,生成一個長度相等,每位是C++的char大小的空間來存放新字符串,反之從C++的字符串傳到C#的也要做一次這樣的反操作。大家可以不理解,直接拷貝下面的代碼用就是了。上面的freeSMem函數是因爲返回的字符串內容要在C#裏面用完了才能銷燬,所以有這個函數供C#來調用並銷燬。


wchar_t* c2w(char* CStr)
{
size_t len = strlen(CStr) + 1;
size_t converted = 0;
wchar_t *WStr;
WStr = (wchar_t*)malloc(len * sizeof(wchar_t));
if (WStr == NULL) return NULL;
mbstowcs_s(&converted, WStr, len, CStr, _TRUNCATE);
return WStr;
}


char* w2c(wchar_t* WStr)
{
size_t len = wcslen(WStr) + 1;
size_t converted = 0;
char *CStr;
CStr = (char*)malloc(len * sizeof(char));
if (CStr == NULL) return NULL;
wcstombs_s(&converted, CStr, len, WStr, _TRUNCATE);
return CStr;
}

上面的c2w和w2c裏面的c和w分別代表C++的char和wchar_t。

好了可以介紹上面這個ProcessAstrolog()函數了,首先給AllInfo 分配了一段內存來保存右側數據信息用來返回到C#,然後更新用戶數據包括年月日時間等的信息,即更新ciCore變量,接着模擬執行一條保存星圖的命令,這個其實大家可以使用cmd嘗試執行Astrolog.exe -Xo d:\abc.bmp這樣的語句,就知道本函數的方法了。傳進去後保存圖片並不是立刻執行的,所以需要加入Action()這一句來保存圖片,最後釋放指針並返回字符串結果。

然後就到C#代碼了,見下面

[DllImport(@"Astrolog.dll")]
        extern unsafe static void init();

[DllImport(@"Astrolog.dll")]
        extern unsafe static IntPtr ProcessAstrolog(char* filepath, int year, int month, int day, double time);

先定義DllImport,這個不難。


 void processastrolog()
        {
            try
            {
                DateTime t = this.dateTimePicker1.Value;
                string filepath = "d:\\lyx\\xyz.bmp";
                int freeresult=0;
                unsafe
                {
                    
                    //在傳遞字符串時,將字符所在的內存固化,並取出字符數組的指針
                    fixed (char* p = &(filepath.ToCharArray()[0]))
                    {
                        //調用方法
                        IntPtr abc = ProcessAstrolog(p, t.Year, t.Month, t.Day, Convert.ToDouble( (t.Hour+t.Minute/100.0).ToString("0.00")));


                        if (abc == null) {
                            MessageBox.Show("Astrolog處理出錯!");
                            return;
                        }


                        this.richTextBox1.Text += Marshal.PtrToStringAuto(abc);


                        freeresult= freeSMem(abc);
                    }
                }
                if (File.Exists(filepath))
                {
                    this.pictureBox1.ImageLocation = filepath;
                }
                MessageBox.Show(freeresult.ToString());
            }
            catch
            { }
           
        }

然後要先建1個圖片鏈接字符串,固化到內存並把內存首地址傳進dll,如果用過C#做圖像處理的應該對鎖內存很熟悉吧。然後把圖片地址,年月日時間等傳進dll,獲取返回的字符串連接,並輸出到文本框,因爲這個字符串所在的內存是C++裏面動態開闢的,所以需要釋放,最後加載圖片就可以了。

C++修改項目屬性爲輸出dll而不是exe,修改C#的項目屬性,將目標平臺設置爲x86,並且允許非安全代碼。

一切都很不錯,成功執行,成功保存圖片並返回字符串結果。可惜的是,運行不了多久就會內存報錯,這真的是百思不得其解的鬱悶事,筆者怎麼說寫代碼15年了也是混過C和C++的,包括單片機的彙編,內存管理大家都知道是個頭疼事不過筆者還是挺自信的,仔細檢查一下,所有的malloc之後都free了,並設置指針指向NULL了,C#裏面也把代碼都放進try catch裏面了,還是報錯,而且內存錯誤是致命的不可能繼續執行。

萬般無奈之下,筆者開始了另一種方式,在C#裏面開闢內存地址存放返回的字符串傳給C++,而C++裏面的只要有malloc那麼在返回到C#之前就free掉,並且做了工作狀態的限制,新建1個布爾類型的全局變量叫DllProcessing,在程序開始的時候才賦值爲真程序末尾賦值爲假,在爲真的狀態下AllInfo才能夠被操作,即if(DllProcessing) strcat(AllInfo, sz);。並且讓C#的unsafe裏面的代碼儘量少,最後代碼如下

DllProcessing = true;
AllInfo = resultString;


/* Month, Day, Year, Time in hours, Daylight offset, Time zone, Longitude, Latitude, Name for chart, Name of location */
ciCore = { month, day, year, time, DaylightOffset, Timezone, lgt, lat, "", "" };
char *v0 = "astrolog";
char *v1 = "-Xo";
char *v2 = w2c(filepath);
if (v2 == NULL) return NULL;
char * vs[] = { v0, v1,v2 };


FProcessSwitches(3, vs);
Action();


DllProcessing = false;


free(v2);
v2 = NULL;


return AllInfo;


C#代碼如下

                Byte[] bPara = new Byte[2000];    //新建字節數組  
                IntPtr pRet;
                unsafe
                {


                    //在傳遞字符串時,將字符所在的內存固化,並取出字符數組的指針
                    fixed (char* p = &(filepath.ToCharArray()[0]))
                    {
                        //調用方法
                        pRet = DoAstrolog(ref bPara[0], p, t.Year, t.Month, t.Day, Convert.ToDouble((t.Hour + t.Minute / 100.0).ToString("0.00")),0.0,-8.0, 122.19984, 47.36584);
                    }
                }
                
                if (pRet == null)
                {
                    MessageBox.Show("Astrolog處理出錯!");
                    return;
                }


                string strGet = System.Text.Encoding.Default.GetString(bPara, 0, bPara.Length);    //將字節數組轉換爲字符串  
                string strRet = Marshal.PtrToStringAnsi(pRet);


                this.richTextBox1.Text += strRet;


這次就好了,沒有深究前面代碼內存出錯的原因,估計是在C++ malloc的字符串指針在返回到C#之後沒有立即銷燬,而這段時間C++裏面的函數在看不見的地方有沒有調用過不知道,畢竟C++的代碼並沒有深入去研究,那麼在C#去釋放這段地址的時候可能它已經改變了,就出錯了。所以建議是,自己在C++額外植入的代碼,用了malloc在自己可預見的範圍內就要銷燬,否則C++原有部分就是個黑匣子,你都不知道會發生什麼。

可以獲得所有需要的數據了,這裏輸出的數據經過筆者篩選,沒用的不輸出,然後可以根據這些數據來做自己想做的事情了。最後要給大家介紹另一位大師的文章也是研究astrolog的,和代碼沒關係,不過可以獲得不少星盤研究的知識,http://blog.sina.com.cn/s/blog_80b9fc470101hmpy.html。



附下載鏈接:

http://download.csdn.net/detail/lyx_zhl/9808555

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章