c#互操作


目录(?)[+]

C++以她的普及率,复杂度和高效率为大家所用,C#作为.NET 的代表语言,与C++相比,是完全面向对象的语言,但C#更广为人知是因 为.NETFrameWork提供的强大的库,使C#能够实现快速开发的利器。综合使用C++和C#使我们可以使用更加合理的成本开发出用户更加满意的产品。
优点:
1)快速,高效:有效的结合了C++的高性能和.NET的高效率
2)安全:防止.NET写的核心代码被反编译,增强软件知识产权的保护

基础:
1)C++DLL 
2)C++COM
3)C# DLL COM
4)C++/CLI
5)C++与.NET中基础类型的对应及转化
交互技巧:
1)C++中使用.NET FrameWork
2)C++调用C#的COM(转载) 
3)C++中通过C++/CLI调用.NET编写的DLL

1)C#通过P/Iovoke调用WIN32API 
3)C#调用C++的COM
2)C#通过C++/CLI调用C++的DLL 

高级:
1)Struct在P/Invoke中的转化
2)C++和C#混编为一个assembly(转载)
3)平台调用封送数据 http://msdn2.microsoft.com/zh-cn/library/fzhhdwae(VS.80).aspx
4)如何:映射 HRESULT 和异常 http://msdn2.microsoft.com/zh-cn/library/9ztbc5s1(VS.80).aspx
实例部分:
1)一个CLI/C++的DLL同时被C#的exe和NativeC++的exe调用 
2)CLI/C++中混合类的使用 
3) NativeC++通过CLI调用C#的Form 
4)C#的Form通过CLI调用C++的DLL 
别人文章:
1 ) CLI中native的string和System::String转化 

参考资料:
1)标准文档:http://www.ecma-international.org/publications/standards/Ecma-372.htm
2)A DesignRationale for C++/CLI: http://www.gotw.ca/publications/C++CLIRationale.pdf
3)Stan Lippman'sBLog:http://blogs.msdn.com/slippman/

互操作性三种方式:在.NET应用程序中使用COM对象;在COM客户程序中使用.NET组件;调用本地方法的Platform Invoke(平台调用)

C#调用C++的COM

C#调用C++的COM比较简单,看下面的简单实例。

一 建立ATL COM ,增加接口ITest和实现函数Test,增加以下函数:
idl文件:

interface ITest : IDispatch                              {
[id(1), helpstring("method ADD")] HRESULT ADD([in] LONG x, [in] LONG y, LONG* z);
[id(2), helpstring("method UpperCase")] HRESULT UpperCase([in] CHAR A, CHAR* B);
[id(3), helpstring("method LowerCase")] HRESULT LowerCase([in] BSTR A, BSTR* B);
[id(4), helpstring("method Change")] HRESULT Change([in] VARIANT A, [out] VARIANT* B);
};

ITest文件:

MIDL_INTERFACE("52CA8A5C-593D-4E2E-B58F-BB6C6604EAF2")
ITest : public IDispatch
{
public:
virtual /**//*[helpstring][id] */ HRESULT STDMETHODCALLTYPEADD( 
/**//* [in] */ LONG x,
/**//* [in] */ LONG y,
LONG *z) = 0;

virtual /**//*[helpstring][id] */ HRESULT STDMETHODCALLTYPEUpperCase( 
/**//* [in] */ CHAR A,
CHAR *B) = 0;

virtual /**//*[helpstring][id] */ HRESULT STDMETHODCALLTYPELowerCase( 
/**//* [in] */ BSTR A,
BSTR *B) = 0;

virtual /**//*[helpstring][id] */ HRESULT STDMETHODCALLTYPEChange( 
/**//* [in] */ VARIANT A,
/**//* [out] */ VARIANT *B) =0;
};
在Test中实现以上接口函数。
二 在C#中的使用方法
1)使用IDE中的reference来reference com,这个之前com必须先注册:
 

2)使用命令TLBIMP ATLCOM.tlb /out:C:ATLCOM.dll ,然后referece生成的DLL(tblmp命令帮你注册com)。

