代码改变世界

.net 互操作之p/invoke- 数据封送之字符串(2)

2010-08-26 23:45  Clingingboy  阅读(753)  评论(0编辑  收藏  举报

     

使用Unicode传递

一.定义托管函数

// void __cdecl TestStringArgumentsInOut(const wchar_t* inString, wchar_t* outString, int bufferSize);
 [DllImport(_dllName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
 private extern static void TestStringArgumentsFixLength(string inString, StringBuilder outString, int bufferSize);


第一个参数是传入的参数,第二个是输出的.

注意
1.String是不可变类型,const wchar_t* inString
2.wchar_t*为Unicode,所以指定为CharSet = CharSet.Unicode,设置为Unicode则整个函数的参数均为以Unicode传递
3.第二个参数是输出参数,所以设置为StringBuilder类型

下面示例功能为拷贝字符串,托管代码分配内存

二.非托管代码示例

void __cdecl TestStringArgumentsFixLength(
     const wchar_t* inString, wchar_t* outString, int bufferSize)
 {
     if(NULL != inString)
     {
         wcscpy_s(outString, bufferSize, inString);
     }
 }

三.托管代码

private static void TestStringArgumentsFixLength()
        {
            string inString = "This is a input string.";
 
            int bufferSize = inString.Length;
            StringBuilder sb = new StringBuilder(bufferSize);
 
           TestStringArgumentsFixLength(inString, sb, bufferSize + 1);
 
           Console.WriteLine("Original: {0}", inString);
            Console.WriteLine("Copied: {0}", sb.ToString());
        }

以MarshalAs的方式传递

MarshalAs标签定义的话,可以细化函数参数是否以Unicode形式传递,可能非托管函数既有ANSI字符串也有Unicode字符串。
MarshalAs以指定UnmanagedType参数来设置要传递参数的类型,UnmanagedType定义了很多非托管类型

1.定义托管函数

// void __cdecl TestStringMarshalArguments(const char* inAnsiString, const wchar_t* inUnicodeString, wchar_t* outUnicodeString, int outBufferSize)
 [DllImport(_dllName, CallingConvention = CallingConvention.Cdecl)]
 private extern static void TestStringMarshalArguments(
     [MarshalAs(UnmanagedType.LPStr)] string inAnsiString, 
     [MarshalAs(UnmanagedType.LPWStr)] string inUnicodeString,
     [MarshalAs(UnmanagedType.LPWStr)] StringBuilder outStringBuffer,
     int outBufferSize);

第一个是ANSI字符串,第二传入的Unicode字符串和第三个是传出的Unicode字符串

2.定义非托管代码

void __cdecl TestStringMarshalArguments(const char* inAnsiString, const wchar_t* inUnicodeString, 
                                         wchar_t* outUnicodeString, int outBufferSize)
 {
     
     size_t ansiStrLength = strlen(inAnsiString);
     size_t uniStrLength = wcslen(inUnicodeString);
 
     size_t totalSize = ansiStrLength + uniStrLength + 2;
     wchar_t* tempBuffer = new(std::nothrow) wchar_t[totalSize];
     if(NULL == tempBuffer)
     {
         return;
     }
 
     wmemset(tempBuffer, 0, totalSize);
     mbstowcs(tempBuffer, inAnsiString, totalSize);
     wcscat_s(tempBuffer, totalSize, L" ");
     wcscat_s(tempBuffer, totalSize, inUnicodeString);
     wcscpy_s(outUnicodeString, outBufferSize, tempBuffer);
     delete[] tempBuffer;
 }


3.托管代码测试

private static void TestMarshalArguments()
 {
     string string1 = "Hello";
     string string2 = "世界!?";
     int outBufferSize = string1.Length + string2.Length + 2;
     StringBuilder outBuffer = new StringBuilder(outBufferSize);
     TestStringMarshalArguments(string1, string2, outBuffer, outBufferSize);
 
     Console.WriteLine("{0}", outBuffer.ToString());
 }


输出结果:
image_2

释放由非托管函数分配的内存

当在函数中传递引用参数时,非托管函数则需要分配内存

一.自动释放

1.非托管函数

void __cdecl TestStringArgumentOut(int id, wchar_t** ppString)
 {
     if(NULL != ppString)
     {
         int bufferSize = 128;
         *ppString = (wchar_t*)CoTaskMemAlloc(bufferSize);
         swprintf_s(*ppString, bufferSize/sizeof(wchar_t), L"Out string of ID: %d", id);
     }
 }


使用CoTaskMemAlloc方法来申请内存,则会自动调用CoTaskMemFree来释放非托管内存,这就意味了托管代码无需处理内存问题,减轻了托管代码的的复杂度.

2.定义托管函数

[DllImport(_dllName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
 private extern static void TestStringArgumentOut(int id, ref string outString);


需要以ref 关键字来制定字符串是输出的,以表示此参数是引用类型

3.测试

string strResult = "";
 TestStringArgumentOut(2, ref strResult);
 Console.WriteLine("Return string value: {0}", strResult);

输出
image_4

二.手动控制

非托管代码不变,重新定义托管函数,以IntPtr 类型代替String类型

// NATIVELIB_API void __cdecl TestStringArgumentOut(int id, wchar_t** ppString);
 [DllImport(_dllName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestStringArgumentOut")]
 private extern static void TestStringArgumentOutIntPtr(int id, ref IntPtr outString);


测试代码

string strResult;
 IntPtr strIntPtr = IntPtr.Zero;
 TestStringArgumentOutIntPtr(1, ref strIntPtr);
//数据类型转换,复制数据

strResult = Marshal.PtrToStringUni(strIntPtr); //手动释放内存
Marshal.FreeCoTaskMem(strIntPtr); Console.WriteLine("Return string IntPtr: {0}", strResult);

封送作为返回值的字符串

看以下托管函数定义,同上返回IntPtr 则需要手动释放内存,直接返回String的则会自动处理

// void wchar_t* __cdecl TestStringAsResult()
 [DllImport(_dllName, CharSet = CharSet.Unicode, 
     CallingConvention = CallingConvention.Cdecl, 
     EntryPoint = "TestStringAsResult")]
 private extern static IntPtr TestStringAsResultIntPtr(int id);
 
 [DllImport(_dllName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
 private extern static string TestStringAsResult(int id);

测试代码

// 1. IntPtr
 string result;
 IntPtr strPtr = TestStringAsResultIntPtr(1);
 result = Marshal.PtrToStringUni(strPtr);
 // Marshal.FreeCoTaskMem(strPtr);
 Console.WriteLine("Return string IntPtr: {0}", result);
 
 // 2. String
 result = TestStringAsResult(2);
 Console.WriteLine("Return string value: {0}", result);

特殊的BSTR

必须采用手动释放,不可以自动释放