标签归档:dll

从C++的dll中Callback到C#返回数组:只取到了第一个元素

假设C/C++的dll中定义回调函数:

// Received data callback
typedef void(__stdcall *ReceivedDataCallback)(SerialPortRef sp, unsigned char* data, int len);


/*********************************************************
* Library API definitions *
/*********************************************************/
#ifdef __cplusplus
extern "C" {
#endif

 // Register handl to receive data from a SerialPort.
 SERIALPORTRW_API void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);

//......................

C#中包含dll,通过包装dll的函数和回调函数,以便dll中收到数据时回调到C#:

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
    public delegate void ReceivedDataCallback(IntPtr serialPortRef, [MarshalAs(UnmanagedType.LPArray)]byte[] data, int len);
	#endregion
		
	#region API Wraper
	public class SerialPortDevice
	{
		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
        public static extern void RegisterReceivedDataCallback(UIntPtr sp, ReceivedDataCallback handle);

使用此DLL包装的API:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, byte[] data, int len) =>
            {
                byte[] localData = data;

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += ByteUtils.bytesToHexString(localData, 0, len); }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }

发现上面红色部分的data的长度总是一个字节。

原因:

marshaller不知道data的长度到底是多少,只是简单的按照数据类型返回了数组中的第一个元素,根本没有办法marshal。

正确的做法是将data定义为指针,然后通过拷贝的方法将数据取到C#端:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, IntPtr data, int len) =>
            {
                byte[] localData = new byte[len];
                Marshal.Copy(data, localData, 0, len);
                
                string strHex = ByteUtils.bytesToHexString(localData, 0, len);

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + strHex; }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }
        }

当然,在这之前要在C#端的回调函数定义中的数据也要改成指针:

public delegate void ReceivedDataCallback(IntPtr serialPortRef, IntPtr data, int len);

 

Unity 5.0 中使用C/C++开发的dll插件使用中的问题 DllNotFoundException 问题集

Unity 中会经常用到C/C++开发的动态库(dll),Unity称为第三方插件,或plugin,这是非常厉害的扩展方法,但是Unity使用dll也非常痛苦,错误百出。

最常见的问题是 DllNotFoundException,就是找不到动态库。导致DllNotFoundException的原因很多,现把一些可能的情况,以及其解决方案一一列举:

1、DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

Unity基于Mono实现的跨平台特性,但是Mono的 .net framework只充分实现了.net 2.0 subet,至于.net framework 4.5,照目前的速度,估计要等到下辈子了。

操作串口有很多问题,于是我自己用C++分装了一个dll,用于管理串口。从dll中导出的C#代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
	public delegate void ReceivedDataCallback(IntPtr serialPortRef, ref byte[] data, int len);
	//
	public delegate void SerialPortStatusChanged(IntPtr serialPortRef, SerialPortStatus status, string msg);
	#endregion
	
	#region Data types	
	// Serial Port Status
	public enum SerialPortStatus
	{
		PortClosed,              // 端口已关闭
		PortOpened,              // 端口已打开
		PortError,               // 端口错误
		PortInvalid,             // 端口不可用
	};
	
	// SerialPort Identity	
	public struct SerialPortRef
	{
		public IntPtr Identity;
	};
	
	#endregion
	
	#region API Wraper
	public class SerialPortDevice
	{
		// Create a serial port for lately operations. return Identity of new serial port.
		// Return NULL if create failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]
		public static extern SerialPortRef CreateSerialPort();

		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);
		
		// Register handl to receive serial port status changed event.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterSerialPortStatusChangedCallback(SerialPortRef sp, SerialPortStatusChanged handle);
		
		// Open a serial port by name.
		// Return false if open failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool OpenSerialPort(SerialPortRef sp, string portName, int bauntRate);
		
		// Write and send data by serial port
		// Return the length sent by serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortWrite(SerialPortRef sp, byte[] data, int len);
		
		// Read data from serial port
		// Return the length readed from serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortRead(SerialPortRef sp, ref byte[] buffer, int bufferLen);
		
		// Check wether a serail port is openning.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool IsSerialPortOpenning(SerialPortRef sp);
		
		// Close a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void CloseSerialPort(SerialPortRef sp);
		
		// Destroy a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void DestroySerialPort(SerialPortRef sp);
	};
	#endregion
}

然后把dll拷贝到Unit Prj 的assert目录下,报错:

DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

折腾了很久,发现在导入dll根本不需要后缀“.dll”,即写上dll文件名就OK了,不需要.dll后缀:

		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]

但是这是VS的C#项目的默认做法。一个习惯性的动作导致了这个问题。于是把所有“.dll”删除,OK了。

 

