1 前言
1.1 CEF的作用
1.2 CEF的下载和编译
1.3 CEF结构
1.3.1 CEF进程和窗口之间的结构关系
1.3.2 Renderer进程的实现结构
1.3.3 browser进程的实现结构
1.4 CEF多进程和多线程
1.4.1 进程
1.4.2 线程
1.5 网页嵌入应用程序结构
1.5.1 入口函数
1.5.2 CEF单实例进程
1.5.3 主子进程的模式
1.6 CefBrowser 浏览器窗口类
1.7 CefBrowserHost浏览器操作控制类
1.8 CefFrame网页界面类
1.9 CefApp应用程序类
1.10 CEF引用计数
1.11 CEF自定义字符串
1.11.1 为什么自定义字符串类型
1.11.2 字符串操作函数CefString
1.11.3 CEF与String的转换
前言
1.1 CEF的作用
CEF全称Chromium Embedded Framework,是一个基于Google Chromium 的开源项目。Google Chromium项目主要是为Google Chrome应用开发的,而CEF的目标则是为第三方应用提供可嵌入浏览器支持。CEF作用是在客户端嵌入网页界面。
- 嵌入一个兼容HTML5的浏览器控件到一个已经存在的本地应用。
- 创建一个轻量化的壳浏览器,用以托管主要用Web技术开发的应用。
- 有些应用有独立的绘制框架,使用CEF对Web内容做离线渲染。
- 使用CEF做自动化Web测试。
1.2 CEF的下载和编译
https://blog.csdn.net/csdnyonghu123/article/details/87982333
如图所示,CefClient工程是一个简单版的网页浏览器demo,有网址输入、前进后退等。
CefSimple更简单的网页工程,网址在代码设置,打开 直接渲染网页。
CefTest测试工程。
AllBuild是一个伪工程,用来执行编译其他工程。
libcef_dll_wrapper是cef的动态库工程,。
include是头文件
Release文件夹包含输出的库文件和依赖库文件,自己开发项目时将include和release文件复制到工程即可。参考CefSimple和CefClient代码实例。
1.3 CEF结构
1.3.1 CEF进程和窗口之间的结构关系
一个浏览器有很多个CefBrowser窗口,这些窗口都是在Browser进程中创建。browser进程用来管理和处理回调函数消息。
Renderer进程用来实现网页的渲染,每个renderer进程包含有一个主网页mainframe和多个子网页subframe,。
1.3.2 Renderer进程的实现结构
renderer程序继承CefApp和CefRenderProcessHandler类,在main函数中初始化。通过CefSettings.browser_subprocess_path配置render可执行程序路径。browser进程就会去启动这个进程去渲染网页。
1.3.3 browser进程的实现结构
browserapp要继承CefApp和CefBrowserProcessHandler类。实现browserapp的定义。同时要新建clienthandler类实现图中的回调函数接口类,用来处理拦截响应请求、管理生命周期、下载、显示加载、右键菜单等。在mian函数中初始化、启动消息循环。调用CefBrowserHost的静态方法创建browser窗口对象,在render进程的Frame中加载渲染内容。
1.4 CEF多进程和多线程
1.4.1 进程
CEF3是多进程架构的,CEF3进程主要有一个Browser(浏览器)进程和多个Renderer(渲染)进程。Browser被定义为主进程,负责窗口管理,网络请求,网页管理 、网络交互。browser从服务器器请求到了响应,将html文本发送给Renderer 进程,render进程加载html,进行渲染,展示网页的内容;除此之外,Renderer进程还负责Js Binding和对Dom节点的访问。Browser和Renderer进程可以通过发送异步消息进行双向通信。主应用程序很大,加载时间比较长,或者不能在非浏览器进程里使用,则宿主程序可使用独立的可执行文件去运行这些Renderer进程。这可以通过配置CefSettings.browser_subprocess_path变量做到。
1.4.2 线程
Browser进程中包含如下主要的线程:
- TID_UI 线程是浏览器的主线程。如果应用程序在调用调用CefInitialize()时,传递CefSettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。
- TID_IO 线程主要负责处理IPC消息以及网络通信。
- TID_FILE 线程负责与文件系统交互。
1.5 网页嵌入应用程序代码构成和实例
每个CEF3应用程序都是相同的结构
- 提供入口函数,用于初始化CEF、运行子进程执行逻辑或者CEF消息循环。
- 提供CefApp实现,用于处理进程相关的回调。
- 提供CefClient实现,用于处理Browser实例相关的回调。
- 执行CefBrowserHost::CreateBrowser()创建一个Browser实例,使用CefLifeSpanHandler管理Browser对象生命周期。
1.5.1 入口函数
需要在程序的入口函数执行cef对象创建,初始化,命令行参数解析,CEF属性设置,和CEF消息循环开启等。CEF需要启动一个主进程来管理网页见面。这个主进程就是在man函数中启动,进入消息循环,直到程序结束,才退出main函数。
1.5.2 CEF单实例进程
单例进程模式就是CEF的启动,网页的打开都在
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
CefRefPtr<MyApp> app(new MyApp);
// Execute the sub-process logic, if any. This will either return immediately for the browser
// process or block until the sub-process should exit.
int exit_code = CefExecuteProcess(main_args, app.get());
if (exit_code >= 0) {
// The sub-process terminated, exit now.
return exit_code;
}
// Populate this structure to customize CEF behavior.
CefSettings settings;
// Initialize CEF in the main process.
CefInitialize(main_args, settings, app.get());
// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return 0;
}
1.5.3 主子进程的模式
主进程和子进程分开的模式,主进程运行browser进程,网页加载放在子进程render进程中。这是需要创建两个进程和两个主函数。
主程序的入口函数:
// Program entry-point function.
// 程序入口函数
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
// 传递命令行参数的结构体。
// 这个结构体的定义与平台相关。
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
// 可选择性地实现CefApp接口
CefRefPtr<MyApp> app(new MyApp);
// Populate this structure to customize CEF behavior.
// 填充这个结构体,用于定制CEF的行为。
CefSettings settings;
// Specify the path for the sub-process executable.
// 指定子进程的执行路径
CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);
// Initialize CEF in the main process.
// 在主进程中初始化CEF
CefInitialize(main_args, settings, app.get());
// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
// 执行消息循环,此时会堵塞,直到CefQuitMessageLoop()函数被调用。
CefRunMessageLoop();
// Shut down CEF.
// 关闭CEF
CefShutdown();
return 0;
}
子进程程序的入口函数:
// Program entry-point function.
// 程序入口函数
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
// 传递命令行参数的结构体。
// 这个结构体的定义与平台相关。
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
// 可选择性地实现CefApp接口
CefRefPtr<MyApp> app(new MyApp);
// Execute the sub-process logic. This will block until the sub-process should exit.
// 执行子进程逻辑,此时会堵塞直到子进程退出。
return CefExecuteProcess(main_args, app.get());
}
1.6 CefBrowser 浏览器窗口类
CefBrowser是浏览器窗口类,相当于浏览器的外壳框架窗口,包含向前、向后、加载、获取内部frame的等方法。调用CefBrowserHost的静态方法创建一个CefBrowser对象,表示一个网页窗口。
///
// Class used to represent a browser window. When used in the browser process
// the methods of this class may be called on any thread unless otherwise
// indicated in the comments. When used in the render process the methods of
// this class may only be called on the main thread.
///
/*--cef(source=library)--*/
class CefBrowser : public virtual CefBaseRefCounted {
public:
///
// Returns the browser host object. This method can only be called in the
// browser process.
///
/*--cef()--*/
virtual CefRefPtr<CefBrowserHost> GetHost() = 0;
///
// Returns true if the browser can navigate backwards.
///
/*--cef()--*/
virtual bool CanGoBack() = 0;
///
// Navigate backwards.
///
/*--cef()--*/
virtual void GoBack() = 0;
///
// Returns true if the browser can navigate forwards.
///
/*--cef()--*/
virtual bool CanGoForward() = 0;
///
// Navigate forwards.
///
/*--cef()--*/
virtual void GoForward() = 0;
///
// Returns true if the browser is currently loading.
///
/*--cef()--*/
virtual bool IsLoading() = 0;
///
// Reload the current page.
///
/*--cef()--*/
virtual void Reload() = 0;
///
// Reload the current page ignoring any cached data.
///
/*--cef()--*/
virtual void ReloadIgnoreCache() = 0;
///
// Stop loading the page.
///
/*--cef()--*/
virtual void StopLoad() = 0;
///
// Returns the globally unique identifier for this browser. This value is also
// used as the tabId for extension APIs.
///
/*--cef()--*/
virtual int GetIdentifier() = 0;
///
// Returns true if this object is pointing to the same handle as |that|
// object.
///
/*--cef()--*/
virtual bool IsSame(CefRefPtr<CefBrowser> that) = 0;
///
// Returns true if the window is a popup window.
///
/*--cef()--*/
virtual bool IsPopup() = 0;
///
// Returns true if a document has been loaded in the browser.
///
/*--cef()--*/
virtual bool HasDocument() = 0;
///
// Returns the main (top-level) frame for the browser window.
///
/*--cef()--*/
virtual CefRefPtr<CefFrame> GetMainFrame() = 0;
///
// Returns the focused frame for the browser window.
///
/*--cef()--*/
virtual CefRefPtr<CefFrame> GetFocusedFrame() = 0;
///
// Returns the frame with the specified identifier, or NULL if not found.
///
/*--cef(capi_name=get_frame_byident)--*/
virtual CefRefPtr<CefFrame> GetFrame(int64 identifier) = 0;
///
// Returns the frame with the specified name, or NULL if not found.
///
/*--cef(optional_param=name)--*/
virtual CefRefPtr<CefFrame> GetFrame(const CefString& name) = 0;
///
// Returns the number of frames that currently exist.
///
/*--cef()--*/
virtual size_t GetFrameCount() = 0;
///
// Returns the identifiers of all existing frames.
///
/*--cef(count_func=identifiers:GetFrameCount)--*/
virtual void GetFrameIdentifiers(std::vector<int64>& identifiers) = 0;
///
// Returns the names of all existing frames.
///
/*--cef()--*/
virtual void GetFrameNames(std::vector<CefString>& names) = 0;
///
// Send a message to the specified |target_process|. Returns true if the
// message was sent successfully.
///
/*--cef()--*/
virtual bool SendProcessMessage(CefProcessId target_process,
CefRefPtr<CefProcessMessage> message) = 0;
};
1.7 CefBrowserHost浏览器操作控制类
CefBrowserHost是CefBrowser的一个主对象,CefBrowser中有方法virtual CefRefPtr<CefBrowserHost> GetHost() = 0;返回CefBrowser的主CefBrowserHost对象。然而CefBrowser对象又是通过CefBrowserHost类的静态方法来创建的。CefBrowserHost类包含一些CefBrowser操作方法,相当于是控制类,可以控制CefBrowser的创建、获取、关闭;获取打开CefBrowser窗口的窗口句柄、请求上下文(RequestContext);下载、查找、鼠标、键盘触发事件、焦点控制、拖拽事件等。
class CefBrowserHost : public virtual CefBaseRefCounted {
public:
typedef cef_drag_operations_mask_t DragOperationsMask;
typedef cef_file_dialog_mode_t FileDialogMode;
typedef cef_mouse_button_type_t MouseButtonType;
typedef cef_paint_element_type_t PaintElementType;
///
// Create a new browser window using the window parameters specified by
// |windowInfo|. All values will be copied internally and the actual window
// will be created on the UI thread. If |request_context| is empty the
// global request context will be used. This method can be called on any
// browser process thread and will not block.
///
/*--cef(optional_param=client,optional_param=url,
optional_param=request_context)--*/
static bool CreateBrowser(const CefWindowInfo& windowInfo,
CefRefPtr<CefClient> client,
const CefString& url,
const CefBrowserSettings& settings,
CefRefPtr<CefRequestContext> request_context);
///
// Create a new browser window using the window parameters specified by
// |windowInfo|. If |request_context| is empty the global request context
// will be used. This method can only be called on the browser process UI
// thread.
///
/*--cef(optional_param=client,optional_param=url,
optional_param=request_context)--*/
static CefRefPtr<CefBrowser> CreateBrowserSync(
const CefWindowInfo& windowInfo,
CefRefPtr<CefClient> client,
const CefString& url,
const CefBrowserSettings& settings,
CefRefPtr<CefRequestContext> request_context);
///
// Returns the hosted browser object.
///
/*--cef()--*/
virtual CefRefPtr<CefBrowser> GetBrowser() = 0;
///
// Request that the browser close. The JavaScript 'onbeforeunload' event will
// be fired. If |force_close| is false the event handler, if any, will be
// allowed to prompt the user and the user can optionally cancel the close.
// If |force_close| is true the prompt will not be displayed and the close
// will proceed. Results in a call to CefLifeSpanHandler::DoClose() if the
// event handler allows the close or if |force_close| is true. See
// CefLifeSpanHandler::DoClose() documentation for additional usage
// information.
///
/*--cef()--*/
virtual void CloseBrowser(bool force_close) = 0;
///
// Helper for closing a browser. Call this method from the top-level window
// close handler. Internally this calls CloseBrowser(false) if the close has
// not yet been initiated. This method returns false while the close is
// pending and true after the close has completed. See CloseBrowser() and
// CefLifeSpanHandler::DoClose() documentation for additional usage
// information. This method must be called on the browser process UI thread.
///
/*--cef()--*/
virtual bool TryCloseBrowser() = 0;
///
// Set whether the browser is focused.
///
/*--cef()--*/
virtual void SetFocus(bool focus) = 0;
///
// Retrieve the window handle for this browser. If this browser is wrapped in
// a CefBrowserView this method should be called on the browser process UI
// thread and it will return the handle for the top-level native window.
///
/*--cef()--*/
virtual CefWindowHandle GetWindowHandle() = 0;
///
// Retrieve the window handle of the browser that opened this browser. Will
// return NULL for non-popup windows or if this browser is wrapped in a
// CefBrowserView. This method can be used in combination with custom handling
// of modal windows.
///
/*--cef()--*/
virtual CefWindowHandle GetOpenerWindowHandle() = 0;
//……………………
}
1.8 CefFrame网页界面类
每个CefBrowser窗口包含一个主CefFrame对象和若干个子Frame对象。主CefFrame相当于网页的主界面,子CefFrame相当于主页面的子页面。CefFrame包含获取网页上的文字、HTML源码字符串、网页名字标题、加载的url、所属的CefBrowser指针、V8Context等。还可以用LoadURL或LoadRequest加载页面。
// Class used to represent a frame in the browser window. When used in the
// browser process the methods of this class may be called on any thread unless
// otherwise indicated in the comments. When used in the render process the
// methods of this class may only be called on the main thread.
///
/*--cef(source=library)--*/
class CefFrame : public virtual CefBaseRefCounted {
public:
///
// True if this object is currently attached to a valid frame.
///
/*--cef()--*/
virtual bool IsValid() = 0;
///
// Execute undo in this frame.
///
/*--cef()--*/
virtual void Undo() = 0;
///
// Execute redo in this frame.
///
/*--cef()--*/
virtual void Redo() = 0;
///
// Execute cut in this frame.
///
/*--cef()--*/
virtual void Cut() = 0;
///
// Execute copy in this frame.
///
/*--cef()--*/
virtual void Copy() = 0;
///
// Execute paste in this frame.
///
/*--cef()--*/
virtual void Paste() = 0;
///
// Execute delete in this frame.
///
/*--cef(capi_name=del)--*/
virtual void Delete() = 0;
///
// Execute select all in this frame.
///
/*--cef()--*/
virtual void SelectAll() = 0;
///
// Save this frame's HTML source to a temporary file and open it in the
// default text viewing application. This method can only be called from the
// browser process.
///
/*--cef()--*/
virtual void ViewSource() = 0;
///
// Retrieve this frame's HTML source as a string sent to the specified
// visitor.
///
/*--cef()--*/
virtual void GetSource(CefRefPtr<CefStringVisitor> visitor) = 0;
///
// Retrieve this frame's display text as a string sent to the specified
// visitor.
///
/*--cef()--*/
virtual void GetText(CefRefPtr<CefStringVisitor> visitor) = 0;
///
// Load the request represented by the |request| object.
///
/*--cef()--*/
virtual void LoadRequest(CefRefPtr<CefRequest> request) = 0;
///
// Load the specified |url|.
///
/*--cef()--*/
virtual void LoadURL(const CefString& url) = 0;
///
// Load the contents of |string_val| with the specified dummy |url|. |url|
// should have a standard scheme (for example, http scheme) or behaviors like
// link clicks and web security restrictions may not behave as expected.
///
/*--cef()--*/
virtual void LoadString(const CefString& string_val,
const CefString& url) = 0;
///
// Execute a string of JavaScript code in this frame. The |script_url|
// parameter is the URL where the script in question can be found, if any.
// The renderer may request this URL to show the developer the source of the
// error. The |start_line| parameter is the base line number to use for error
// reporting.
///
/*--cef(optional_param=script_url)--*/
virtual void ExecuteJavaScript(const CefString& code,
const CefString& script_url,
int start_line) = 0;
///
// Returns true if this is the main (top-level) frame.
///
/*--cef()--*/
virtual bool IsMain() = 0;
///
// Returns true if this is the focused frame.
///
/*--cef()--*/
virtual bool IsFocused() = 0;
///
// Returns the name for this frame. If the frame has an assigned name (for
// example, set via the iframe "name" attribute) then that value will be
// returned. Otherwise a unique name will be constructed based on the frame
// parent hierarchy. The main (top-level) frame will always have an empty name
// value.
///
/*--cef()--*/
virtual CefString GetName() = 0;
///
// Returns the globally unique identifier for this frame or < 0 if the
// underlying frame does not yet exist.
///
/*--cef()--*/
virtual int64 GetIdentifier() = 0;
///
// Returns the parent of this frame or NULL if this is the main (top-level)
// frame.
///
/*--cef()--*/
virtual CefRefPtr<CefFrame> GetParent() = 0;
///
// Returns the URL currently loaded in this frame.
///
/*--cef()--*/
virtual CefString GetURL() = 0;
///
// Returns the browser that this frame belongs to.
///
/*--cef()--*/
virtual CefRefPtr<CefBrowser> GetBrowser() = 0;
///
// Get the V8 context associated with the frame. This method can only be
// called from the render process.
///
/*--cef()--*/
virtual CefRefPtr<CefV8Context> GetV8Context() = 0;
///
// Visit the DOM document. This method can only be called from the render
// process.
///
/*--cef()--*/
virtual void VisitDOM(CefRefPtr<CefDOMVisitor> visitor) = 0;
};
1.9 CefApp应用程序类
CefApp是应用程序类,网页嵌入程序都要实现这样一个应用程序类。提供了一些简单的接口:命令行参数修改,主题修改,获取进程句柄等。还有很多的子类,例如SimpleApp、ClientApp等,clientapp又有很多子类ClientAppBrowser、ClientAppRenderer、ClientAppOther,表示不同的应用类型:应用程序类定义如下:
class CefApp : public virtual CefBaseRefCounted {
public:
///对命令行参数修改
// Provides an opportunity to view and/or modify command-line arguments before
// processing by CEF and Chromium. The |process_type| value will be empty for
// the browser process. Do not keep a reference to the CefCommandLine object
// passed to this method. The CefSettings.command_line_args_disabled value
// can be used to start with an empty command-line object. Any values
// specified in CefSettings that equate to command-line arguments will be set
// before this method is called. Be cautious when using this method to modify
// command-line arguments for non-browser processes as this may result in
// undefined behavior including crashes.
///
/*--cef(optional_param=process_type)--*/
virtual void OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) {}
///
// Provides an opportunity to register custom schemes. Do not keep a reference
// to the |registrar| object. This method is called on the main thread for
// each process and the registered schemes should be the same across all
// processes.
///
/*--cef()--*/注册自定义主题
virtual void OnRegisterCustomSchemes(
CefRawPtr<CefSchemeRegistrar> registrar) {}
///
// Return the handler for resource bundle events. If
// CefSettings.pack_loading_disabled is true a handler must be returned. If no
// handler is returned resources will be loaded from pack files. This method
// is called by the browser and render processes on multiple threads.
///
/*--cef()--*/
virtual CefRefPtr<CefResourceBundleHandler> GetResourceBundleHandler() {
return NULL;
}
///
// Return the handler for functionality specific to the browser process. This
// method is called on multiple threads in the browser process.
///
/*--cef()--*/
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() {
return NULL;
}
///
// Return the handler for functionality specific to the render process. This
// method is called on the render process main thread.
///
/*--cef()--*/
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() {
return NULL;
}
};
1.10 CEF引用计数
CEF创建对象形式如CefRefPtr<SimpleApp> app(new SimpleApp);或者CefRefPtr<SimpleApp> app = new SimpleApp();创建的指针引用计数由CefRefPtr管理,CefRefPtr通过调用AddRef()和Release()方法自动管理引用计数。CefRefPtr定义如下:
using CefRefPtr = scoped_refptr<T>;
template <class T>
class scoped_refptr {
public:
typedef T element_type;
scoped_refptr() : ptr_(NULL) {}
scoped_refptr(T* p) : ptr_(p) {
if (ptr_)
ptr_->AddRef();
}
scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
if (ptr_)
ptr_->AddRef();
}
template <typename U>
scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
if (ptr_)
ptr_->AddRef();
}
~scoped_refptr() {
if (ptr_)
ptr_->Release();
}
T* get() const { return ptr_; }
// Allow scoped_refptr<C> to be used in boolean expression
// and comparison operations.
operator T*() const { return ptr_; }
T* operator->() const {
assert(ptr_ != NULL);
return ptr_;
}
scoped_refptr<T>& operator=(T* p) {
// AddRef first so that self assignment should work
if (p)
p->AddRef();
T* old_ptr = ptr_;
ptr_ = p;
if (old_ptr)
old_ptr->Release();
return *this;
}
scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
return *this = r.ptr_;
}
template <typename U>
scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
return *this = r.get();
}
void swap(T** pp) {
T* p = ptr_;
ptr_ = *pp;
*pp = p;
}
void swap(scoped_refptr<T>& r) { swap(&r.ptr_); }
protected:
T* ptr_;
};
1.11 CEF自定义字符串
1.11.1 为什么自定义字符串类型
libcef包和宿主程序可能使用不同的运行时,对堆管理的方式也不同。所有的对象,包括字符串,需要确保和申请堆内存使用相同的运行时环境。所以需要自定义字符串类型。
1.11.2 字符串操作函数CefString
CEF提供了一批C语言的方法来操作字符串(通过#define的方式来适应不同的字符编码)
cef_string_set 对制定的字符串变量赋值(支持深拷贝或浅拷贝)。
cef_string_clear 清空字符串。
cef_string_cmp 比较两个字符串。
1.11.3 CEF与String的转换
CefString支持与std::string(UTF8)、std::wstring(wide)类型的相互转换。也可以用来包裹一个cef_string_t结构来对其进行赋值。
(1)string转CefString
单字节
std::string str = “Some UTF8 string”;
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);
宽字节
std::wstring str = “Some wide string”;
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);
ASCII码
const char* cstr = “Some ASCII string”;
CefString cef_str;
cef_str.FromASCII(cstr);
(2)CefString转string
单字节
str = cef_str;
str = cef_str.ToString();
宽字节
str = cef_str;
str = cef_str.ToWString();
自己开发了一个股票智能分析软件,功能很强大,需要的点击下面的链接获取: