C++ Programmer's Cookbook

{C++ 基础} {C++ 高级} {C#界面,C++核心算法} {设计模式} {C#基础}

CLI中native的string和System::String转化


一 实例 (代码下载 http://www.cppblog.com/Files/mzty/SystemStringAndNativeString.rar
nativeclass.h
#pragma once 

#include 
<string>
#include 
<iostream>

class NativeClass
{
public:
    NativeClass(std::wstring name);
    
void PrintSelf();

private:
    std::wstring m_strName;
}
;
nativeclass.cpp
#include "stdafx.h"
#include 
"NativeClass.h"

NativeClass::NativeClass(std::wstring name)
{
    m_strName 
= name;
}

void NativeClass::PrintSelf()
{
    std::wcout 
<< "NativeClass: " << m_strName << std::endl;
}

refclass.h
#pragma once 

public ref class RefClass
{
public:
    RefClass(System::String
^ name);
    
void PrintSelf();

private:
    System::String
^ m_strName;
}
;
refclass.cpp
#include "stdafx.h"
#include 
"RefClass.h"

RefClass::RefClass(System::String
^ name)
{
    m_strName 
= name;
}

void RefClass::PrintSelf()
{
    System::Console::WriteLine(
"RefClass {0}", m_strName);
}

调用代码:
// SystemStringAndNativeString.cpp : main project file.

#include 
"stdafx.h"
#include 
"NativeClass.h"
#include 
"RefClass.h"

#include 
<windows.h>
#include 
<vcclr.h>

using namespace System;

int main(array<System::String ^> ^args)
{
    std::wstring nativeName 
= L"NativeName";
    String
^ refName = gcnew String("refName");

    NativeClass nativeClass(nativeName);
    nativeClass.PrintSelf();
    pin_ptr
<const wchar_t> tempNativePointer = PtrToStringChars(refName);
    NativeClass nativeClass2(tempNativePointer);
    nativeClass2.PrintSelf();

    RefClass
^ refClass = gcnew RefClass(refName);
    refClass
->PrintSelf();
    String
^ tempRefName = gcnew String(nativeName.c_str());
    RefClass
^ refClass2 = gcnew RefClass(tempRefName);
    refClass2
->PrintSelf();

    
return 0;
}


二 参考 http://www.cnblogs.com/tallman/archive/2007/07/25/831247.html

转向.NET后,手头上往往仍有旧的模块要重用。也许这些模块是Delphi写的,也许是C/C++写的,或者是其它编程语言……为了能把它们移植到.NET下,或者是在.NET中调用,To be or not to be, that is a question。
  在这里,我笔记了几个在工作中遇到的几个场景。不过,这里不包括完全使用C#来重写原来用C++编写的程序这种变态的需求。当你被要求做这种事的时候,请三思而后行……这简直是种非人的折磨。

场景一:在.NET中调用WindowsAPI或DLL。

  这是比较普遍的需求。一般来说,简单的函数调用,大可直接用C#/VB.NET,经过DllImport属性包装出函数来调用。如:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW",  SetLastError=true,
CharSet
=CharSet.Unicode, ExactSpelling=true,
CallingConvention
=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);

由于WindowsAPI用到的人实在是多,因此有一个专门的wiki站点,收集这方面的资料:http://www.pinvoke.net/,对于常用的函数甚至有完整的应用例子和帮助。当然,如果你有相应的资料和例子,你也可以贡献你的力量,给其它人帮助。

   场景二:用托管C++包装现有的DLL,供C#调用

  当函数的参数或返回值比较复杂,或函数比较多的时候,这种方法对与人来说,实在是一个折磨。常常这些接口和定义就要用掉几千行的代码,而且还不能保证是正确的。这些错误往往在运行时才能显现出来,甚至有些错误会引起内存泄漏,或其它更为隐蔽的错误。
  在这种情况下,使用C++/Managed代码来包装,就成了最合理的选择。因为托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。

  仍以MoveFile为例吧,这样比较简单:
#include <windows.h>
#include 
<vcclr.h>

using namespace System;

namespace wrapper
{
    
public ref class ApiWrapper {
    
public:
        
bool static MoveFile(String ^ lpExistingFileName, String ^ lpNewFileName )
        
{
            pin_ptr
<const wchar_t> src = PtrToStringChars(lpExistingFileName);
            pin_ptr
<const wchar_t> dst = PtrToStringChars(lpNewFileName);
            
return ::MoveFile(src, dst);
        }

    }
;
}

然后在C#中,引用上面代码生成的DLL文件,就可以直接调用了:
wrapper.ApiWrapper.MoveFile(@"c:\debug.log"@"c:\debug.txt");
假如原有的代码是基于COM的,那么太好了,VisualStudio等IDE会自动生成一个用于包装的dll,供你调用。当然因特殊需要而手工编码的是另一回事。

  场景三:现有C++原代码,包装后供C#调用。

  C++的原代码,实际上可以直接编译成托管代码。MFC也好ATL也好……这样看起来在.NET中最强大的编程语言就是C++了:它不仅可以编写托管程序,甚至可以将标准C++的代码也编译成托管程序!其实VC++最强大的地方不止如此,它还在于能够编写混合了托管和非托管的代码的程序!!!这样最大的好处不仅可以将关键代码直接编译成非托管的代码,还可以避免被反编译。
  
  假设现有C++代码如下:
class UnmanagedClass {
public:
    LPCWSTR GetPropertyA() 
return L"Hello!"; }
    
void MethodB( LPCWSTR ) {}
}
;


