Platform Invoke in CLR (2) ---字符的封送(Marshal)

如果是写过C++的肯定一提到字符就会想到字符编码(Multibyte或Unicode)。写.NET程序的便不会考虑这个,因为在CLR环境中默认会采用Unicode编码。(其实刚毕业的时候写了很久的.NET程序却完全不知道编码这回事,因为在.NET环境中好像不知道编码技术也不会对开发有什么影响。因此个人感觉出于对计算机科学系统学习,最好还是从非托管环境开始学习程序设计)。那在调用非托管的方法时,如何解决编码问题呢?答案是要用到DllImportAttribute的CharSet属性。

根据MSDN对CharSet的说明如下:

CharSet可能有:

Ansi

调用时将字符参数封送为Ansi编码的字符

Unicode

调用时将字符参数封送为Unicode编码的字符

Auto

字符的编码是运行时根据运行环境动态适应的(若是Unicode环境测用Unicode编码,否测用Ansi.在CLR环境下默认为Unicode)。

这里还有另外一个小细节,就是关于非托管方法的名字(在C++中很多方法都提供两个版本,比如:MessageBoxA,MessageW用来分别表示Ansi和Unicode版本的调用)。用DllImportAttribute的 ExactSpelling 属性来设定是否允许CLR根据CharSet的设定值去额外匹配对应的非托管函数中的相应版本的函数名。(具体可见MSDN)

Example:


Ansi版本:

C++ Code:

_declspec(dllexport) void _stdcall Print(char* msg)

{

    if(msg)
       printf("Message:%s",msg);

}

Native Method封装类 Code:

[DllImport("native.dll",EntryPoint = "Print")]
public static extern void Print(string msg);

// DllImport默认的CharSet为Ansi


托管调用Code:

NativeWrapper.Print("Hello World");


Unicode版本:

C++ Code:

_declspec(dllexport) void _stdcall PrintW(wchar_t* msg)

{

    setlocale(LC_ALL,"chs");
    if(msg)
       wprintf(L"Message:%ls\n",msg);

}


Native Method封装类 Code:

[DllImport("native.dll", EntryPoint = "PrintW", CharSet = CharSet.Unicode)]
public static extern void PrintW(string msg);

//CharSet设为了Unicode,因为PrintWr接收的为宽字节字符


托管代码调用Code:

NativeWrapper.PrintW("你好");


关于ExactSpelling我始终没有尝试成功,不知是我使用不对还是怎么回事。如下代码:

C++ Code:

_declspec(dllexport) void _stdcall PrintW(wchar_t* msg)

{

    setlocale(LC_ALL,"chs");
    if(msg)
       wprintf(L"Message:%ls\n",msg);

}


Native Method封装类 Code:

[DllImport("native.dll", EntryPoint = "Print", CharSet = CharSet.Unicode,ExactSpelling=false)]
  public static extern void Print(string msg);

//CharSet设为了Unicode,因为PrintW接收的为宽字节字符

但是调用的时候总是报错说找不到Print Entry Point.


其实写了这么多,太顺理成章了。有一个细节就被忽略了:C++接收的是指针,C#这边是string对象。为什么会工作正常呢?其实指针,对象都是浮云。如果

探究底层实现,指针就是用来表示一个内存地址的类型罢了,也就是这个变量的值是一个地址。而C#中的引用类型变量在栈上保存的就是对象在堆上的地址,当把这个

变量传给C++时,其实就是传了对象在堆上的地址(也就是C++中的指针)。

  • 只读的CLR string

做了如下实验,将string传入c++,然后将内存中的值改掉:

C++代码:

_declspec(dllexport) void _stdcall ModifyText(char* originalText)

{

    if(originalText)
      strcpy(originalText,"Hello");

}


NativeWrapper代码:

[DllImport("native.dll", EntryPoint = "ModifyText")]
public static extern void ModifyText(string msg);


托管调用代码:

string originText = "Hello,World";
NativeWrapper.ModifyText(originText);


跟踪看到C++中代码确实改了originalText指针内存中的值,但是回到C#中originalText依然是原值。疑惑,string在CLR

缺少研究。C#中string是只读的这一点是可以理解的,那为什么在C++中又可以修改呢。关键是修改了回到C#中为什么又恢复到原值了呢?莫非修改的不是C#中的内存,

但是string的值确实以指针的形式传进去了啊,有一种可能就是CLR传给C++的是一份拷贝,并不是originalText本身。待确认

补遗:确实是自己技艺不精,CLR在对参数进行封送的时候其实是将CLR中的堆中的数据拷贝一份到非托管堆中,然后会同步两块内存,但是此处string在CLR中是只读的

因此originalText仍然是原值。


  • 封送为指定的字符指针类型

      C++中为字符定了很多指针类型:LPSTR,LPWSTR,LPCSTR,LPCWSTR,LPTSTR,LPCTSTR等等。CLR默认会将string封送为LPSTR

那如何将C#中的字符指定封送为这些类型的指针从而满足C++函数的需求呢?答案是需要用到MarshalAsAttribute.

根据MSDN:

MarshalAs可以指定传入传出参数,返回值的封送目标类型。

Example:


C++代码:

_declspec(dllexport) void _stdcall Test4ConstStr(LPCWSTR pConstStr)

{

    printf("Message:%s",pConstStr);

}


NativeWrapper代码:

[DllImport("native.dll", EntryPoint = "Test4ConstStr",CharSet= CharSet.Unicode)]
public static extern void Test4ConstStr([MarshalAs(UnmanagedType.LPWStr)]string constStr);


托管调用代码:

NativeWrapper.Test4ConstStr("你好");

  • 对返回结果的封送

    [DllImport("native.dll", EntryPoint = "Test4ConstStr",CharSet= CharSet.Unicode)]
    [return:MarshalAs(UnManagedType.LPWStr)]
     public static extern string Test4ConstStr([MarshalAs(UnmanagedType.LPWStr)]string constStr);

 

转载于:https://www.cnblogs.com/Code-life/archive/2012/11/06/2757386.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值