.NET Micro Framework动态调用C/C++底层代码(原理篇)

.NET Micro Framework和WinCE系统不同,从应用开发角度来说,仅支持C#开发(从V4.2版本开始,才支持VB.NET开发),而不像WinCE应用开发,既可以用C#/VB.Net,也可以用EVC等工具进行C/C++开发。针对.NET Micro Framework平台由于C#等.NET语言是托管代码,系统需要对中间语言进行解释执行,所以运行效率上和原生的C/C++相比,效率是打了一个折扣的,这样对一些实时性要求比较高的应用来说,是很难实现的。

如果非要用.NETMicro Framework开发一些实时性高的应用,通常的做法就是从底层移植(Porting kit)入手,专门用C/C++写一个驱动,然后再封装一个可供C#调用的接口,以供应用开发者调用(参见《MicroFramework Interop功能实现》)。但是这种方法,必须要熟悉.NET Micro Framework系统移植,另外手头还必须有一套系统源码,不仅需要熟悉C/C++,还需要熟悉C#,以需上下结合,完成相关功能。从这个角度来说,对普通开发者来说太苛刻了,不仅对技术能力要求高,开发周期长,并且还需要重新编译固件,对原有系统进行升级。

在几年前,我就一直考虑能否采用Windows或WinCE平台的dll动态调用的思路,来实现.NET Micro Framework动态调用C/C++代码。所以后续也看了不少PE文件结构的文章,还有一些编译原理的书籍,但是由于自己知识储备不够,再加上该技术实现难度也比较高,一直不得其门。

在.NET MicroFramework系统移植和开发过程中,深深感受到,封装一个专有的硬件驱动接口是一件比较麻烦的事,所以受WinCE平台上流式驱动开发(可以参见我以前写的《我的第一个WINCE驱动》)的启迪,我封装了一套基于.NET Micro Framework的流式接口(目前基于这个接口,我已经开发了DHT11温湿度模块、超声波模块和看门狗的驱动程序,后续将发博文一一介绍),其C#语言的接口如下:

public sealed class GeneralStream

    {

        publicGeneralStream();

        public event GeneralStreamEventHandlerNotice;

        public int Close();

        public int IOControl(intcode);

        public int IOControl(intcode, int parameter);

        public int IOControl(intcode, byte[] inBuffer, intinCount, byte[] outBuffer, int outCount);

        public int Open(stringname);

        public int Open(string name,int config);

        public int Open(string name,string config);

        public int Read(byte[]buffer, int offset, intcount);

        public int Write(byte[]buffer, int offset, intcount);

    }

比较有特色的是,还提供了一个事件通知接口,这样就为各种硬件驱动开发提供了更灵活的支持。

有了这个流式接口,一般情况下,为上层C#语言提供专有的硬件底层功能,就不需要再编写接口相关的代码了,直接写相关的C/C++代码,然后编译链接即可。

由此,我突然想到,能否把基于流式接口开发的驱动,实现动态加载。

最初我开发的流式接口和各个流式驱动是在一个项目里面的,最终会编译成一个obj文件,后来考虑到便于调试和维护,把流式接口部分和各个流式驱动分开,每一个流式驱动都是一个单独的obj文件。

又受到ER_Config和ER_DAT的启发,在编译的时候,它们可以指定编译的起始位置,并且可以独立编译成一个bin(或hex)文件,所以我们的一个流式驱动也是可以独立的编译成一个bin文件的,这样部署的时候就可以单独部署了。

由于每个流式驱动的接口都是一致的,我们自然就可以想到,这个bin文件理论上是可以替换的,比如刚开始我们加载的是A功能的流式驱动,那么我们根据需要也可以替换为B功能的流式驱动。

那用户怎么开发这种相对独立的流式驱动模块呢?

如果还是基于.NETMicro Framework整个Porting kit开发环境,那对一般开发者来说,简直就是梦魇,因为光搭建环境,熟悉环境就得花很长时间。

所以最好的办法,就是用MDK开发环境,并且可以基于一个简单的流式驱动模块工程来开发驱动。

