.net 互操作之p/invoke- 数据封送之字符串(2)
2010-08-26 23:45 Clingingboy 阅读(767) 评论(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()); }
释放由非托管函数分配的内存
当在函数中传递引用参数时,非托管函数则需要分配内存
一.自动释放
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);
输出

二.手动控制
非托管代码不变,重新定义托管函数,以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
必须采用手动释放,不可以自动释放
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2009-08-26 Spring.NET学习笔记(6)-基础AOP