3)TypeLibConverter类 
TypeLibConverter 类(位于 System.Runtime.InteropServices命名空间中)提供了将类型库中的 coclass 和接口转换为程序集中的元数据的方法。此 API 将生成与 Tlbimp.exe 相同的元数据输出。不过,与Tlbimp.exe 不同的是,TypeLibConverter 类可以将内存中的类型库转换为元数据。

4)自定义包装 
当类型库不可用或不正确时,一种可选的做法是在托管源代码中创建类或接口的重复定义。然后,用面向运行库的编译器来编译源代码以生成程序集中的元数据。 
要手动定义 COM 类型,必须具备下列各项:

所定义的 coclass 和接口的精确描述。
可生成正确 .NET Framework 类定义的编译器,如 C# 编译器。
有关类型库到程序集转换规则的知识。

编写自定义包装是一种较少使用的高级技术。有关生成自定义包装的其他信息,请参见自定义标准包装。

三 在C#的client调用COM提供的接口,我们可以看到在C#中COM的接口和函数被变换为如下:
Member of atlcom.TestClass:
public TestClass(); 
public virtual void ADD(int x, int y, ref int z);
public virtual void UpperCase(sbyte A, ref sbyte B);
public virtual void Change(object A, out object B) ; 
public virtual void LowerCase(string A, ref string B);

四 更多可以参考:http://www.codeproject.com/dotnet/cominterop.asp

 

 

C++调用C#的COM(转载)

 

http://www.codeproject.com/KB/cs/ManagedCOM.aspx

Downloadsource – 5.21 Kb

Preface

COM Interoperability is thefeature of Microsoft .NET that allows managed .NET code to interact withunmanaged code using Microsoft's Component Object Model semantics.

This article is geared towards C#programmers who are familiar with developing COM components and familiar withthe concept of an interface. I'll review some background on COM, explain how C#interacts with COM, and then show how to design .NET components to smoothlyinteract with COM.

For those die-hard COM experts,there will be some things in this article that are oversimplified, but theconcepts, as presented, are the important points to know for those developerssupplementing their COM code with .NET components.

Introduction

.NET Interfaces and Classes

The basis for accessing .NETobjects either from other .NET code or from unmanaged code is the Class. A .NETclass represents the encapsulation of the functionality (methods andproperties) that the programmer wants to expose to other code. A .NET interfaceis the abstract declaration of the methods and properties that classes whichimplement the interface are expected to provide in their implementations.Declaring a .NET interface doesn't generate any code, and a .NET interface isnot callable directly. But any class which implements ("inherits")the interface must provide the code that implements each of the methods and propertiesdeclared in the interface definition.

Microsoft realized that the veryfirst version of .NET needed a way to work with the existing Windows technologyused to develop applications over the past 8+ years: COM. With that in mind,Microsoft added support in the .NET runtime for interoperating with COM -simply called "COM Interop". The support goes both ways: .NET codecan call COM components, and COM code can call .NET components.

Using the code

Steps to create a Managed .NET C#COM Object:

  1. Open VS.NET2003->New Project->Visual C#     Projects->Class Library.

  2. Project name: MyInterop.

  3. Create MyDoNetClass.cs file, and add     the following lines of code:

using System.Runtime.InteropServices;
using System.Windows.Forms;
  1. Create an Interface IMyDotNetInterface.

  2. Create a class MyDoNetClass.

  3. Add the following line for MyDotNetClass:

 [ClassInterface(ClassInterfaceType.None)]

Although a .NET class is notdirectly invokable from unmanaged code, Microsoft has provided the capabilityof wrapping a .NET interface in an unmanaged layer of code that exposes themethods and properties of the .NET class as if the class were a COM object.There are two requirements for making a .NET class visible to unmanaged code asa COM object:

Requirement 1:

