一、引言
1.实验目的
构建一个基于C#的组件,该组件具有以下功能:
1)给一个链接地址,下载这个地址所指向的网页;
2)对下载的网页进行去重、中文分词等,并给出这个文档的“主题词”和各词的重要性评价。(可自己写或者基于其他库来分词,主题词的定义可参考语言处理模型)
分别用VC 和 C#调用这个库,提供GUI界面。
2.环境配置
- 操作系统:Windows 10 X64
- 编程工具:Visual Studio 2017
二、c#封装动态链接库DLL并调用
1.准备阶段
(1)使用Visual Studio 2017,新建c#项目:文件->新建->项目
(2)点击左侧的Visual C#,选择Windows窗体应用(.NET Framework),命名为JiebaSpider,选择自己的文件位置,框架选择.NET Framework 4.5,然后选择确定
(3)创建好项目之后,设计GUI界面,点击视图->工具箱
(4)按照下图将控件的位置先拉好(直接从工具箱拖拉拽控件即可)
(5)右键控件选择属性,在右侧属性栏外观下修改Text如下图
2.编译c#类并利用Jieba分词工具进行分词
(1)导入jieba.NET包
点击工具->NuGet包管理器->管理解决方案的NuGet程序包
点击浏览,输入jieba,选择jieba.NET包,在右侧选中项目,点击安装
点击确定
(2)编辑Form1.cs类
具体代码如下:Form1.cs
using JiebaNet.Analyser;
using JiebaNet.Segmenter;
using System;
using System.Net;
using System.Text;
using System.Windows.Forms;
namespace JiebaSpider
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string url = textBox1.Text;
Uri httpURL = new Uri(url);
///HttpWebRequest类继承于WebRequest,并没有自己的构造函数,需通过WebRequest的Creat方法 建立,并进行强制的类型转换
HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(httpURL);
//httpReq.Headers.Add("cityen", "tj");
///通过HttpWebRequest的GetResponse()方法建立HttpWebResponse,强制类型转换
HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse();
///GetResponseStream()方法获取HTTP响应的数据流,并尝试取得URL中所指定的网页内容
///若成功取得网页的内容,则以System.IO.Stream形式返回,若失败则产生ProtoclViolationException错 误。
System.IO.Stream respStream = httpResp.GetResponseStream();
///返回的内容是Stream形式的,所以可以利用StreamReader类获取GetResponseStream的内容
System.IO.StreamReader respStreamReader = new System.IO.StreamReader(respStream, Encoding.UTF8);
//从流的当前位置读取到结尾
string strBuff = respStreamReader.ReadToEnd();
respStreamReader.Close();
respStream.Close();
//使用Jieba组件进行分词操作
var segmenter = new JiebaSegmenter();
var segments = segmenter.Cut(strBuff);
textBox2.Text = string.Join("/ ", segments);
var extractor = new TfidfExtractor();
//获取文档的关键词和权重,这里是提取了前十个关键名词或动词
var keywords = extractor.ExtractTagsWithWeight(textBox2.Text, 10, Constants.NounAndVerbPos);
foreach (var keyword in keywords)
{
textBox3.Text += keyword.Word + "(" + String.Format("{0:F}", keyword.Weight) + ")" + " ";
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
}
可以使用Alt+Enter快捷键导入引用的包
(3)把jieba.NET包里的Resources文件复制进JiebaSpider/bin/Debug文件夹下(不然运行会报错)
(4)运行结果,测试课程网站,进行分词并给出主题词和权重
3.生成动态链接库DLL
(1)点击项目->JiebaSpider属性
(2)选中左侧的应用程序,将输出类型改为类库,保存
(3)点击生成->重新生成解决方案
(4)可以再JiebaSpider->bin->Debug文件夹下看到生成的动态链接库JiebaSpider.dll
4.在c#中调用JiebaSpider.dll库
(1)创建一个新项目,选择Windows窗体应用(.NET Framework),命名为Test,框架版本为。NET Framework4.5,点击确定
(2)引用JiebaSpider.dll库
在解决方案里右键引用,选择添加引用
点击浏览,找到JiebaSpider.dll文件,点击添加
(3)修改Form1的名字(避免和要调用的dll库里的Form1重名)
右键Form1视图,选择属性
在右侧属性栏中修改Name和Text为Form1_test
(4)创建一个Button组件,用来调用JiebaSpider的界面
(5)编写Form1.cs
具体代码如下:Form1.cs
using JiebaSpider;
using System;
using System.Windows.Forms;
namespace Test
{
public partial class Form1_test : Form
{
public Form1_test()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form1 form = new Form1();
this.Hide();
form.Show();//显示在JiebaSpider项目中设计的那个Form1窗体
}
}
}
(6)把JiebaSpider/bin/Debug文件夹下的Resources文件复制进Test/bin/Debug文件夹
这里是因为要调用jieba.NET里的方法需要用到Resources里的文件
(7)运行程序,点击开始测试按钮,调用成功
三、VC++调用c#动态链接库DLL
c++调用c#的动态链接库和c#调用操作类似,这里就不重复一遍了,接下来主要是从c++调用c#生成的COM组件角度分析
1. 用C#编写dll
(1)文件->新建->项目
选择类库(.NET Framework),命名为JiebaSpiderClass,框架为.NET Framework4.5,点击确定
(2)创建接口类
在解决方案里,右键项目->添加->新建项
选择接口,命名为IJieba.cs,点击添加
(3)编写代码
具体代码如下:
IJieba.cs:
using System.Runtime.InteropServices;
namespace JiebaSpiderClass
{
//可替换成自己的GUID
[Guid("7CA6063F-EFD1-4EE0-81CD-3C5E64A8894E")]
public interface IJieba
{
[DispId(1)]
void Split(string URL, out string strBuff);
[DispId(2)]
void Getkey(string strBuff, out string Text2);
}
}
Jieba.cs:
using JiebaNet.Analyser;
using JiebaNet.Segmenter;
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace JiebaSpiderClass
{
//可替换成自己的GUID
[Guid("9164BC25-F8F8-4192-935C-9B230241E63F")]
[ClassInterface(ClassInterfaceType.None)]
public class Jieba : IJieba
{
public void Split(string URL, out string strBuff)
{
string url = URL;
Uri httpURL = new Uri(url);
///HttpWebRequest类继承于WebRequest,并没有自己的构造函数,需通过WebRequest的Creat方法 建立,并进行强制的类型转换
HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(httpURL);
//httpReq.Headers.Add("cityen", "tj");
///通过HttpWebRequest的GetResponse()方法建立HttpWebResponse,强制类型转换
HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse();
///GetResponseStream()方法获取HTTP响应的数据流,并尝试取得URL中所指定的网页内容
///若成功取得网页的内容,则以System.IO.Stream形式返回,若失败则产生ProtoclViolationException错 误。
System.IO.Stream respStream = httpResp.GetResponseStream();
///返回的内容是Stream形式的,所以可以利用StreamReader类获取GetResponseStream的内容
System.IO.StreamReader respStreamReader = new System.IO.StreamReader(respStream, Encoding.UTF8);
//从流的当前位置读取到结尾
strBuff = respStreamReader.ReadToEnd();
//使用Jieba组件进行分词操作
//var segmenter = new JiebaSegmenter();
//var segments = segmenter.Cut(strBuff);
//Text1 = string.Join("/ ", segments);
respStreamReader.Close();
respStream.Close();
return;
}
public void Getkey(string strBuff,out string Text2)
{
//使用Jieba组件进行分词操作
var segmenter = new JiebaSegmenter();
var segments = segmenter.Cut(strBuff);
string Text1 = string.Join("/ ", segments);
Text2 = "";
var extractor = new TfidfExtractor();
//获取文档的关键词和权重,这里是提取了前十个关键名词或动词
var keywords = extractor.ExtractTagsWithWeight(Text1, 10, Constants.NounAndVerbPos);
foreach (var keyword in keywords)
{
Text2 += keyword.Word + "(" + String.Format("{0:F}", keyword.Weight) + ")" + " ";
}
return;
}
}
}
(4)创建GUID,替换上述文件里的GUID,两个文件不一样
点击工具->创建GUID
选择第5个,点击复制,替换文件里的GUID
(5)设置“使程序集COM可见”和“为COM互操作注册”
点击项目->JiebaSpiderClass属性
设置好后保存文件
(6)以管理员身份重新打开这个项目,生成动态链接库
由于注册表权限的问题,这里需要用到管理员权限
点击生成->生成解决方案
生成成功
在JiebaSpiderClass/bin/Debug文件夹下可以看到生成的JiebaSpiderClass.dll和JiebaSpiderClass.tlb文件
2. 将dll封装成COM组件
(1)新建一个项目,选择c++的动态链接库,命名为JiebaCom,点击确定
(2)把之前生成的JiebaSpiderClass.dll和JiebaSpiderClass.tlb文件复制进JiebaCom/JiebaCom文件夹下
(3)编辑JiebaCom.cs
具体代码如下:JiebaCom.cs
// JiebaCom.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
using namespace JiebaSpiderClass;
#define DLLEXPORT extern "C" __declspec(dllexport)
DLLEXPORT void Split(char* URL, char* str)
{
CoInitialize(NULL);
JiebaSpiderClass::IJiebaPtr JiebaPtr(__uuidof(Jieba));//获取Jieba所关联的GUID
BSTR temp;
JiebaPtr->Split(_bstr_t(URL), &temp);
strcpy(str, _com_util::ConvertBSTRToString(temp));
JiebaPtr->Release();
CoUninitialize();
}
DLLEXPORT void Getkey(char* str, char* Text)
{
CoInitialize(NULL);
JiebaSpiderClass::IJiebaPtr JiebaPtr(__uuidof(Jieba));//获取Calc所关联的GUID
BSTR temp;
JiebaPtr->Getkey(_bstr_t(str), &temp);
strcpy(Text, _com_util::ConvertBSTRToString(temp));
JiebaPtr->Release();
CoUninitialize();
}
(4)修改头文件stdafx.h
添加一句#import “JiebaSpiderClass.tlb”
具体代码如下:
// stdafx.h: 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 项目特定的包含文件
//
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
// 在此处引用程序需要的其他标头
#import "JiebaSpiderClass.tlb"
(5)配置预处理器,避免4996错误提示
点击项目->JiebaCom属性,打开属性页,点击C/C++目录下的预处理器,编辑预处理器定义,添加_CRT_SECURE_NO_DEPRECATE,点击确定
(6)设置项目字符集为“使用多字节字符集”
这里是为了避免一些格式不兼容的错误
点击常规->字符集,选择使用多字节字符集,点击应用,再点击确定
(7)生成解决方案
生成的JiebaCom.dll文件在Jieba/Debug文件夹下
3. c++调用c#封装成的COM组件
(1)创建一个新项目
选择Visual C++目录下的MFC/ATL(如果vs没有需要安装),选择MFC应用程序,命名为JiebaTest,点击确定
在MFC应用程序界面选择“基于对话框”的应用程序类型,选择完成
(2)编辑GUI界面
在解决方案资源管理器里打开资源文件目录,双击JiebaTest.rc文件
打开Dialog目录,双击IDD_JIEBATEST_DIALOG
创建好GUI组件,和上文的类似
这里设置分词结果的Edit Controller的格式,为可滑动的文本框
多行Multiple -> Ture
返回换行Want Return -> True
垂直滚动Vertical Scroll -> Ture
(3)编辑JiebaTestDlg.cpp文件
这里是需要声明方法:
typedef void(Split)(char URL, char* str);
typedef void(Getkey)(char str, char* text);
在视图界面双击“分词”按钮,编辑按钮的点击事件
这一块的具体代码如下:
void CJiebaTestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
char A[255];
char B[100000];
char C[100000];
CString str_A;
CString str_B;
CString str_C;
GetDlgItem(IDC_EDIT1)->GetWindowText(str_A);
strcpy(A, str_A);
strcpy(B, str_B);
strcpy(C, str_C);
HINSTANCE calc;
calc = LoadLibrary(TEXT("JiebaCom.dll"));
if (NULL == calc)
{
MessageBox("cant't find dll");
return;
}
Split _Split = (Split)::GetProcAddress(calc, "Split");
Getkey _Getkey = (Getkey)::GetProcAddress(calc, "Getkey");
if (NULL == _Split)
{
MessageBox("cant't find function Split");
return;
}
else
{
_Split(A, B);
SetDlgItemText(IDC_EDIT2, B);
//CString boxMsg;
//boxMsg.Format("分词结果:%s\n", B);
//MessageBox(boxMsg);
}
if (NULL == _Getkey)
{
MessageBox("cant't find function Getkey");
return;
}
else
{
_Getkey(B,C);
SetDlgItemText(IDC_EDIT3, C);
}
}
(4)添加动态链接库JiebaCom.dll
将JiebaCom.dll文件复制到JiebaTest/JiebaTest文件夹下
(5)运行程序,查看结果
四、总结
在本次实验中遇到了一些问题,现在总结一下:
(1)在利用jieba.NET包进行分词的时候,只添加jieba.NET包是不行的,还需要把jieba.NET文件夹下面的Resources文件夹复制到源程序的Debug文件夹下,否则使用会报错。
(2)在用VC调用c#生成动态链接库中,c#在编译动态链接库时报“无法注册程序集***dll- 拒绝访问。请确保您正在以管理员身份运行应用程序。对注册表项”***“的访问被拒绝。”的错误。引起这个问题的根本原因是win10的管理员权限问题,在win10系统中安装程序时,系统都会各种要管理员权限才能执行。
(3)最后一个问题是c++不管在调用.NET组件还是COM组件时,都不能正常的调用jieba.NET的方法,c++引入的.NET Framework框架是4.0版本的,而jieba.NET只有在.NET Framework4.5版本下才能正常工作,所以本次实验中,只实现了VC++调用c#的动态链接库里的爬虫方法,分词方法无法正常调用。