在从Emgu学习C#调用C++库(1)中,挑了一个Emgu的“第一”函数分析了一下。虽然还是有些许不解,但大意还是略懂些了。百看不如一练,今天就试着从我的C#中调用一下我的C库吧。
要调用的是我在写的一个OCR库。为了不过多地泄露“机密”(拿不出手),就只拿其中的一个函数接口作为例子吧。就也举一个创建实例的例子。我的C接口是这么写的:
/** 创建OCR实例
@param[in] reader_type OCR的类型,\ref READER_TYPE
@param[in] ocr_info 与识别器有关的参数
@return OCR的句柄
*/
GGAPI(READER_HANDLE) ggCreateReader(int reader_type, const OCRRelatedInfo_t* ocr_info);
三点说明:
- reader_type实际上是一个枚举类型。
typedef enum {
GG_ACCOUNT_READER = 1,
GG_MONEY_READER = 2,
GG_RMBSN_READER = 3
}READER_TYPE;
- OCRRelatedInfo_t是自己定义的一个结构体。
//! 账号图像信息
typedef struct tagOCRRelatedInfo
{
int x; //!< ROI起点x坐标
int y; //!< ROI起点y坐标
int width; //!< 宽度
int height; //!< 高度
int percent; //!< 如果ROI为所占的百分比,则可以设置此参量为分母
int bits; //!< 个数
int min_h; //!< 字符最小高度
int max_h; //!< 字符最大高度
int min_w; //!< 字符最小宽度
int max_w; //!< 字符最大宽度
char rec_file[256]; //!< 识别相关的文件存储的位置
int image_width; //!< 配置结构体项时对应的图像的宽度
int image_height; //!< 配置结构体项时对应的图像的高度
char resv[120]; //!< 保留字节
}OCRRelatedInfo_t;
-
READER_HANDLE
实际上是一个指针的值,但被定义为long型
#define READER_HANDLE long
首先来看如何转换枚举类型。仿照IPL_DEPTH
,我也来写一个enum:
namespace OCRAlgWrapper.OCREnum
{
public enum READER_TYPE
{
GG_ACCOUNT_READER = 1,
GG_MONEY_READER = 2,
GG_RMBSN_READER = 3
}
}
然后来定义自定义的结构体:
namespace OCRAlgWrapper
{
///
/// Managed structure equivalent to OCRRelatedInfo_t
///
[StructLayout(LayoutKind.Sequential)]
public struct OCRRelatedInfo
{
public int RoiX;
public int RoiY;
public int RoiW;
public int RoiH;
public int RoiPercent;
public int CharBits;
public int CharMinH;
public int CharMaxH;
public int CharMinW;
public int CharMaxW;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public string RecFileName;
public int ImageWidth;
public int ImageHeight;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=120)]
public string resv;
}
}
自定义结构体的封装在(1)中并未提及。官网中讲的很详细:
可以为传递到非托管函数或从非托管函数返回的结构和类的字段指定自定义封送处理属性。通过向结构或类的字段中添加MarshalAs
属性可以做到这一点。还必须使用StructLayout
属性设置结构的布局,还可以控制字符串成员的默认封送处理,并设置默认封装大小。
请考虑下面的 C 结构:
typedef struct tagLOGFONT
{
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
在 C# 中,可以使用StructLayout
和MarshalAs
属性描述前面的结构,如下所示:
// logfont.cs
// compile with: /target:module
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class LOGFONT
{
public const int LF_FACESIZE = 32;
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]
public string lfFaceName;
}
然后即可将该结构用在 C# 代码中,如下所示:
// pinvoke.cs
// compile with: /addmodule:logfont.netmodule
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr CreateFontIndirect(
[In, MarshalAs(UnmanagedType.LPStruct)]
LOGFONT lplf // characteristics
);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(
IntPtr handle
);
public static void Main()
{
LOGFONT lf = new LOGFONT();
lf.lfHeight = 9;
lf.lfFaceName = "Arial";
IntPtr handle = CreateFontIndirect(lf);
if (IntPtr.Zero == handle)
{
Console.WriteLine("Can't creates a logical font.");
}
else
{
if (IntPtr.Size == 4)
Console.WriteLine("{0:X}", handle.ToInt32());
else
Console.WriteLine("{0:X}", handle.ToInt64());
// Delete the logical font created.
if (!DeleteObject(handle))
Console.WriteLine("Can't delete the logical font");
}
}
}
运行示例
C30A0AE5
在前面的示例中,CreateFontIndirect 方法使用了一个 LOGFONT 类型的参数。MarshalAs 和 In 属性用于限定此参数。程序将由此方法返回的数值显示为十六进制大写字符串。
好了,准备工作到此结束,下面就要来写C#中的接口函数了:
namespace OCRAlgWrapper
{
public class OCRInvoke
{
[DllImport("gg_universal_ocr.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr ggCreateReader(OCREnum.READER_TYPE readerType, ref OCRRelatedInfo ocrInfo);
}
}
其中,结构体的指针被写成了ref参数。而由于返回的其实就是指针,因此用IntPtr来表示。
最后,就可以在主函数中调用了:
IntPtr ocrHandle = OCRInvoke.ggCreateReader(READER_TYPE.GG_RMBSN_READER, ref m_ocrInfo);
到此,C#就可以调用C++的函数了,到这里,故事似乎就应该结束了。但是还差一点点:当我调试的时候,竟然F11
不进去,无法调试C++的代码!不能调试当然不行。而“知其要者,一言而终;不知其要,流散无穷。”
事实上,我们只需要选择项目属性中的调试标签,并勾选“启用本机代码调试”就可以了。