先等一下,我们暂且先不要考虑如何搭建MDK开发环境,让我们理一下思路,即使我们解决了开发和编译问题,但是最重要的是——需要解决流式驱动模块如何和宿主(也就是TinyCLR)进行交互的问题。

这里面有两个问题需要解决,一是宿主如何获取流式驱动模块的接口地址?二是流式驱动模块如何访问宿主的资源(我们在windows或wince平台就是通过所谓的API接口,访问系统资源的)。

第一个问题,看似简单,但是实现起来我走了不少弯路。

首先,我们很容易想到,我们把流式驱动函数接口的指针存储到一个变量中去,如下面的代码所示:

const IGeneralStream g_GeneralStream_UserDriver  =

{

         &GeneralStream_Open1_UserDriver,

         &GeneralStream_Open2_UserDriver,   

         &GeneralStream_Close_UserDriver,     

         &GeneralStream_IOControl1_UserDriver, 

         &GeneralStream_IOControl2_UserDriver, 

         &GeneralStream_Read_UserDriver,

         &GeneralStream_Write_UserDriver,     

};

我们只要知道g_GeneralStream_UserDriver的地址,就知道各个函数接口的地址了,换句话说,我们编译的时候,其实可以指定g_GeneralStream_UserDriver变量的地址的。但是问题来了,如果我编译的时候指定g_GeneralStream_UserDriver变量的地址,我们就无法固定流式驱动模块编译的起始地址,这样我们就不知道这个编译好的bin文件该部署到什么位置。另外g_GeneralStream_UserDriver变量也无法保持在和bin文件一个相对确定的位置上去(这和实际代码的多寡都有关系),所以解决这个问题我还是颇费周折的(如果大家有更好的方法确定g_GeneralStream_UserDriver地址的方法,可以交流一下)。

第一个问题,我们算解决了,我们实现了宿主加载和调用流式驱动接口。

第二个问题,我最初的做法是绝对定位,先根据系统函数的原型声明一个函数指针,然后根据编译后的map文件,查到这个函数的绝对地址,做一个转换。如下面的代码:

typedef  void(*MF_lcd_printf)(char const * format,...);

#define lcd_printf   ((MF_lcd_printf)0x0805ab73)

经过这一步后,我们就可以在流式驱动接口里直接调用这个系统函数了。但是这样做有一个明显的问题,就是一旦系统固件升级(需重新编译),那么这些绝对的地址,可能会发生变化。一旦有变化,这对流式驱动来说是致命的,不仅调用失败,并且非常可能导致系统挂起(如果是windows系统,此时就是蓝屏了)。

所以,我采用了另一种方式,和流式驱动提供流式驱动接口的方式一样,系统的API接口,也定义在一个变量中,如下面的代码所示:

const IGeneralStream_Function g_GeneralStream_Function  =

{       

     &Notice_GenerateEvent,

           &lcd_printf,

           &debug_printf,  

           &HAL_Time_Sleep_MicroSeconds_InterruptEnabled,  

           &CPU_GPIO_DisablePin, 

           &CPU_GPIO_EnableInputPin, 

           &CPU_GPIO_EnableOutputPin,

           &CPU_GPIO_GetPinState, 

           &CPU_GPIO_SetPinState, 

           &CPU_TIMER_Initialize, 

           &CPU_TIMER_Uninitialize,

           &CPU_TIMER_Start,

           &CPU_TIMER_Stop,

           &CPU_TIMER_GetState,

           &CPU_TIMER_SetState,

};

宿主调用流式驱动接口的时候,把这个g_GeneralStream_Function地址直接传个流式驱动即可。这种方法的优点是,不受系统固件的升级影响,但是缺点也很明显,就是系统给你提供了什么接口,你才能用什么接口(其实这个时候,第一种方法仍然有效,不过可以算作黑客的做法了)。

另外值得一提的是,由于这是驱动层面的开发,驱动程序理论上可以访问系统的任何资源,所以驱动程序一定尽可能在预先为自己规划好的代码区和RAM区工作,以免对系统的稳定性造成影响。