我们只要再增加一个包装类到工程文件中:
namespace wrapper
{
    
public ref class ManagedClass {
    
public:
        
// Allocate the native object on the C++ Heap via a constructor
        ManagedClass() : m_Impl( new UnmanagedClass ) {}

        
// Deallocate the native object on a destructor
        ~ManagedClass() {
            delete m_Impl;
        }


    
protected:
        
// Deallocate the native object on the finalizer just in case no destructor is called
        !ManagedClass() {
            delete m_Impl;
        }


    
public:
        property String 
^  get_PropertyA {
            String 
^ get() {
                
return gcnew String( m_Impl->GetPropertyA());
            }

        }


        
void MethodB( String ^ theString ) {
            pin_ptr
<const WCHAR> str = PtrToStringChars(theString);
            m_Impl
->MethodB(str);
        }


    
private:
        UnmanagedClass 
* m_Impl;
    }
;
}

然后,改变编译选项为“使用公共语言扩展 /clr”就可以了。这样,我们把代码编译成DLL文件就可以供.NET其它语言调用了。
  最后,C#中可以象如下的代码一样调用C++类了:
ManagedClass mc = new ManagedClass();
mc.MethoB(
"Hello");
string s = mc.get_PropertyA;

 

场景四:如何在托管C++代码中混合托管和非托管代码

  很简单,只要从#pragma unmanaged编译指示开始的程序,一率编译成非托管代码;要想恢复成托管代码,只要使用#pragma managed就可以了。如:

#pragma unmanaged

#include 
<iostream>
using namespace std;

template
<typename T>
void f(T t){
    cout 
<< t << endl;
}


#pragma managed

using namespace System;

void m(String ^ s){
    Console::WriteLine(s);
}


void main(){
    f(
"Hello");
    m(
"World");
}


生成exe文件后,用反编译程序查看 f 函数:

 

[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType=MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
public static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)*);

看不到源码,而方法属性标记为Unmanaged。
  如果没有加上#pragma unmanaged,反编译得到的 f 函数为:
internal static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)* t)
{
      std.basic_ostream
<char,std::char_traits<char> >.<<(std.operator<<<struct std::char_traits<char> >(*((basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced)*&__imp_std.cout), t), (basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced) modopt(CallConvCdecl) *(basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced))) __unep@?endl@std@@$$FYAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z);
}