You have to add GUIDs – GloballyUnique Identifiers – into your code for the interface and the class separately,through a GUID tool.

  1. Now, create a GUID for the Interface, and add     the following line for the interface:

 [Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
  1. Now, create a GUID for the class, and add the     following line for the class:

 [Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
  1. Your code will look like:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
 
namespace MyInterop
{
    [Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
    interface IMyDotNetInterface
    {
        void ShowCOMDialog();
    }
     
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
    class MyDotNetClass : IMyDotNetInterface
    {
        // Need a public default constructor for COM Interop.
 
        public MyDotNetClass()
        {}
        public void ShowCOMDialog()
        {
            System.Windows.Forms.MessageBox.Show("I am a" +
                  "  Managed DotNET C# COM Object Dialog");
        }
    }
}
  1. Compile the solution.

  2. You will see inside the project     directory->obj->debug directory, the file �MyInterop.dll� generated     after compilation.

Requirement 2:

Registration of the COM Class andInterfaces

For a COM class to be accessibleby the client at runtime, the COM infrastructure must know how to locate thecode that implements the COM class. COM doesn't know about .NET classes, but.NET provides a general "surrogate" DLL – mscoree.dll — whichacts as the wrapper and intermediary between the COM client and the .NET class.

  1. Hard-code a specific version number in your AssemblyVersion attribute in the AssemblyInfo.cs     file which is in your project.

Example:

 [assembly: AssemblyVersion("1.0.0.0")]
  1. Create a strong-name key pair for your     assembly and point to it via the AssemblyKeyFile attribute in theAssemblyInfo.cs     file which is in your project. Example:

sn -k TestKeyPair.snk
 [assembly: AssemblyKeyFile("TestKeyPair.snk")]
  1. Add your assembly to the GAC using the     following command:

gacutil /i MyInterop.dll
  1. Register your assembly for COM by using the     REGASM command along with the "/tlb" option to generate a COM     type library.

REGASM MyInterop.dll /tlb:com.MyInterop.tlb
  1. Close the C# project.

Steps to create an Unmanaged C++application to call a .NET Managed C# COM

  1. Open VS.NET2003->New Project->Visual C++     Projects->Win32->Win32 Console Project.

  2. Name: DotNet_COM_Call.

  3. Include the following line in your DoNet_COM_Call.cpp     file:

#import �<Full Path>com.MyInterop.tlb" named_guids raw_interfaces_only
  1. Compile the solution.

  2. It will generate a �com.myinterop.tlh�     file into your project->debug directory.

  3. You can open this file and see the contents.     This is basically the proxy code of the C# COM code.

  4. Now, you can write the code to call the .NET     Managed COM.

  5. Please add the following lines of code before     calling the COM exported functions:

CoInitialize(NULL);   //Initialize all COM Components
 
    
// <namespace>::<InterfaceName>
 
MyInterop::IMyDotNetInterfacePtr pDotNetCOMPtr;
 
// CreateInstance parameters
 
// e.g. CreateInstance (<namespace::CLSID_<ClassName>)
 
HRESULT hRes =
  pDotNetCOMPtr.CreateInstance(MyInterop::CLSID_MyDotNetClass);
if (hRes == S_OK)
{
    BSTR str;
    pDotNetCOMPtr->ShowCOMDialog ();
    //call .NET COM exported function ShowDialog ()
 
}
 
CoUninitialize ();   //DeInitialize all COM Components
  1. Run this console application.

  2. Expected result: a managed code (C# ) dialog     should appear with the string �I am a Managed DotNET C# COM Object Dialog�.    

Points of Interest

While creating an Interface forCOM exported functions, creating GUIDs for the Interface and the class andregistering the class are required steps, and doing all this is alwaysinteresting and fun. Calling parameterized exported functions also is veryinteresting.

 

C#通过P/Iovoke调用WIN32 API 
1, PInvoke什么意思? Platform Invocation Services
2, 干什么用?导入外部函数?什么是外部函数,就是不属于.Net托管的函数。
3,如何用?看下面的例子。用[DllImport(dllname)]来实现,但是首先要把System.Runtiime.InteropServices using进来。但是不using也行,就要敲全称,随你便了。
[DllImport("user32.dll")]

        static extern int MessageBoxA(int hWnd,

                                    string msg,

                                    string caption,

                                    int type );

        private void button1_Click(object sender,System.EventArgs e)

        {

            MessageBoxA(0, "Msg:hello", "Caption:Hello",0 );

        }
4,万一我的程序中已经有了一个函数叫MessageBoxA怎么办?这时候,可以使用EntryPoint来帮忙,下面的例子中,你把自己的函数定义为MyMsg.
[DllImport("user32.dll",EntryPoint="MessageBoxA")]

        static extern int MyMsg(int hWnd,

                                    string msg,

                                    string caption,

                                    int type );

        private void button1_Click(object sender,System.EventArgs e)

        {

            MyMsg(0, "Msg:hello", "Caption:Hello",0 );

        }

5,charset如何使用?****A的是Ansi编码,****W的是unicode编码,如何使用charset,看你的函数调用而定。2K以后都用unicode了,前面的9x都是ansi编码,但是这是缺省的,微软给9x打布丁支持unicode不算。

API有两个版本: A(ASNI)版本和W(Unicode)版本. A版本调用时候会用ANSI来封送字符串,一般是win95/98上。W版本用Unicode来封送,在NT,2K和XP上。

.Net和win32交互的时候,默认是使用CharSet.Ansi来传送。

在DllImportAttribute.ExactSpelling 字段为 true 时(它是 Visual Basic .NET 中的默认值),平台调用将只搜索您指定的名称。例如,如果指定 MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 托管扩展和 C# 中的默认值),平台调用将首先搜索未处理的别名 (MessageBox),如果没有找到未处理的别名,则将搜索已处理的名称 (MessageBoxA)。请注意,ANSI 名称匹配行为与 Unicode 名称匹配行为不同。

        //CharSet.Ansi will call MessageBoxA

        //CharSet.Unicode will call MessageBoxW

        [DllImport("user32.dll",EntryPoint="MessageBox",CharSet=CharSet.Ansi)]

        static extern int MyMsg(int hWnd,

                                    string msg,

                                    string caption,

                                    int type );

        private void button1_Click(object sender,System.EventArgs e)

        {

            MyMsg(0, "Msg:hello", "Caption:Hello",0 );

        }

6,Dll里面的callback函数如何实现?看下面这个例子:
delegate bool CallBackDef( int hWnd, int lParm );

        [DllImport("user32.dll")]

            static extern int GetWindowText( int hWnd,

                                            StringBuildertext,

                                            int count );

        [DllImport("user32.dll")]

            static extern intEnumWindows(CallBackDef callback, int lParam );

        static bool PrintWindow(int hWnd, int lParm )

        {

            StringBuildertext = newStringBuilder(255);

            GetWindowText(hWnd, text, 255 );

            Console.WriteLine(text.ToString() );

            return true;

        }

        private void button1_Click(object sender, System.EventArgse)

        {

            CallBackDefcallBack = newCallBackDef( PrintWindow );

            EnumWindows(callBack, 0 );

        }

7,MarshalAs如何用,什么时候用?
在MessageBox传递string去Dll的时,C#编译器知道Win32LPSTR等价与一个C#字符串。但是如果想覆盖默认.Net行为, 这时候就需要MarshallAs
[DllImport("user32.dll", CharSet=CharSet.Unicode )]

            static extern int MessageBox( int hWnd,

                                        [MarshalAs(UnmanagedType.LPWStr)]

                                        string msg,

                                        [MarshalAs(UnmanagedType.LPWStr)]

                                        string caption,

                                        int type);

8,我怎么知道要调用的函数在那个dll了?
这个问题我不会比你更清楚,特殊的函数应该在你特殊的dll中。Win32中常用的几个dll是user32.dll, kernel32.dll和GDI32.dll.用dumpbin -exports kernel32.dll可以看到这个dll所有的API函数。

9,相互之间传递struct怎么办?我是说传递很复杂的struct?
传递一个结构,这个要用到StructLayoutAttribute属性。比如:

PtInRect 具有以下非托管签名:
BOOL PtInRect(const RECT *lprc, POINT pt);
请注意,由于函数需要指向 RECT 类型的指针,必须通过引用来传递 Rect 结构。
usingSystem.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]

public structPoint

{

     public int x;

     public int y;

}

[StructLayout(LayoutKind.Explicit)]

public structRect

{

     [FieldOffset(0)] public int left;

     [FieldOffset(4)] public inttop;

     [FieldOffset(8)] public intright;

     [FieldOffset(12)] public intbottom;

}

classWin32API

{

     [DllImport("User32.dll")]

     public static extern boolPtInRect(refRect r, Point p);

}
MarshalAs是提供向非托管代码封送数据时的规则。通过MarshalAs特性告诉.NET应该封送成什么类型。Marshal就是把一个结构(类)序列化成一段内存,然后送到另一个进程中,供另一个进程中的函数使用。

StructLayout控制类或结构的数据字段在托管内存中的物理布局,如果要将类或结构传递给需要指定布局的非托管代码,则需要显式控制类或结构的布局。
控制类或结构内存布局的参数由一个枚举类型LayoutKind指定,其中,有两个枚举值比较常用:

LayoutKind.Sequential:用于强制将成员按其出现的顺序进行顺序布局; 
LayoutKind.Explicit:用于控制每个数据成员的精确位置。利用Explicit,每个成员必须使用FieldOffset指示此字段在类型中的位置。如:
[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)] 
public class MySystemTime

[FieldOffset(0)]public ushort wYear;
[FieldOffset(2)]public ushort wMonth; 
[FieldOffset(4)]public ushort wDayOfWeek;
[FieldOffset(6)]public ushort wDay;
[FieldOffset(8)]public ushort wHour; 
[FieldOffset(10)]public ushort wMinute;
[FieldOffset(12)]public ushort wSecond;
[FieldOffset(14)]public ushort wMilliseconds; 
}

 

 

[StructLayout(LayoutKind.Sequential, Pack =1)] 

publicstruct CMPP_QUERY 

[MarshalAs(UnmanagedType.ByValTStr,SizeConst = 8)] 

public string sTime; 

public byte uchQueryType; 

[MarshalAs(UnmanagedType.ByValTStr,SizeConst = 10)] 

public string sQueryCode; 

[MarshalAs(UnmanagedType.ByValTStr,SizeConst = 8)] 

public string sReserved; 

}  

