前一篇提到计算八卦五行的算法,这里要跟大家分享一个星座星盘的算法。你们可能觉得笔者怎么开始研究这些玄幻的东西了,确实笔者觉得有一些真的是很扯,不过笔者的目的是为了研究大数据。好了,说到星盘笔者发现新浪星座有个很不错的星盘解说的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