2、Failed to load ‘Assets/PNLib.dll’ with error ‘The specified module could not be found.‘, GetDllDirectory returned ”. If GetDllDirectory returned non empty path, check that you’re using SetDirectoryDll correctly.

这种情况是,找到dll了,但是无法装载。看提示基本还是一头雾水: ‘The specified module could not be found.‘, GetDllDirectory returned ”。看似还是路径的问题。

这个问题测试了很多种情况,基本上是,只要是dll放到Assets目录下(或Assets下创建的Plugin目录下)就都会提示这个错误。而放到Assets外与Assets同目录就OK了,这难道不是Unity的某个bug吗?更奇怪的是之前在Assets下的Plugin里放的另一个dll(SerialPortDevice.dll)就不会有这样的问题。何解?

 

如果还有其他问题,后续继续补充。。。。。。

 

【转】Windows上调用Cygwin编译的函数库

2012-04-05 19:22
转载自 kaien_space

本篇介绍的是静态库函数。静态库扩展名是.lib(windows上)或.a(linux上),他和动态库(dll)是有区别的。

调用静态库编译后会写入执行程序中。然后就可以独立运行了。

动态库旨在动态调用,调用的时候需要加载dll才能正常工作。

(所以动态库往往可以提供补丁,或功能升级的时候使用,但是运行的速度有待商协)

另外,两个库的编译器也不一样,例如mingw用g++.exe生成动态库.dll, 用ar 生成静态库.a

而VC则一律用link.exe生成生成动态和静态库,用options来区别生成哪种。

 

本文只讲静态库.dll和.a 的例子。动态库其实很类似。

我们的目的是,把若干的C++和C文件在Cygwin上编译成一个静态库函数文件。

然后在Windows上调用这个库函数。

举个简单的例子:

我们有两个库函数文件.cpp或.c

1. myf1.cpp

#include <stdio.h>

void f1_Fonction1(int a, double b, char *c)
{
printf(“调用文件myf1.cpp的f1_Fonction1成功\n”);
}
int f1_Fonction2(int c)
{
printf(“调用文件myf1.cpp的f1_Fonction2成功\n”);
return c+1;
}

2. myf2.c

#include <stdio.h>

void f2_Fonction1(void)
{
printf(“调用文件myf2.c的f2_Fonction1成功\n”);
}
int f2_Fonction2(int c)
{
printf(“调用文件myf2.c的f2_Fonction2成功\n”);
return c+2;
}

上面两个文件一个是C++的,另一个是C的。

两个文件各提供了两个函数。

现在我们在Cygwin上编译他们为库函数mylib.a 或mylib.lib (扩展名不重要)

> gcc -c myf1.cpp myf2.c

由于我们这里的函数都是C标准格式,所以我们还使用g++, c++或cc等编译。选用任意一种都可以编译通过。

(通常情况下,选哪个编译器和你的C++代码的编写用语格式有关,每个编译器都有其特殊的支持格式)

上面的命令编译生成两个文件myf1.o和myf2.o

接着,我们使用ar生成静态库

> ar r mylib.lib myf1.o myf2.o

这样,我们就在cygwin上生成了一个静态库mylib.lib

现在,我们尝试在windows上调用这个库中的一个函数试试看

写一个简单的调用的C++程序main.cpp

#include <iostream>
using namespace std;

void f1_Fonction1(int a, double b, char *c);

int main()
{
char c;

f1_Fonction1(1,2.0,&c); //调用了myf1里面的函数f1_Fonction

cout << “Hello world!” << endl;
return 0;
}

现在,在window上用MinGW编译。

首先生成main.o文件

> gcc -c main.cpp

接着把main.o和函数库mylib.lib连接起来生成main.exe文件

> g++ -o main.exe main.o mylib.lib

注意link的顺序。函数库必须在主函数后面。另外,因为main.cpp是C++格式,所以link的时候要用g++编译器。gcc编译器会出现没有定义std::cout 这类的错误信息。

另外一个值得注意的是,虽然VC的.lib和Linux的.a格式是相同,都是一种归档文件格式。但是由于VC编译的程序中函数列表需要符号索引 (如果你用 reimp -s mylib.lib 察看VC的lib文件就会发现,函数名都被加了冗长的前后缀),所以vc的lib似乎无法直接在gcc上连接通过,同样的gcc的.a似乎也无法在vc上使用。虽然也有些文章讨论过lib和a的转换问题,配合使用reimp和dlltool命令转换。但是我的测试中发现转换后的库文件还是无法调用。如果有人成功的实现vc和gcc的静态库互换,请务必留言告诉我具体方法。