其中的函数内容一目了然。如果你的函数没有调用operator等不好理解的类库,那么反编译出来的代码简直和源码没差别。 

  开心一刻:我只会C++不懂.NET不懂C#,怎么编写.NET程序?

  很简单,你照样用你的C++写你的程序,然后测试没有错误后,将编译选项改为/clr,好了,Rebuild,你的程序现在是.NET了。

  恶搞:“我想问一下,在能将现有的C++代码直接进行封装,被C#进行调用,而不是去调用DLL,也就是不生成DLL,就在C#下能直接调用VC的工程源文件不?”

  我想,提问的人是不是指,现有c++源码,但不想费劲去转换成C#源码,但又想能与C#一起编译。
  于是我就给了一个极其变态的方法,不过,个人是不建议使用这种变态的方法啊。方法如下:
  1 先将C++源码,改用CLR编译选项,编译成.NET的Assembly(DLL文件)。
  2 然后用reflector等反编译软件,反编译成C#代码,并导出(reflector有专门的导出插件)。
  3 将导出的C#代码,添加上新写的C#代码一起编译。
  
  这种方法生成的代码很是恐怖,强烈建议不要把C++源码就这么丢了,否则后果自负。


 

posted on 2008-03-12 16:47 梦在天涯 阅读(8386) 评论(4)  编辑 收藏 引用 所属分类: Manage c++ /CLI

评论

# re: CLI中native的string和System::String转化 2008-03-20 00:28 七星重剑

呵呵,这个当年我也整过,不过是项目经理搞定的  回复  更多评论   

# re: CLI中native的string和System::String转化 2008-05-04 11:26 陈梓瀚(vczh)

无论如何还是要建立一个c++/cli工程,然后原来的部分使用#pragma unmanaged编译,然后再用c++/cli加一个壳。c#的工程引用这个工程就行了。不过还是做成dll好。

至于他们的互相转换我是这样做的:
System::String^ ConvertString(const freescript::FsString& Source)
{
return gcnew System::String(Source.w_str());
}

freescript::FsString ConvertString(System::String^ Source)
{
System::IntPtr Pointer=System::Runtime::InteropServices::Marshal::StringToCoTaskMemUni(Source);
wchar_t* Buffer=(wchar_t*)(void*)(Pointer);
freescript::FsString Result=Buffer;
System::Runtime::InteropServices::Marshal::FreeCoTaskMem(Pointer);
return Result;
}  回复  更多评论   

# re: CLI中native的string和System::String转化 2008-07-17 14:33 梦在天涯

// convert_system_string.cpp
// compile with: /clr
#include <string>
#include <iostream>
using namespace std;
using namespace System;

void MarshalString ( String ^ s, string& os ) {
using namespace Runtime::InteropServices;
const char* chars =
(const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
os = chars;
Marshal::FreeHGlobal(IntPtr((void*)chars));
}

void MarshalString ( String ^ s, wstring& os ) {
using namespace Runtime::InteropServices;
const wchar_t* chars =
(const wchar_t*)(Marshal::StringToHGlobalUni(s)).ToPointer();
os = chars;
Marshal::FreeHGlobal(IntPtr((void*)chars));
}

int main() {
string a = "test";
wstring b = L"test2";
String ^ c = gcnew String("abcd");

cout << a << endl;
MarshalString(c, a);
c = "efgh";
MarshalString(c, b);
cout << a << endl;
wcout << b << endl;
}  回复  更多评论   

# re: CLI中native的string和System::String转化 2008-07-17 14:34 梦在天涯

// convert_string_to_wchar.cpp
// compile with: /clr
#include < stdio.h >
#include < stdlib.h >
#include < vcclr.h >

using namespace System;

int main() {
String ^str = "Hello";

// Pin memory so GC can't move it while native function is called
pin_ptr<const wchar_t> wch = PtrToStringChars(str);
printf_s("%S\n", wch);

// Conversion to char* :
// Can just convert wchar_t* to char* using one of the
// conversion functions such as:
// WideCharToMultiByte()
// wcstombs_s()
// ... etc
size_t convertedChars = 0;
size_t sizeInBytes = ((str->Length + 1) * 2);
errno_t err = 0;
char *ch = (char *)malloc(sizeInBytes);

err = wcstombs_s(&convertedChars,
ch, sizeInBytes,
wch, sizeInBytes);
if (err != 0)
printf_s("wcstombs_s failed!\n");

printf_s("%s\n", ch);
}  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


公告

EMail:itech001#126.com

导航

统计

  • 随笔 - 461
  • 文章 - 4
  • 评论 - 746
  • 引用 - 0

常用链接

随笔分类

随笔档案

收藏夹

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

积分与排名

  • 积分 - 1797002
  • 排名 - 5

最新评论

阅读排行榜