C#: 星座星盘计算算法

前一篇提到计算八卦五行的算法,这里要跟大家分享一个星座星盘的算法。你们可能觉得笔者怎么开始研究这些玄幻的东西了,确实笔者觉得有一些真的是很扯,不过笔者的目的是为了研究大数据。好了,说到星盘笔者发现新浪星座有个很不错的星盘解说的http://astro.sina.com.cn/pc/zodiac.html大家可以试一下,笔者就觉得分析出来的星盘那个图很不错看起来很专业,图里面的也是数据格式的而不是底下文字描述的。后来想想,星座这种东西应该国外比国内更多研究,于是就搜了一些英文资料。(这里也鼓励大家多学一门外语总是好的)。居然无意中发现新浪星盘用的就是国外的Astrolog,还是开源的,从版本上看是5.416,而这个Astrolog是一直有更新的,目前已经是6.10版本了。新浪还有不少收费占卜的,笔者没试过,因为这并不是笔者看重的。

C#: 星座星盘计算算法_第1张图片

不敢独享,跟大家分享一下,官网是http://www.astrolog.org/可以下载到源代码,是C++的,很明显大家猜笔者会把这个改成C#版的,更重要的是,改成中文版的。哈哈,笔者也这么认为的,不过代码量太大,就不重写了,另外由于里面有不少C的写法比如sprintf要支持中文比较麻烦,而且已经有人做过中文版软件,这不是我目的,所以打包成dll让C#调用,中文部分根据输出在C#代码处理会比较可行。

在代码之前,笔者先分享一些基本知识,首先是12星座。

C#: 星座星盘计算算法_第2张图片


其次是星和行星


C#: 星座星盘计算算法_第3张图片

上面的太阳(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。

C#: 星座星盘计算算法_第4张图片


附下载链接:

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

你可能感兴趣的:(c#,星座,12星座,.Net)