以上思路仅仅是一个初步,我们完全也可以像PE文件一样,为流式驱动程序加上一个类PE头,把导出的函数指针和需要引用的系统API指针等等资源,填写到类PE头上去。这样系统就可以根据PE头信息,自动加载各种流式驱动,以供上层应用程序调用。

下面一张截图是MDK开发流式驱动的场景,至于如何具体编写流式驱动和使用,后续我会专门写一篇.NET Micro Framework动态加载C/C++代码的应用篇。

 

MF简介:http://blog.csdn.net/yefanqiu/article/details/5711770

MF资料:http://www.sky-walker.com.cn/News.asp?Id=25


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是网络服务能力测试工具对应模块的实现方法和技术手段的C/C++代码示例: 1. 测试数据生成模块 测试数据生成模块可以使用随机数生成算法、数据模板生成算法等,以下是使用随机数生成算法的示例代码: ```c++ #include <cstdlib> #include <ctime> // 生成随机数 int rand_int(int min, int max) { static bool initialized = false; if (!initialized) { srand(time(NULL)); initialized = true; } return rand() % (max - min + 1) + min; } ``` 2. 测试执行控制模块 测试执行控制模块可以使用多线程技术、事件驱动技术等,以下是使用多线程技术的示例代码: ```c++ #include <thread> #include <vector> void test_worker(int thread_id) { // 执行测试任务 } // 启动并发测试 void start_concurrent_test(int thread_count) { std::vector<std::thread> threads; for (int i = 0; i < thread_count; i++) { threads.push_back(std::thread(test_worker, i)); } for (auto& t : threads) { t.join(); } } ``` 3. 测试结果收集模块 测试结果收集模块可以使用性能指标计算算法、性能指标统计算法等,以下是使用性能指标计算算法的示例代码: ```c++ #include <chrono> // 计算响应时间 long long calc_response_time(std::chrono::system_clock::time_point start_time, std::chrono::system_clock::time_point end_time) { std::chrono::duration<long long, std::micro> diff = end_time - start_time; return diff.count(); } ``` 4. 测试结果分析模块 测试结果分析模块可以使用数据分析算法、统计分析算法等,以下是使用统计分析算法的示例代码: ```c++ #include <algorithm> #include <vector> // 计算最大值 template<typename T> T calc_max(const std::vector<T>& data) { return *std::max_element(data.begin(), data.end()); } // 计算平均值 template<typename T> T calc_mean(const std::vector<T>& data) { T sum = std::accumulate(data.begin(), data.end(), 0); return sum / data.size(); } ``` 5. 网络通信模块 网络通信模块可以使用Socket编程接口、第三方网络库等,以下是使用Socket编程接口的示例代码: ```c++ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // 建立TCP连接 int tcp_connect(const char* ip, int port) { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { return -1; } struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(ip); serv_addr.sin_port = htons(port); if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { return -1; } return sockfd; } ``` 6. 界面交互模块 界面交互模块可以使用命令行界面、图形界面等,以下是使用命令行界面的示例代码: ```c++ #include <iostream> // 输出测试结果 void output_test_result(const std::string& result) { std::cout << result << std::endl; } // 获取测试参数 void get_test_params(int& thread_count, int& test_duration) { std::cout << "请输入线程数:"; std::cin >> thread_count; std::cout << "请输入测试时间(秒):"; std::cin >> test_duration; } ``` 7. 其他组成部分 其他组成部分包括日志记录模块、配置文件管理模块、异常处理模块等,以下是日志记录模块的示例代码: ```c++ #include <fstream> #include <chrono> // 记录日志 void write_log(const std::string& message) { std::ofstream log_file("log.txt", std::ios::app); if (log_file.is_open()) { auto now = std::chrono::system_clock::now(); std::time_t now_time = std::chrono::system_clock::to_time_t(now); log_file << std::ctime(&now_time) << message << std::endl; log_file.close(); } } ``` 总之,在C/C++中实现网络服务能力测试工具的对应模块可以使用多种技术手段和算法,包括随机数生成算法、多线程技术、事件驱动技术、性能指标计算算法、数据分析算法、Socket编程接口等。通过合理选择和应用这些技术和算法,可以实现高效、准确的网络服务能力测试工具。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值