visual c#中调用visual c++ 编译的cxform.dll问题总结

3 篇文章 2 订阅
3 篇文章 0 订阅

1.调用bat自动生成的cxform-c.dll库的结果

最近有一个项目,收到“J2000”的坐标要转换为“GEO”格式显示,不同坐标系之间的转换,网上已经有现成的源代码了,源代码叫做cxform,百度搜索不到的话,google一下,就可以找到其网站介绍,这里给出网站链接,这个网站中Download下下载选项,可以直接现在源代码进行编译,也可以下载在MSVC和GCC、linux、Mac以及SunOS下编译好的dll文件,如下图所示。

这里写图片描述

这里推荐下载源代码,因为我们只在c#中调用cxform-c.dll,而编译好的dll文件可以同时用于idl和C的调用。

刚开始我抱着多一事不如少一事的态度,想着直接用编译好的cxform.dll,但是在c#中调用过程中发现提示,无法在cxform.dll中无法找到date2es的入口点,又去百度了这个问题的原因,最后发现问题在于,这个dll库根本没有吧date2es这个函数作为导出函数以作为其他程序调用的API,既然没有把这个函数作为API让其它程序调用,其它程序调用当然就找不到date2es这个入口点了。这是经过提示,我下载一个depends_x86的程序,把cxform.dll直接在depends_x86中打开,得到如下所示的情况。

这里写图片描述

选中CXFORM.dll,看到Function中只有三个函数,没有我们需要的date2es函数,所以在c#中调用date2es函数就会出现这个问题,其原因就是编译好的dll库中没有date2es函数,depends_x86很好的说明了这个问题。既然编译源代码不行,那我们用现成的还不行么?那么问题来了,怎么用呢?没事,我们接着往下说。
我们就用它自己的源代码文件下的make_CXFORM_MSVC.bat来手动编译cxform-c.dll。这个cxform-c.dll和cxform.dll的区别在于,cxform-c.dll没办法支持IDL的调用,但是cxform可以支持IDL的调用,不过本文中我们不会用IDL调用,我们只需要C调用即可。打开Visual studio 命令提示(2010)命令行窗口(以管理员的方式运行,打开VS2010命令行窗口的方法直接在开始菜单输入“cmd”就能显示VS2010的命令提示符,之后右键单击选择以管理员身份运行,这是为了避免文件写入的Permission Denied错误。),命令行中切换到源代码文件所在目录,输入命令:cd /d 源代码中cxform.h的所在的目录,例如,我的visual studio中输入的命令如下:

cd /d C:\Users\tiger\Downloads\cxform-0.71_source.tar\cxform-0.71_source\cxform-0.71

如下图所示,则切换到了该目录下。

这里写图片描述
然后输入make_CXFORM_MSVC.bat,开始运行,然后提示只生成c调用的dll么?我们输入‘y’表示确认,随后在该目录下就生成了cxform-c.dll,生成的该dll在depends中打开截图如下:

这里写图片描述

可以看到在Function一个描述中一个函数都没有,这说明该源代码编译错误,我们并没有将接口导出来。那我们是不是应该尝试一下其他编译结果呢?比如说GCC下编译得到的dll?会不会完美的解决这个问题呢?我告诉你:

这里写图片描述

我已经试过GCC编译下的结果,同时也试过在64位和32位VS命令提示符下进行过计算,结果在生成的dll库仍然没有可以调用的函数,那么怎么办呢?最后我想到了一个办法,通过微软教程学习如何制作dll库,然后比对这个源代码和教程的区别,找出差异,再进行修改。

2.自己手动编译dll库

这里贴出一个教程,来自MSDN的官方教程,连接如下:

https://msdn.microsoft.com/zh-cn/library/ms235636.aspx

我不再对教程重复,而是针对教程讲解一下其机理。在这个教程里面,最重要的一部分就是MathFuncsDll.h文件中的如下代码:

// MathFuncsDll.h

#ifdef MATHFUNCSDLL_EXPORTS
#define MATHFUNCSDLL_API __declspec(dllexport) 
#else
#define MATHFUNCSDLL_API __declspec(dllimport) 
#endif

暂且对于这个代码的意义我们不去深究,但是确定的是,定义任何dll都不能少了上述声明代码,定义自己的导出dll时,只需要将上述代码中“MATHFUNCSDLL”变成自己头文件的名字,头文件全部大写,相信你也可以看出来,MATHFUNCSDLL就是教程中头文件的名字。如果你想对这个声明的内涵和外延有进一步的了解,我只能帮你到这儿了:

dllexport和dllimport

接下来第二重要的部分,我们且看MathFuncsDll.h文件中的函数的声明方式:

namespace MathFuncs
{
    // This class is exported from the MathFuncsDll.dll
    class MyMathFuncs
    {
    public: 
        // Returns a + b
        static MATHFUNCSDLL_API double Add(double a, double b); 

        // Returns a - b
        static MATHFUNCSDLL_API double Subtract(double a, double b); 

        // Returns a * b
        static MATHFUNCSDLL_API double Multiply(double a, double b); 

        // Returns a / b
        // Throws const std::invalid_argument& if b is 0
        static MATHFUNCSDLL_API double Divide(double a, double b); 
    };
}

我们的重点应该关注于这些函数的声明中有一个修饰符“MATHFUNCSDLL_API”,这个符号极其重要,可以看出这个符号的构成是形式是”头文件名称_API”的形式,这个符号表明了这个函数作为导出函数,可以被外部程序调用,如果没有这个修饰符,编译成功的dll文件中无法找到接口函数。
既然知道了问题dll编译接口函数的必备条件,那么就好办了,我们只需要稍加修改我们的cxform.h、文件中的内容就行了。对于cxform.h文件,我们修改如下:
在头文件中照猫画虎,加入如下代码:

#ifdef CXFORMCDLL_EXPORTS
#define CXFORMCDLL_API __declspec(dllexport)
#else
#define CXFORMCDLL_API __declspec(dllimport)
#endif

同时,将cxform的声明和date2es形式修改为如下形式:

CXFORMCDLL_API int  cxform(const char *from,const char *to,const double et,Vec v_in,Vec v_out);
CXFORMCDLL_API long  date2es(int yyyy, int mm, int dd, int hh, int mm2, int ss);

好了,最后点击生成(&B)中的生成解决方案,打开我们dll如下图所示:

这里写图片描述

可以看出,date2es和cxform两个函数已经导出,因此,说明编译dll已经成功了。当然了,如果你觉得很繁琐的话,我也已经将该dll打包上传,连接如下:
http://download.csdn.net/detail/t46414704152abc/9855135

3.在c#中调用该dll

接下来,就是在c#中调用这个dll库的方法了,需要将该DLL文件放入Debug文件目录下,或者系统System32文件目录下面。在c#要调用该dll的类中加入如下代码,下列代码中CallingConvention = CallingConvention.Cdel不可以发生变化,如果发生变化,则我们编译上述dll的代码也要相应变化。

//int cxform(const char *from,const char *to,const double et,Vec v_in,Vec v_out);
//上面注释是c++中的cxform的参数形式,但是在c#中调用需要进行格式转换,其中Vec是装有三个double元素的数组
[DllImport("cxform-c.dll", EntryPoint = "cxform", CallingConvention = CallingConvention.Cdecl)]
public static extern  int  cxform(string from, string to, double et,   double[] v_in,  double[] v_out);


//long  date2es(int yyyy, int mm, int dd, int hh, int mm2, int ss);
//上面注释是c++中date2es的参数形式,但是在c#中调用需要进行格式转换
[DllImport("cxform-c.dll", EntryPoint = "date2es", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int date2es(int yyyy, int mm, int dd, int hh, int mm2, int ss);

本例子中的变量转换规则如下

c++中的变量c#中的形式
longint
const char *string
double[]double[]

于是,贴出下文代码中一个c#的例子

 static void Main(string[] args)
        {
            DateTime dt = new DateTime(2017, 5, 27, 21, 40, 51);
            dt = dt.ToUniversalTime();
            //将GSE坐标转换GEO坐标
            string from = "GSE";
            string to = "GEO";
            long es = date2es(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
            double[] coord = new double[3] { 10.0, 10.0 , 10.0 };//设置坐标
            double[] rsltCoord = new double[3];//存放转换结果
            cxform(from, to, es, coord, rsltCoord);//进行坐标转换
            foreach (double x in rsltCoord)
            {
                Console.Write(x);
            }
        }

4.内容总结

  1. 如果你的代码在运行中出现了类似于如下的错误:

检测到 FatalExecutionEngineError Message: 运行时遇到了错误。此错误的地址为
0x6d968090,在线程 0xa28 上。错误代码为 0xc0000005。此错误可能是 CLR 中的
bug,或者是用户代码的不安全部分或不可验证部分中的 bug。此 bug 的常见来源包括用户对 COM-interop 或 PInvoke
的封送处理错误,这些错误可能会损坏堆栈。

那么可能是你的c++和c#之间参数转换的问题了,需要再次检查你的参数转换是否出现了问题

2.如果你的代码在运行中出现了如下错误:

对 PInvoke 函数“xxFunction()”的调用导致堆栈不对称。原因可能是托管的 PInvoke
签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。

解决方法如下:

方法1.在c#中函数声明处改一个参数,[DllImport(“xx.dll”, EntryPoint=“xxFunction”, CallingConvention = CallingConvention.Cdecl)]调用时不变

方法2.在c++代码中改对应的c++函数参数从extern“C” _declspec(dllexport) void xxFunction()改成
extern“C” _declspec(dllexport) void __stdcall xxFunction(),即再添加一个修饰关键字“__stdcall”

具体原因如下:

在c++WIN32程序中有三种calling convention(呼叫约定):__cdecl, __stdcall, __fastcall默认为__cdecl,而c#中默认为CallingConvention =CallingConvention.Winapi,两个平台呼叫约定不一致,所以会出现提示的不匹配错误。

__cdecl为调用函数即C#中清理堆栈中保存的参数。参数的大小不确定时用这个,比如string

__stdcall对应c#中CallingConvention =CallingConvention.Winapi,它由c++中函数自动清理。

Win32 calling convention(呼叫约定)的三种约定具体分析见下面链接:

http://www.cnblogs.com/super119/archive/2011/04/10/2011304.html
http://www.cnblogs.com/dust/articles/1190641.html

3.官方既然提供了dll库,但是这个库又不能使用,妈卖批不知道想干什么?害得我搞这事情搞了一个星期,既然提供了dll,那么为什么没有接口函数?为什么没有接口函数?为什么没有接口函数?不过通过这坏事,也让我学习到了通过VS如何编译生成dll库,同时在c#中调用。不过正是因为官方源代码存在的问题,使得我学到了更多的知识。正是应验了那句话“在失败中学到的,往往比在成功中学到的更多!”

4.最后通过这件事情,学到的一件事情,遇到不要轻易放弃,你已经坚持了这么久,离成功很近了,再坚持一次说不定就有转机了,每次坚持不下去了,告诉自己,再坚持一次,再努力一次,你的付出不会亏待你的,真的会有奇迹发生!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值