原文:http://www.cnblogs.com/clever101/archive/2009/12/14/1624204.html

本文转自博客园,此文作者依据codeproject英文版本翻译!

原文地址:http://www.codeproject.com/KB/cs/DotNetActiveX.aspx

原文作者:Daniel Yanovsky

翻译:朱金灿

 

本文介绍了怎样在C++工程集成C#窗口(Window Form)。

介绍

本文介绍如何在C++窗口中集成C#窗体(也被称为视窗窗体)。我们的C++窗口是使用纯粹的Win32 API函数创建的并不需要MFC。

背景

一般地,使用C#进行Windows编程比使用C++要容易得多,特别地当我们不能使用MFC库时。所以很多程序员喜欢使用C#环境建立他们的工程。但是有时候需要在C++环境下进行编程。例如:当你想在一个C++程序上开发一个附件软件时。我们解决这个问题的办法是为它开发一个插件,比如著名的Notepad++程序。Notepad++是使用C++语言编写的,具体是使用纯粹的Win32 API函数和STL以确保它具有较高的运行速度和更小的可执行文件。但是由此带来的弊端是设计和开发界面变得困难。所以,我们面对这个挑战决定使用C#创建我们的插件。我们怎样成功集成我们的.NET插件到使用纯粹的Win32 API函数创建的窗口?我希望这篇文章能帮你理解这一点。

我们为这篇文章准备了一个小示例。如果你希望见到我们为Notepad++编写的插件的全部源码,请访问我们的插件主页

使用C#语言创建ActiveX控件

基础知识

这部分的解决方案是基于由Morgan Skinner发表的文章《以ActiveX控件的形式暴露Windows窗体控件》。尽管Skinner提供他的解决方案是由Visual Studio 8 Beta版本开发的,但他的例子在VS8的发行版运行得也很好(只是做了小小改动)。下面是我们对Skinner的解决方案所作的改动的一个列表:

1.       设置ClassInterface为ClassInterfaceType.None(这样我们暴露唯一指定的接口到COM)。更多的信息在下一章。

2.       工程应该被设置为visible to COM具体是这样做的:在“Project Properties”菜单项上的“Application”选项卡上的Assembly Information对话框上的“Make assembly COM visible”被选中(具体如下图):

1.       你还应该注册工程为COM互操作(请注意在VS8.0的发行版,“Build”属性窗口和beta版本有着不同的设计)。当这个特性被选上,当工程成功编译之后Visual Studio将自动注册.NET ActiveX控件。(具体如下图)

4.       在Skinner的文章中,在ComUnregisterFunction()函数中有一个小错误。下面是正确的函数:


/// <summary>
/// Unregister ActiveX DLL function
/// </summary>
/// <param name="i_Key"></param>
[ComUnregisterFunction()]
public static void UnregisterClass(string i_Key)
{
    // strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
    StringBuilder sb = new StringBuilder(i_Key);
    sb.Replace(@"HKEY_CLASSES_ROOT\", "");     // open HKCR\CLSID\{guid} for write access
    RegistryKey registerKey = 
        Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);     // delete the 'Control' key, 
    // but don't throw an exception if it does not exist
    registerKey.DeleteSubKey("Control", false);     // next open up InprocServer32
    RegistryKey inprocServer32 = 
        registerKey.OpenSubKey("InprocServer32", true);     // and delete the CodeBase key, again not throwing if missing
    inprocServer32.DeleteSubKey("CodeBase", false);     // finally close the main key
    registerKey.Close();
}    

为COM导出明确的方法

为了进行更为精确的设计,我们为COM导出指定的方法。每一个使用我们控件的外部程序都将仅仅访问它们必需的方法。

导出具体方法的最好方法是为包含所有相关方法创建一个接口。那么,具体的属性应该被添加到这个接口。Form类应该在这个接口实现。


/// <summary>
/// COM Interface - enables to run c# code from c++
/// </summary>
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICSSExplorerInterface
{
    void setButtonCaption(String strNewCaption);
    void setAdapterDllPtr(IntPtr i_AdapterDllPtr);
}

使用微软消息体系

我们使用微软消息体系和VC工程的容器窗口和其他窗口进行通信。我们不处理任何事件,因为如果这样它将变得更为复杂并且对于我们的解决方案它不是必需的。

我们增加下面代码到我们的MyDotNetActiveX类去允许消息传输:


private static uint DOT_NET_BUTTON_PRESSED = 0x0400;

private void btnOK_Click(object sender, EventArgs e)
{
   SendMessage(m_AdapterDllPtr.ToInt32(), 
    DOT_NET_BUTTON_PRESSED, IntPtr.Zero, IntPtr.Zero);
} #region MAPPING_OF_USER32_DLL_SECTION [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(
    int hwnd, uint wMsg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
    int hwnd, uint wMsg, int wParam, string lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
    int hwnd, uint wMsg, int wParam, out int lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int GetNbFiles(
    int hwnd, uint wMsg, int wParam, int lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int GetFileNames(
    int hwnd, uint wMsg,
    [MarshalAs(UnmanagedType.LPArray)]IntPtr[] wParam,
    int lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
    int hwnd, uint wMsg, int wParam, StringBuilder lParam); #endregion    

在初始化代码中,我们假设我们的容器窗口将传给它的窗口句柄(hwnd参数)来通讯。

编译工程

现在我们准备编译和测试这个控件。Visual Studio在成功编译之后将自动注册我们的ActiveX控件。你可以通过免费软件RegDllView查看注册信息。

在ActiveX控件测试容器中测试控件

在我们跳到这篇文章的下一步之前,在第三方程序中测试我们的控件是一段美好时光。我们使用ActiveX控件测试容器(tstcon32.exe)进行测试。这个程序在Visual Studio的安装目录下是可以找得到的。

1. 通过“Edit”菜单栏的“Insert New Control”菜单项插入控件

1. 现在选择“Control”菜单栏中的“Invoke Methods”菜单项。

2. 在Method Name combo-box控件选择setButtonCaption函数。

3. 在Parameter Value文本框中输入“Hello”并按下“Invoke”按钮

5. 下面是测试结果

添加 C# ActiveX 控件to C++ 窗口

使用ATL Control Containment

任何通过使用Active Template Library (ATL)都可以包含ActiveX控件。

在这部分指南里,我们将完成下面工作:

  1. 创建一个C++ Win32 Application 工程
  2. 插入 our ActiveX 控件到C++ 窗口
  3. 发送命令到 ActiveX控件
  4. 从我们的 ActiveX控件中接收消息

创建 C++ Win32 Application 工程

  1. 创建一个新的 Win32 工程并命名为“CPP_Container:”:

2.采用默认设置并按下“OK”按钮:

插入 C# ActiveX 控件到C++ 窗口

  1. 添加下面代码到CPP_Container.cpp开头:

#define DOT_NET_BUTTON_PRESSED  0x0400

HWND                                            _hAtl;
HWND                                            _hSelf;
IUnknown*                                       _pUnk;
DotNetActiveX::ICSSExplorerInterfacePtr         _pDotNetCOMPtr; HINSTANCE _hWebLib = ::LoadLibrary(TEXT("ATL.DLL")); 

2.当Visual Studio编译完我们的C#工程,它创建了DotNetActiveX.tlb文件。这个文件包含了这个工程的所有方法和结构体。我们将通过下面命令导入这些数据:

// import C# control function and structures
#import "DotNetActiveX.tlb" named_guids raw_interfaces_only

3.添加下面函数到CPP_Container.cpp。这个函数插入ATL容器到窗口并加载我们的C# ActiveX控件:


  void loadActiveX(LPCTSTR strActiveXName)
{
    //Initialize ATL control containment code.
    BOOL (WINAPI *m_AtlAxWinInit)();
    m_AtlAxWinInit = (BOOL (WINAPI *)(void))::GetProcAddress
                (_hWebLib, "AtlAxWinInit");
    m_AtlAxWinInit();     // Get the dimensions of the main window's client 
    // area, and enumerate the child windows. Pass the 
    // dimensions to the child windows during enumeration. 
    RECT rcClient;      GetClientRect(_hSelf, &rcClient);      _hAtl = ::CreateWindowEx(
            WS_EX_CLIENTEDGE,\
            TEXT("AtlAxWin"),\
            strActiveXName,\
            WS_CHILD | WS_VISIBLE | /*WS_CLIPCHILDREN | */WS_EX_RTLREADING,\
            0, 0, rcClient.right, rcClient.bottom,\
            _hSelf,\
            NULL,\
            NULL,\
            NULL);     if (!_hAtl)
    {
        MessageBox( NULL, TEXT("Can not load AtlAxWin!"), 
                szTitle, MB_OK | MB_ICONSTOP);
        throw int(106901);
    }     HRESULT (WINAPI *m_AtlAxGetControl) (HWND h, IUnknown** pp);
    m_AtlAxGetControl = (HRESULT (WINAPI *) 
        (HWND, IUnknown**))::GetProcAddress(_hWebLib, "AtlAxGetControl");     m_AtlAxGetControl(_hAtl, &_pUnk);     _pUnk->QueryInterface(__uuidof(DotNetActiveX::ICSSExplorerInterface),
                    (LPVOID *) &_pDotNetCOMPtr);     if (_pDotNetCOMPtr != NULL)
    {
        _pDotNetCOMPtr->setAdapterDllPtr((long) _hSelf);
    }
    else
    {
        // Get the dimensions of the main window's client 
        // area, and enumerate the child windows. Pass the 
        // dimensions to the child windows during enumeration. 
        RECT rcClient; 
        GetClientRect(_hSelf, &rcClient);         ::DestroyWindow(_hAtl);
        _hAtl = ::CreateWindowEx(
                    WS_EX_CLIENTEDGE,\
                    TEXT("AtlAxWin"),\
                    TEXT("MSHTML:""Please register ActiveX 
                    control before using this plugin."""),\
                    WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | 
                    WS_EX_RTLREADING,\
                    0, 0, rcClient.right, rcClient.bottom,\
                    _hSelf,\
                    NULL,\
                    NULL,\
                    NULL);
    }
}  

4.为了更准确的开发,在WndProc函数中添加下面代码到WM_DESTORY消息处理块中(注:为了销毁C# ActiveX控件窗口和释放加载的内存)。

_pDotNetCOMPtr->Release();
::DestroyWindow(_hAtl);
_pUnk->Release(); 
::FreeLibrary(_hWebLib); 

5. 最后在_tWinMain函数调用loadActiveX函数。

loadActiveX(TEXT("DotNetActiveX.MyDotNetActiveX")); 

发送命令到C# ActiveX控件

在插入TLB文件之后,我们在C#工程中导出的所有方法将会显示。现在我们简单地调用相关的方法:

char *strHelloWorld = "Hello World!";
_bstr_t bstrHelloWorld(strHelloWorld);
_pDotNetCOMPtr->setButtonCaption(bstrHelloWorld);

这将会把按钮的标题改为“Hello World!”。

从C# ActiveX控件接收消息

通过微软的消息体系从C#控件的消息能够到达C++窗口。通过调用loadActiveX函数我们已经将我们的窗口句柄发给C#控件。所以现在,我们仅仅需要在WndProc函数添加一些代码(注:消息处理代码)。WndProc函数是负责处理到达该窗口的每个消息。所以我们将在这个函数添加一个另外的case分支:

case DOT_NET_BUTTON_PRESSED:
    MessageBox(NULL, TEXT("Message from C# arrived: Button Pressed!!"), 
    szTitle, MB_OK | MB_ICONINFORMATION);
    break;

现在,你可以按下在C# ActiveX控件的按钮并看到下面结果:

结论

我们希望这篇文章能够对那些在工程中混合使用C#和C++语言的开发者有所帮助。你可以查看我们开发的Notepad++插件:CSSExplorer plug-in for Notepad++。上面提到的所有想法在我们的插件都得到了实现。

参考文献

  1. C# Programming Guide - Example COM Class (C# Programming Guide)
  2. How to add ATL control containment support to any window in Visual C++
  3. Exposing Windows Forms Controls as ActiveX controls

历史

  • 2009年10月10日: 初次提交

授权

这篇文章,包括所有相关源码和文件,遵循The Code Project Open License (CPOL)授权协议。

关于作者

Daniel Yanovsky :职业:软件开发工程师。国籍:以色列。

【转载】怎样在C++工程中集成C#窗口的更多相关文章

  1. react native 之 在现有的iOS工程中集成react native

    在现有的iOS工程中集成react native, 或者说将react native引入到iOS 项目,是RN和iOS混合开发的必经之路 参考官网教程:https://reactnative.cn/d ...

  2. 现有工程中集成Cordova

    cocoapods引入cordova源码 1.依赖Cordova和wk插件 pod 'Cordova' pod 'cordova-plugin-wkwebview-engine' 建立Cordova支 ...

  3. iOS中集成ijkplayer视频直播框架

    ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...

  4. VS工程中添加c/c++工程中外部头文件及库的基本步骤

    转载自 在VS工程中,添加c/c++工程中外部头文件及库的基本步骤: 1.添加工程的头文件目录:工程---属性---配置属性---c/c++---常规---附加包含目录:加上头文件存放目录. 2.添加 ...

  5. 把 Reative Native 47 版本集成到已有的 Native iOS 工程中

    一.搭建开发环境 http://reactnative.cn/docs/0.46/getting-started.html#content 二.创建一个模板 运行以下命令,创建一个最新版本的 reac ...

  6. [转载]android工程中引入另一个工程中的资源

    原文地址:android工程中引入另一个工程中的资源作者:87fayuan 在项目中可能遇到这样的问题:项目过大,于是细分为N个子模块来做,每个模块都是不同的工程.涉及到activity传数据时,可以 ...

  7. 在WebStorm中集成Karma+jasmine进行前端单元测试

    在WebStorm中集成Karma+jasmine进行前端单元测试 前言 好久没有写博了,主要还是太懒=.=,有点时间都去带娃.看书了,今天给大家分享一个原创的小东西,如果大家对TDD或者BDD有兴趣 ...

  8. 如何在 ASP.NET MVC 中集成 AngularJS(2)

    在如何在 ASP.NET MVC 中集成 AngularJS(1)中,我们介绍了 ASP.NET MVC 捆绑和压缩.应用程序版本自动刷新和工程构建等内容. 下面介绍如何在 ASP.NET MVC 中 ...

  9. 关于在工程中添加新文件时的LNK2019错误的一个解决办法

    我这几天一直在研究Qt的串口程序,在读懂了官方给出的实例程序后我决定把其多线程的串口监视程序加入到我自己的工程中,便直接把问价复制到自己的工程下面,在Qt中加入到自己的工程中,但是总是出现LNK201 ...

随机推荐

  1. Oracle EBS 应收API只创建收款没有核销行以及消息堆栈

    只创建了收款但没有创建核销行 排除其他原因 有可能是缓存溢出导致的这个要改成true 且使用消息堆栈处理

  2. Vue2学习笔记:class和style

    1.用法 <!DOCTYPE html> <html> <head> <title></title> <meta charset=&q ...

  3. Redis学习---Redis操作之List

    List操作,redis中的List在在内存中按照一个name对应一个List来存储 lpush(name,values) --> 实际上是左添加 # 在name对应的list中添加元素,每个新 ...

  4. 铁乐学python_Day44_IO多路复用

    目录 IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO ...

  5. 跟我一起阅读Java源代码之HashMap(一)

    最近闲的很,想和大家一起学习并讨论下Java的一些源代码以及其实现的数据结构, 不是什么高水平的东西,有兴趣的随便看看 1. 为什么要用Map,以HashMap为例 很多时候我们有这样的需求,我们需要 ...

  6. jQuery 和 YUI (Yahoo User Interface) 各自的优缺点有哪些?具体的使用场景是怎样的?

    张经纬,前端工程师 知乎用户.赵勇杰.知乎用户 等人赞同 其实jQuery和YUI的侧重点是不一样的. jQuery专注于DOM的操作,他通过继承的方式给传入的对象增加了新的方法,从而使我们可以通过链 ...

  7. 【python库安装问题解决】UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 121: invalid start byte

    好久没用python了...今天随便pip安装个库突然报错: Exception:‘’ (most recent call last):  File "C:\ProgramData\Anac ...

  8. CF585D Lizard Era: Beginning

    嘟嘟嘟 题面我是不会咕的(没有真香):有\(n(n \leqslant 25)\)个任务和三个人,每次任务给出每个人能得到的值,每次任务选两个人,使\(n\)个任务结束后三个人得到的值是一样的,且尽量 ...

  9. 基于Naive Bayes算法的文本分类

    理论 什么是朴素贝叶斯算法? 朴素贝叶斯分类器是一种基于贝叶斯定理的弱分类器,所有朴素贝叶斯分类器都假定样本每个特征与其他特征都不相关.举个例子,如果一种水果其具有红,圆,直径大概3英寸等特征,该水果 ...

  10. 使用ROS节点——Node & Master——roscore、rosrun、rosnode

    1.Node 在ROS的世界里, 最小的进程单元就是节点( node) . 一个软件包里可以有多个可执行文件, 可执行文件在运行之后就成了一个进程(process), 这个进程在ROS中就叫做节点. ...