平台调用数据类型

提供托管数据类型及其相应的非托管数据类型的列表。

封送字符串

描述如何通过值、通过引用、在结构中、在类中和在数组中传递字符串。

封送类、结构和联合

描述如何通过值传递类,如何传递各种结构以及如何传递具有值和混合类型的联合。

封送类型数组

描述如何通过值传递多维整数数组以及如何通过引用传递一维数组。

其他封送处理示例

描述影响 Interop 封送处理行为的垃圾回收和线程处理的各个方面。

这里有一个PInvoke的Add-In tools for Visual Studio.Net,几乎所有的Win32 API都有。安装了以后,基本不用自己写了。http://www.pinvoke.net

使用Signature Tool自动生成P/Invoke调用WindowsAPI的C#函数声明

           在.NET程序中调用Win32 API,或者调用自己的VC DLL里面提供的函数的时候,总是被生成正确的C函数在C#中的正确声明而困扰,使用Signature Tool很方便生成C#函数声明。

 

非托管的vc工程中部分文件使用.Net Framwork

使用vs2005环境 ,且工程不使用与编译头文件stdafx.h
一,建立一般的c++的console32的project ,name: Test ,工程setting使用默认,不改变.如下:
 r_testclass.jpg
二,add header file: ManagedCppClass.h

#pragma once
class ManagedCppClass
{
public:
voidPrintString();
};
三,add cpp file: ManagedCppClass.cpp

#using <mscorlib.dll> 
using namespace System;
#include "ManagedCppClass.h"
void ManagedCppClass::PrintString()

{
Console::WriteLine("Hello, i am a Managed cpp class ");
}

四 main函数如下:

#include"stdafx.h"
#include "ManagedCppClass.h"

int _tmain(int argc, _TCHAR* argv[])
{
ManagedCppClass test;
test.PrintString();
return 0;
}

五,修改Managedcppclass.cpp文件的property setting,

r_managedcppclass.jpg 
next
 o_managedcppclass2.jpg
六,compile->link->run ,it is ok!

Leave a Reply