StringToHGlobalAnsi 分配的内存必须通过调用 FreeHGlobal 或 GlobalFree 来解除分配。
如何:使用 C++ 互操作封送 ANSI 字符串
本文内容
本主题演示如何使用 C++ 互操作传递 ANSI 字符串,但.NET Framework String 表示 Unicode 格式的字符串,因此转换为 ANSI 是一个额外的步骤。 若要与其他字符串类型进行互操作,请参阅以下主题:
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 由于仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译,因此它们可以保留其性能特征。
示例:传递 ANSI 字符串
该示例演示如何使用 StringToHGlobalAnsi 将 ANSI 字符串从托管函数传递到非托管函数。 此方法在非托管堆上分配内存,并在执行转换后返回地址。 这意味着不需要固定(因为 GC 堆上的内存不会传递到非托管函数),并且必须显式释放从 StringToHGlobalAnsi 中返回的 IntPtr 或意味着内存泄漏结果。
// MarshalANSI1.cpp
// compile with: /clr
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
void NativeTakesAString(const char* p) {
printf_s("(native) received '%s'\n", p);
}
#pragma managed
int main() {
String^ s = gcnew String("sample string");
IntPtr ip = Marshal::StringToHGlobalAnsi(s);
const char* str = static_cast<const char*>(ip.ToPointer());
Console::WriteLine("(managed) passing string...");
NativeTakesAString( str );
Marshal::FreeHGlobal( ip );
}
示例:访问 ANSI 字符串所需的数据封送
以下示例演示了访问由非托管函数调用的托管函数中的 ANSI 字符串所需的数据封送。 接收本机字符串时,托管函数可以直接使用此字符串,也可以使用 PtrToStringAnsi 方法将其转换为托管字符串,如下所示。
// MarshalANSI2.cpp
// compile with: /clr
#include <iostream>
#include <vcclr.h>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma managed
void ManagedStringFunc(char* s) {
String^ ms = Marshal::PtrToStringAnsi(static_cast<IntPtr>(s));
Console::WriteLine("(managed): received '{0}'", ms);
}
#pragma unmanaged
void NativeProvidesAString() {
cout << "(native) calling managed func...\n";
ManagedStringFunc("test string");
}
#pragma managed
int main() {
NativeProvidesAString();
}
另请参阅
如何:使用 C++ 互操作封送 Unicode 字符串
本文内容
本主题演示 Visual C++ 互操作性的一个方面。 有关详细信息,请参阅使用 C++ 互操作(隐式 PInvoke)。
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
本主题演示如何将 Unicode 字符串从托管函数传递到非托管函数,或者反向传递。 若要与其他字符串类型进行互操作,请参阅以下主题:
示例:将 Unicode 字符串从托管函数传递到非托管函数
要将 Unicode 字符串从托管函数传递到非托管函数,可以使用 PtrToStringChars 函数(在 Vcclr.h 中声明)来访问存储托管字符串的内存。 由于此地址将传递给本机函数,因此如果在非托管函数执行时发生垃圾回收循环,则使用 pin_ptr (C++/CLI) 固定内存以防止字符串数据被重新定位便非常重要。
// MarshalUnicode1.cpp
// compile with: /clr
#include <iostream>
#include <stdio.h>
#include <vcclr.h>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
void NativeTakesAString(const wchar_t* p) {
printf_s("(native) received '%S'\n", p);
}
#pragma managed
int main() {
String^ s = gcnew String("test string");
pin_ptr<const wchar_t> str = PtrToStringChars(s);
Console::WriteLine("(managed) passing string to native func...");
NativeTakesAString( str );
}
示例:访问 Unicode 字符串所需的数据封送
以下示例演示了访问由非托管函数调用的托管函数中的 Unicode 字符串所需的数据封送。 托管函数在接收到本机 Unicode 字符串时,使用 PtrToStringUni 方法将其转换为托管字符串。
// MarshalUnicode2.cpp
// compile with: /clr
#include <iostream>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma managed
void ManagedStringFunc(wchar_t* s) {
String^ ms = Marshal::PtrToStringUni((IntPtr)s);
Console::WriteLine("(managed) received '{0}'", ms);
}
#pragma unmanaged
void NativeProvidesAString() {
cout << "(unmanaged) calling managed func...\n";
ManagedStringFunc(L"test string");
}
#pragma managed
int main() {
NativeProvidesAString();
}
另请参阅
如何:使用 C++ 互操作封送 COM 字符串
本文内容
本主题演示如何将 BSTR(COM 编程中支持的基本字符串格式)从托管函数传递到非托管函数,反之亦然。 若要与其他字符串类型进行互操作,请参阅以下主题:
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
示例:将 BSTR 从托管函数传递到非托管函数
下面的示例演示如何将 BSTR(COM 编程中使用的字符串格式)从托管函数传递到非托管函数。 调用托管函数使用 StringToBSTR 获取 .NET System.String 内容的 BSTR 表示形式的地址。 该指针使用 pin_ptr (C++/CLI) 固定,以确保在非托管函数执行时,其物理地址在垃圾回收周期期间不会更改。 禁止垃圾收集器移动内存,直到 pin_ptr (C++/CLI) 超出范围。
// MarshalBSTR1.cpp
// compile with: /clr
#define WINVER 0x0502
#define _AFXDLL
#include <afxwin.h>
#include <iostream>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
void NativeTakesAString(BSTR bstr) {
printf_s("%S", bstr);
}
#pragma managed
int main() {
String^ s = "test string";
IntPtr ip = Marshal::StringToBSTR(s);
BSTR bs = static_cast<BSTR>(ip.ToPointer());
pin_ptr<BSTR> b = &bs;
NativeTakesAString( bs );
Marshal::FreeBSTR(ip);
}
示例:将 BSTR 从非托管函数传递到托管函数
下面的示例演示如何将 BSTR 从非托管函数传递到托管函数。 接收托管函数可以使用字符串作为 BSTR,也可以使用 PtrToStringBSTR 将其转换为 String 以与其他托管函数一起使用。 由于表示 BSTR 的内存是在非托管堆上分配的,因此不需要固定,因为非托管堆上没有垃圾回收。
// MarshalBSTR2.cpp
// compile with: /clr
#define WINVER 0x0502
#define _AFXDLL
#include <afxwin.h>
#include <iostream>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma managed
void ManagedTakesAString(BSTR bstr) {
String^ s = Marshal::PtrToStringBSTR(static_cast<IntPtr>(bstr));
Console::WriteLine("(managed) convered BSTR to String: '{0}'", s);
}
#pragma unmanaged
void UnManagedFunc() {
BSTR bs = SysAllocString(L"test string");
printf_s("(unmanaged) passing BSTR to managed func...\n");
ManagedTakesAString(bs);
}
#pragma managed
int main() {
UnManagedFunc();
}
另请参阅
如何:使用 C++ 互操作封送结构
本文内容
本主题演示 Visual C++ 互操作性的一个方面。 有关详细信息,请参阅使用 C++ 互操作(隐式 PInvoke)。
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
示例:将结构从托管函数传递到非托管函数
下面的示例演示了通过值和通过引用将结构从托管函数传递到非托管函数。 因为此示例中的结构仅包含简单的内在数据类型(请参阅 Blittable 和非 Blittable 类型),所以不需要特殊的编组。 要编组非 blittable 结构,例如包含指针的结构,请参阅如何:使用 C++ 互操作编组嵌入式指针。
// PassStruct1.cpp
// compile with: /clr
#include <stdio.h>
#include <math.h>
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
struct Location {
int x;
int y;
};
double GetDistance(Location loc1, Location loc2) {
printf_s("[unmanaged] loc1(%d,%d)", loc1.x, loc1.y);
printf_s(" loc2(%d,%d)\n", loc2.x, loc2.y);
double h = loc1.x - loc2.x;
double v = loc1.y - loc2.y;
double dist = sqrt( pow(h,2) + pow(v,2) );
return dist;
}
void InitLocation(Location* lp) {
printf_s("[unmanaged] Initializing location...\n");
lp->x = 50;
lp->y = 50;
}
#pragma managed
int main() {
Location loc1;
loc1.x = 0;
loc1.y = 0;
Location loc2;
loc2.x = 100;
loc2.y = 100;
double dist = GetDistance(loc1, loc2);
Console::WriteLine("[managed] distance = {0}", dist);
Location loc3;
InitLocation(&loc3);
Console::WriteLine("[managed] x={0} y={1}", loc3.x, loc3.y);
}
示例:将结构从非托管函数传递到托管函数
下面的示例演示了通过值和通过引用将结构从非托管函数传递到托管函数。 因为此示例中的结构仅包含简单的内在数据类型(请参阅 Blittable 和非 Blittable 类型),所以不需要特殊的编组。 要编组非 blittable 结构,例如包含指针的结构,请参阅如何:使用 C++ 互操作编组嵌入式指针。
// PassStruct2.cpp
// compile with: /clr
#include <stdio.h>
#include <math.h>
using namespace System;
// native structure definition
struct Location {
int x;
int y;
};
#pragma managed
double GetDistance(Location loc1, Location loc2) {
Console::Write("[managed] got loc1({0},{1})", loc1.x, loc1.y);
Console::WriteLine(" loc2({0},{1})", loc2.x, loc2.y);
double h = loc1.x - loc2.x;
double v = loc1.y = loc2.y;
double dist = sqrt( pow(h,2) + pow(v,2) );
return dist;
}
void InitLocation(Location* lp) {
Console::WriteLine("[managed] Initializing location...");
lp->x = 50;
lp->y = 50;
}
#pragma unmanaged
int UnmanagedFunc() {
Location loc1;
loc1.x = 0;
loc1.y = 0;
Location loc2;
loc2.x = 100;
loc2.y = 100;
printf_s("(unmanaged) loc1=(%d,%d)", loc1.x, loc1.y);
printf_s(" loc2=(%d,%d)\n", loc2.x, loc2.y);
double dist = GetDistance(loc1, loc2);
printf_s("[unmanaged] distance = %f\n", dist);
Location loc3;
InitLocation(&loc3);
printf_s("[unmanaged] got x=%d y=%d\n", loc3.x, loc3.y);
return 0;
}
#pragma managed
int main() {
UnmanagedFunc();
}
另请参阅
如何:使用 C++ 互操作封送数组
本文内容
本主题演示 Visual C++ 互操作性的一个方面。 有关详细信息,请参阅使用 C++ 互操作(隐式 PInvoke)。
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
示例:将托管数组传递给非托管函数
以下示例演示如何将托管数组传递给非托管函数。 托管函数使用 pin_ptr (C++/CLI) 在调用非托管函数之前禁止对数组进行垃圾回收。 通过将非托管函数与固定指针一起提供给 GC 堆,可以避免创建数组副本的开销。 为了演示非托管函数正在访问 GC 堆内存,它会修改数组的内容,并在托管函数恢复控制时反映更改。
// PassArray1.cpp
// compile with: /clr
#ifndef _CRT_RAND_S
#define _CRT_RAND_S
#endif
#include <iostream>
#include <stdlib.h>
using namespace std;
using namespace System;
#pragma unmanaged
void TakesAnArray(int* a, int c) {
cout << "(unmanaged) array received:\n";
for (int i=0; i<c; i++)
cout << "a[" << i << "] = " << a[i] << "\n";
unsigned int number;
errno_t err;
cout << "(unmanaged) modifying array contents...\n";
for (int i=0; i<c; i++) {
err = rand_s( &number );
if ( err == 0 )
a[i] = number % 100;
}
}
#pragma managed
int main() {
array<int>^ nums = gcnew array<int>(5);
nums[0] = 0;
nums[1] = 1;
nums[2] = 2;
nums[3] = 3;
nums[4] = 4;
Console::WriteLine("(managed) array created:");
for (int i=0; i<5; i++)
Console::WriteLine("a[{0}] = {1}", i, nums[i]);
pin_ptr<int> pp = &nums[0];
TakesAnArray(pp, 5);
Console::WriteLine("(managed) contents:");
for (int i=0; i<5; i++)
Console::WriteLine("a[{0}] = {1}", i, nums[i]);
}
示例:将非托管数组传递给托管函数
以下示例演示如何将非托管数组传递给托管函数。 托管函数直接访问数组内存(而不是创建托管数组并复制数组内容),这允许托管函数在重新获得控制权时,将所做的更改反映在非托管函数中。
// PassArray2.cpp
// compile with: /clr
#include <iostream>
using namespace std;
using namespace System;
#pragma managed
void ManagedTakesAnArray(int* a, int c) {
Console::WriteLine("(managed) array received:");
for (int i=0; i<c; i++)
Console::WriteLine("a[{0}] = {1}", i, a[i]);
cout << "(managed) modifying array contents...\n";
Random^ r = gcnew Random(DateTime::Now.Second);
for (int i=0; i<c; i++)
a[i] = r->Next(100);
}
#pragma unmanaged
void NativeFunc() {
int nums[5] = { 0, 1, 2, 3, 4 };
printf_s("(unmanaged) array created:\n");
for (int i=0; i<5; i++)
printf_s("a[%d] = %d\n", i, nums[i]);
ManagedTakesAnArray(nums, 5);
printf_s("(ummanaged) contents:\n");
for (int i=0; i<5; i++)
printf_s("a[%d] = %d\n", i, nums[i]);
}
#pragma managed
int main() {
NativeFunc();
}
另请参阅
如何:使用 C++ 互操作封送回调和委托
本文内容
本主题演示使用 Visual C++ 在托管代码和非托管代码之间封送回调和委托(回调的托管版本)。
以下代码示例使用 managed、unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但也可以在单独的文件中定义这些函数。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
示例:配置非托管 API 以触发托管委托
以下示例演示如何配置非托管 API 以触发托管委托。 创建托管委托,并使用其中一种互操作方法 GetFunctionPointerForDelegate 来检索此委托的基本入口点。 然后将此地址传递给非托管函数,后者调用它时并不知道它是作为托管函数实现的。
请注意,可以(但非必要)使用 pin_ptr (C++/CLI) 固定委托,防止垃圾收集器重新定位或处置委托。 尽管需要防止过早的垃圾回收,但固定可提供比所需保护更进一步的保护,因为它不仅可以阻止回收,还可以阻止重新定位。
如果委托由垃圾回收重新定位,这将不会影响基础托管回调,因此 Alloc 用于添加对委托的引用,从而允许重新定位委托,同时阻止处置。 使用 GCHandle 而不是 pin_ptr 可以减少托管堆出现碎片可能性。
// MarshalDelegate1.cpp
// compile with: /clr
#include <iostream>
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
int TakesCallback(ANSWERCB fp, int n, int m) {
printf_s("[unmanaged] got callback address, calling it...\n");
return fp(n, m);
}
#pragma managed
public delegate int GetTheAnswerDelegate(int, int);
int GetNumber(int n, int m) {
Console::WriteLine("[managed] callback!");
return n + m;
}
int main() {
GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
GCHandle gch = GCHandle::Alloc(fp);
IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
Console::WriteLine("[managed] sending delegate as callback...");
// force garbage collection cycle to prove
// that the delegate doesn't get disposed
GC::Collect();
int answer = TakesCallback(cb, 243, 257);
// release reference to delegate
gch.Free();
}
示例:非托管 API 存储的函数指针
下面的示例与上一个示例类似,但在此示例中,提供的函数指针由非托管 API 存储,因此可以随时对它进行调用,这需要在任意长度的时间内阻止垃圾回收。 因此,以下示例使用 GCHandle 的全局实例来阻止委托被重新定位,与函数范围无关。 如第一个示例中所述,这些示例不需要使用 pin_ptr,但在本例中,pin_ptr 无论如何都不起作用,因为它的范围仅限于单个函数。
// MarshalDelegate2.cpp
// compile with: /clr
#include <iostream>
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma unmanaged
// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
static ANSWERCB cb;
int TakesCallback(ANSWERCB fp, int n, int m) {
cb = fp;
if (cb) {
printf_s("[unmanaged] got callback address (%d), calling it...\n", cb);
return cb(n, m);
}
printf_s("[unmanaged] unregistering callback");
return 0;
}
#pragma managed
public delegate int GetTheAnswerDelegate(int, int);
int GetNumber(int n, int m) {
Console::WriteLine("[managed] callback!");
static int x = 0;
++x;
return n + m + x;
}
static GCHandle gch;
int main() {
GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
gch = GCHandle::Alloc(fp);
IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
Console::WriteLine("[managed] sending delegate as callback...");
int answer = TakesCallback(cb, 243, 257);
// possibly much later (in another function)...
Console::WriteLine("[managed] releasing callback mechanisms...");
TakesCallback(0, 243, 257);
gch.Free();
}
另请参阅
如何:使用 C++ 互操作封送嵌入式指针
本文内容
以下代码示例使用 managed 和 unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但如果这些函数在单独的文件中定义,则将以相同方式进行交互。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。
示例
以下示例演示如何从托管函数调用所用结构包含指针的非托管函数。 托管函数创建结构的实例,并使用新关键字(而不是 ref new, gcnew 关键字)初始化嵌入式指针。 由于此操作会在本机堆上分配内存,因此无需固定数组来禁用垃圾回收。 但是,必须显式删除内存以避免内存泄漏。
// marshal_embedded_pointer.cpp
// compile with: /clr
#include <iostream>
using namespace System;
using namespace System::Runtime::InteropServices;
// unmanaged struct
struct ListStruct {
int count;
double* item;
};
#pragma unmanaged
void UnmanagedTakesListStruct(ListStruct list) {
printf_s("[unmanaged] count = %d\n", list.count);
for (int i=0; i<list.count; i++)
printf_s("array[%d] = %f\n", i, list.item[i]);
}
#pragma managed
int main() {
ListStruct list;
list.count = 10;
list.item = new double[list.count];
Console::WriteLine("[managed] count = {0}", list.count);
Random^ r = gcnew Random(0);
for (int i=0; i<list.count; i++) {
list.item[i] = r->NextDouble() * 100.0;
Console::WriteLine("array[{0}] = {1}", i, list.item[i]);
}
UnmanagedTakesListStruct( list );
delete list.item;
}
[managed] count = 10
array[0] = 72.624326996796
array[1] = 81.7325359590969
array[2] = 76.8022689394663
array[3] = 55.8161191436537
array[4] = 20.6033154021033
array[5] = 55.8884794618415
array[6] = 90.6027066011926
array[7] = 44.2177873310716
array[8] = 97.754975314138
array[9] = 27.370445768987
[unmanaged] count = 10
array[0] = 72.624327
array[1] = 81.732536
array[2] = 76.802269
array[3] = 55.816119
array[4] = 20.603315
array[5] = 55.888479
array[6] = 90.602707
array[7] = 44.217787
array[8] = 97.754975
array[9] = 27.370446
另请参阅
如何:扩展封送处理库
本文内容
本主题介绍如何扩展封送库,以在数据类型之间提供更多转换。 用户可以为封送库当前不支持的任何数据转换扩展库。
可以通过这两种方式之一扩展封送库:使用或不使用 marshal_context 类。 查看 C++ 中的封送处理概述主题,确定新转换是否需要上下文。
在这两种情况下,首先为新的封送转换创建一个文件。 执行此操作可保持标准封送库文件的完整性。 如果要将项目移植到另一台计算机或另一个程序员,必须将新的封送文件与项目的其余部分一起复制。 这样可以保证接收项目的用户能够接收新转换,而无需修改任何库文件。
使用不需要上下文的转换扩展封送库
-
创建文件以存储新的封送函数,例如 MyMarshal.h。
-
包括一个或多个封送库文件:
-
marshal.h,用于基类型。
-
marshal_windows.h,用于 windows 数据类型。
-
marshal_cppstd.h,用于 C++ 标准库数据类型。
-
marshal_atl.h,用于 ATL 数据类型。
-
-
在这些步骤末尾使用代码编写转换函数。 在此代码中,TO 是要转换到的类型,FROM 是要转换的类型,
from
是要转换的参数。 -
使用代码替换关于转换逻辑的注释,以便将
from
参数转换为 TO 类型的对象并返回转换后的对象。
namespace msclr {
namespace interop {
template<>
inline TO marshal_as<TO, FROM> (const FROM& from) {
// Insert conversion logic here, and return a TO parameter.
}
}
}
使用需要上下文的转换扩展封送库
-
创建文件以存储新的封送函数,例如 MyMarshal.h
-
包括一个或多个封送库文件:
-
marshal.h,用于基类型。
-
marshal_windows.h,用于 windows 数据类型。
-
marshal_cppstd.h,用于 C++ 标准库数据类型。
-
marshal_atl.h,用于 ATL 数据类型。
-
-
在这些步骤末尾使用代码编写转换函数。 在此代码中,TO 是要转换到的类型,FROM 是要转换的类型,
toObject
是在其中存储结果的指针,fromObject
是要转换的参数。 -
使用代码替换关于初始化的注释,以将
toPtr
初始化为相应的空值。 例如,如果它是指针,请将其设置为NULL
。 -
使用代码替换关于转换逻辑的注释,以便将
from
参数转换为 TO 类型的对象。 转换后的对象将存储在toPtr
中。 -
使用代码替换关于设置
toObject
的注释,以将toObject
设置为转换后的对象。 -
使用代码替换关于清理本机资源的注释,以释放由
toPtr
分配的任何内存。 如果toPtr
是使用new
分配的内存,请使用delete
来释放内存。
namespace msclr {
namespace interop {
template<>
ref class context_node<TO, FROM> : public context_node_base
{
private:
TO toPtr;
public:
context_node(TO& toObject, FROM fromObject)
{
// (Step 4) Initialize toPtr to the appropriate empty value.
// (Step 5) Insert conversion logic here.
// (Step 6) Set toObject to the converted parameter.
}
~context_node()
{
this->!context_node();
}
protected:
!context_node()
{
// (Step 7) Clean up native resources.
}
};
}
}
示例:扩展封送库
以下示例使用不需要上下文的转换扩展封送库。 在此示例中,代码将员工信息从本机数据类型转换为托管数据类型。
// MyMarshalNoContext.cpp
// compile with: /clr
#include <msclr/marshal.h>
value struct ManagedEmp {
System::String^ name;
System::String^ address;
int zipCode;
};
struct NativeEmp {
char* name;
char* address;
int zipCode;
};
namespace msclr {
namespace interop {
template<>
inline ManagedEmp^ marshal_as<ManagedEmp^, NativeEmp> (const NativeEmp& from) {
ManagedEmp^ toValue = gcnew ManagedEmp;
toValue->name = marshal_as<System::String^>(from.name);
toValue->address = marshal_as<System::String^>(from.address);
toValue->zipCode = from.zipCode;
return toValue;
}
}
}
using namespace System;
using namespace msclr::interop;
int main() {
NativeEmp employee;
employee.name = "Jeff Smith";
employee.address = "123 Main Street";
employee.zipCode = 98111;
ManagedEmp^ result = marshal_as<ManagedEmp^>(employee);
Console::WriteLine("Managed name: {0}", result->name);
Console::WriteLine("Managed address: {0}", result->address);
Console::WriteLine("Managed zip code: {0}", result->zipCode);
return 0;
}
在前面的示例中,marshal_as
函数返回转换后数据的句柄。 这样做是为了防止创建额外的数据副本。 直接返回变量将产生与之相关的不必要的性能成本。
Managed name: Jeff Smith
Managed address: 123 Main Street
Managed zip code: 98111
示例:转换员工信息
以下示例将员工信息从托管数据类型转换为本机数据类型。 此转换需要封送上下文。
// MyMarshalContext.cpp
// compile with: /clr
#include <stdlib.h>
#include <string.h>
#include <msclr/marshal.h>
value struct ManagedEmp {
System::String^ name;
System::String^ address;
int zipCode;
};
struct NativeEmp {
const char* name;
const char* address;
int zipCode;
};
namespace msclr {
namespace interop {
template<>
ref class context_node<NativeEmp*, ManagedEmp^> : public context_node_base
{
private:
NativeEmp* toPtr;
marshal_context context;
public:
context_node(NativeEmp*& toObject, ManagedEmp^ fromObject)
{
// Conversion logic starts here
toPtr = NULL;
const char* nativeName;
const char* nativeAddress;
// Convert the name from String^ to const char*.
System::String^ tempValue = fromObject->name;
nativeName = context.marshal_as<const char*>(tempValue);
// Convert the address from String^ to const char*.
tempValue = fromObject->address;
nativeAddress = context.marshal_as<const char*>(tempValue);
toPtr = new NativeEmp();
toPtr->name = nativeName;
toPtr->address = nativeAddress;
toPtr->zipCode = fromObject->zipCode;
toObject = toPtr;
}
~context_node()
{
this->!context_node();
}
protected:
!context_node()
{
// When the context is deleted, it will free the memory
// allocated for toPtr->name and toPtr->address, so toPtr
// is the only memory that needs to be freed.
if (toPtr != NULL) {
delete toPtr;
toPtr = NULL;
}
}
};
}
}
using namespace System;
using namespace msclr::interop;
int main() {
ManagedEmp^ employee = gcnew ManagedEmp();
employee->name = gcnew String("Jeff Smith");
employee->address = gcnew String("123 Main Street");
employee->zipCode = 98111;
marshal_context context;
NativeEmp* result = context.marshal_as<NativeEmp*>(employee);
if (result != NULL) {
printf_s("Native name: %s\nNative address: %s\nNative zip code: %d\n",
result->name, result->address, result->zipCode);
}
return 0;
}
Native name: Jeff Smith
Native address: 123 Main Street
Native zip code: 98111
另请参阅
如何:访问 System::String 中的字符
本文内容
你可以访问 String 对象的字符,以便对采用 wchar_t*
字符串的非托管函数进行高性能调用。 该方法生成指向 String 对象第一个字符的内部指针。 此指针可以直接操作,也可以固定并传递给需要普通 wchar_t
字符串的函数。
示例
PtrToStringChars
返回一个 Char,它是内部指针(也称为 byref
)。 因此,它受到垃圾回收的约束。 除非要将其传递给本机函数,否则无需固定此指针。
考虑下列代码。 不需要固定,因为 ppchar
是内部指针,如果垃圾回收器将它指向的字符串,它还会更新 ppchar
。 如果没有 pin_ptr (C++/CLI) ,代码将正常工作,并且没有由固定导致的潜在性能命中。
如果把 ppchar
传递给本机函数,则必须是固定指针;垃圾回收器将无法更新非托管堆栈帧上的任何指针。
// PtrToStringChars.cpp
// compile with: /clr
#include<vcclr.h>
using namespace System;
int main() {
String ^ mystring = "abcdefg";
interior_ptr<const Char> ppchar = PtrToStringChars( mystring );
for ( ; *ppchar != L'\0'; ++ppchar )
Console::Write(*ppchar);
}
abcdefg
此示例显示需要固定的位置。
// PtrToStringChars_2.cpp
// compile with: /clr
#include <string.h>
#include <vcclr.h>
// using namespace System;
size_t getlen(System::String ^ s) {
// Since this is an outside string, we want to be secure.
// To be secure, we need a maximum size.
size_t maxsize = 256;
// make sure it doesn't move during the unmanaged call
pin_ptr<const wchar_t> pinchars = PtrToStringChars(s);
return wcsnlen(pinchars, maxsize);
};
int main() {
System::Console::WriteLine(getlen("testing"));
}
7
内部指针具有本机 C++ 指针的所有属性。 例如,可以使用它来演练链接的数据结构,并使用一个指针执行插入和删除:
// PtrToStringChars_3.cpp
// compile with: /clr /LD
using namespace System;
ref struct ListNode {
Int32 elem;
ListNode ^ Next;
};
void deleteNode( ListNode ^ list, Int32 e ) {
interior_ptr<ListNode ^> ptrToNext = &list;
while (*ptrToNext != nullptr) {
if ( (*ptrToNext) -> elem == e )
*ptrToNext = (*ptrToNext) -> Next; // delete node
else
ptrToNext = &(*ptrToNext) -> Next; // move to next node
}
}
另请参阅
如何:将 char * 字符串转换为 System::Byte 数组
本文内容
将 char *
字符串转换为 Byte 数组的最有效方法是使用 Marshal 类。
示例
// convert_native_string_to_Byte_array.cpp
// compile with: /clr
#include <string.h>
using namespace System;
using namespace System::Runtime::InteropServices;
int main() {
char buf[] = "Native String";
int len = strlen(buf);
array< Byte >^ byteArray = gcnew array< Byte >(len + 2);
// convert native pointer to System::IntPtr with C-Style cast
Marshal::Copy((IntPtr)buf,byteArray, 0, len);
for ( int i = byteArray->GetLowerBound(0); i <= byteArray->GetUpperBound(0); i++ ) {
char dc = *(Byte^) byteArray->GetValue(i);
Console::Write((Char)dc);
}
Console::WriteLine();
}
Native String
另请参阅
如何:将 System::String 转换为 wchar_t* 或 char*
本文内容
可以在 Vcclr.h 中使用 PtrToStringChars
将 String 转换为本机 wchar_t *
或 char *
。 这将始终返回宽 Unicode 字符串指针,因为 CLR 字符串在内部为 Unicode。 然后,可以从宽字符串进行转换,如以下示例所示。
示例
// 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);
}
Hello
Hello
另请参阅
如何:将 System::String 转换为标准字符串
本文内容
你可以将 String 转换为 std::string
或 std::wstring
,而无需在 Vcclr.h 中使用 PtrToStringChars
。
示例
// 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;
}
test
abcd
efgh
另请参阅
如何:将标准字符串转换为 System::String
本文内容
本主题介绍如何将 C++ 标准库字符串 (<string>) 转换为 String。
示例
// convert_standard_string_to_system_string.cpp
// compile with: /clr
#include <string>
#include <iostream>
using namespace System;
using namespace std;
int main() {
string str = "test";
cout << str << endl;
String^ str2 = gcnew String(str.c_str());
Console::WriteLine(str2);
// alternatively
String^ str3 = gcnew String(str.data());
Console::WriteLine(str3);
}
test
test
test
另请参阅
如何:获取字节数组的指针
本文内容
可以通过采用第一个元素的地址并将它分配给指针来获取指向 Byte 数组中的数组块的指针。
示例
// pointer_to_Byte_array.cpp
// compile with: /clr
using namespace System;
int main() {
Byte bArr[] = {1, 2, 3};
Byte* pbArr = &bArr[0];
array<Byte> ^ bArr2 = gcnew array<Byte>{1,2,3};
interior_ptr<Byte> pbArr2 = &bArr2[0];
}
另请参阅
如何:将非托管资源加载到一个字节数组
本文内容
本主题讨论将非托管资源加载到 Byte 数组的几种方法。
示例
如果知道非托管资源的大小,则可以预先分配 CLR 数组,然后使用指向 CLR 数组的数组块的指针将资源加载到数组中。
// load_unmanaged_resources_into_Byte_array.cpp
// compile with: /clr
using namespace System;
void unmanaged_func( unsigned char * p ) {
for ( int i = 0; i < 10; i++ )
p[ i ] = i;
}
public ref class A {
public:
void func() {
array<Byte> ^b = gcnew array<Byte>(10);
pin_ptr<Byte> p = &b[ 0 ];
Byte * np = p;
unmanaged_func( np ); // pass pointer to the block of CLR array.
for ( int i = 0; i < 10; i++ )
Console::Write( b[ i ] );
Console::WriteLine();
}
};
int main() {
A^ g = gcnew A;
g->func();
}
0123456789
以下示例演示如何将非托管内存块中的数据复制到托管数组。
// load_unmanaged_resources_into_Byte_array_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
#include <string.h>
int main() {
char buf[] = "Native String";
int len = strlen(buf);
array<Byte> ^byteArray = gcnew array<Byte>(len + 2);
// convert any native pointer to IntPtr by doing C-Style cast
Marshal::Copy( (IntPtr)buf, byteArray, 0, len );
}
另请参阅
P/Invoke Tutorial: Basics (Part 1)
P/Invoke is a way of calling C/C++ functions from a .NET program. It’s very easy to use. This article will cover the basics of using P/Invoke.
Note: This tutorial will focus on Windows and thus use Visual Studio. If you’re developing on another platform or with another IDE, adopting the things in this article should be easy enough.
Project Structure
For this tutorial, we need a small project structure containing two projects:
- NativeLib : a C++ library project
-
PInvokeTest : a C# console project
To get you started real quick, you can download the project structure here:
If you’re not using Visual Studio 2010 (or don’t want to use the provided zip file), adopt the following settings.
For project NativeLib, go to the project settings and (for all configurations):
- under
C/C++
–>Preprocessor
–>Preprocessor Definitions
addMYAPI=__declspec(dllexport)
-
under
C/C++
–>Advanced
: changeCalling Convention
to__stdcall (/Gz)
For project PInvokeTest:
- Specify NativeLib as dependency for PInvokeTest. Right click on PInvokeTest and choose
Project Dependencies...
. Then select NativeLib and hitOK
. -
Change the
Output path
(under project settings:Build
) to../Debug
and../Release
for the differentConfiguration
s respectively.
Simple P/Invoke
First, let’s create a native function called print_line()
.
Add a file called NativeLib.h
to NativeLib (or replace it contents):
#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MYAPI
#define MYAPI
#endif
#ifdef __cplusplus
extern "C" {
#endif
MYAPI void print_line(const char* str);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_
Then, add NativeLib.cpp
:
#include "NativeLib.h"
#include <stdio.h>
MYAPI void print_line(const char* str) {
printf("%s\n", str);
}
Now, let’s call this function from the PInvokeTest project. To do this, add the highlighted lines to Program.cs
:
1
2
3
4
56
7
8
9
1011
12
131415
16
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace PInvokeTest {
class Program {
static void Main(string[] args) {
print_line("Hello, PInvoke!"); }
[DllImport("NativeLib.dll")] private static extern void print_line(string str); }
}
|
The most important lines in this sections are lines 13 and 14. Here we’re specifying the C/C++ function to import into our .NET class. There are a couple of things to note about this:
- The modifier is
static extern
.extern
means that the function is imported from C/C++.static
is necessary because the function has no knowledge about the classProgram
. - The name of the function matches the name of C/C++ function.
- The type of parameter
str
is a .NET type (here:string
). P/Invoke automatically converts (also called: marshals) data types from .NET to C/C++ and the other way around. -
The attribute
[DllImport]
specifies the name of DLL file from which we import the function. Note: DllImport allows you to control almost every aspect of the import, like providing a different .NET method name or specifying the calling convention.
Now compile the project and it should print Hello, PInvoke!
to the console.
You can download the complete project here:
Troubleshooting
There are a couple of things that can go wrong with P/Invoke.
Unable to load DLL
You may get a DllNotFoundException
with an error message like “The specified module could not be found.”
As the error message suggests the DLL “NativeLib.dll” could not be found.
The problem here is that Visual Studio doesn’t copy native DLLs to the output directory of .NET projects.
Solution: Change the output directory of the .NET project (PInvokeTest) to match the output directory of the native project (NativeLib). In PInvokeTest‘s project settings under Build
choose ../Debug
and ../Release
for Output path
in the respective configuration.
Stack Imbalance
You may get an error saying that a PInvokeStackImbalance was detected
.
The reason is most likely that the native library uses another calling convention then the .NET project. By default, C/C++ projects use the __cdecl
calling convention, whereas [DllImport]
uses __stdcall
by default.
Solution: Make sure the calling conventions match. Either:
- Specify the correct calling convention in
[DllImport]
, for example[DllImport("NativeLib.dll", CallingConvention=CallingConvention.Cdecl)]
- Change the default calling convention for the native project. This is done in the project settings under
C/C++
–>Advanced
–>Calling Convention
. -
Add the desired calling convention to the desired C/C++ functions, for example:
void __stdcall print_line(const char* str)
. This will only change the calling convention for these functions.
In most cases, it doesn’t matter what calling convention you use. There are some differences, though. You can read more about these differences in the Code Project article Calling Conventions Demystified (Section: Conclusion).
Portability
On non-Windows systems you can use Mono to execute .NET applications. If you’re planning on supporting multiple platforms with your .NET code, I suggest you either:
- Don’t specify a file extension (
.dll
) in[DllImport]
, like[DllImport("NativeLib")]
. This way the appropriate file name will be chosen automatically. Note, however, that this only works as long as there is no dot in the file name (like inSystem.Network.dll
). -
Or: Always specify the full Windows file name (i.e. including file extension) and use Mono’s library mapping mechanism to map platform-dependent file names to Windows file names.
C++/CLI
Besides P/Invoke, the other way of integrating C/C++ functions is using C++/CLI. Although C++/CLI performs better than P/Invoke it also has several drawbacks:
- You need to learn a new language (if you only know C#; even if you know C++ as well). See my C++/CLI Cheat Sheet for an overview.
-
C++/CLI is not supported by Mono; so you can use C++/CLI assemblies only on Windows.
Read On
You can find more information about P/Invoke here:
12 comments
P/Invoke Tutorial: Passing strings (Part 2)
In the previous tutorial we passed a single string to a native C/C++ function by using P/Invoke.
This function was defined like this:
// C++
void print_line(const char* str);
// C#
[DllImport("NativeLib.dll")]
private static extern void print_line(string str);
However, there exists a hidden pitfall here:
What happens when the user passes a non-ASCII character to this function?
ASCII and Unicode: A Historical Overview
Historically there was ASCII which defined characters up to character number 127 (i.e. everything that fits into 7 bits). However, these 128 characters contained only letters used in English. Umlauts (like ä, ö, ü) and other characters were not present. So, the 8th bit was used to map these characters, but the mapping was not standardized. Basically each country had its own mapping of the region 128 – 255. These different mapping were called code pages.
For example, on code page 850 (MS-DOS Latin 1) the character number 154 is Ü (German Umlaut) while on code page 855 (MS-DOS Cyrillic) the very same character number represents џ (Cyrillic small letter DZHE).
To unify these different mapping the Unicode standard was established in 1991. The idea was (and is) to give each existing character a unique id. These ids are called code points. So basically the Unicode standard is “just” a much bigger version of the ASCII standard. The latest version as of writing is Unicode version 6.1 which covers over 110,000 characters.
Along with the Unicode standard several encodings were developed. Each encoding describes how to convert Unicode code points into bytes. The most famous ones are UTF-8 and UTF-16.
Please note that all encodings can encode all Unicode code points. They just differ in the way they do this.
If you want to experiment a little bit with Unicode, there is a Unicode Explorer I’ve written. Go ahead and give it a try.
P/Invoke String Conversions
Back to the actual problem. With the parameter of print_line()
defined as const char*
(and char
being 8 bit) it’s not clear which code page to use for the strings passed to this function.
Instead, let’s change the parameter type to Unicode (also sometimes referred to as “wide characters”):
void print_line(const wchar_t* str);
No, let’s also adopt the C# mapping:
[DllImport("NativeLib.dll", CharSet = CharSet.Unicode)]
private static extern void print_line(string str);
The only difference here it that we specified the CharSet
to be Unicode.
With this, C# will pass strings as UTF-16 encoded strings to the C++ function.
UTF-16 is, as said before, an encoding the converted Unicode code points into bytes and the other way around. In UTF-16 each code point is either encoded with one or with two WORD
s (16 bit values). The most frequently used code points will fit into one WORD
, the less frequently used code points fit into two WORD
s (called a “surrogate pair“).
Important: There is no ISO C way of how to print Unicode characters to the console. wprintf()
won’t work – at least on Windows.
Returning Strings
Returning strings is not as trivial as passing them as parameters.
The following is a quote from Stack Overflow.
The problem though comes with what to do with the native memory that was returned from foo()
. The CLR assumes the following two items about a PInvoke function which directly returns the string type
- The native memory needs to be freed
-
The native memory was allocated with
CoTaskMemAlloc
Therefore it will marshal the string and then call CoTaskMemFree
on the native memory blob. Unless you actually allocated this memory with CoTaskMemAlloc
this will at best cause a crash in your application.
In order to get the correct semantics here you must return an IntPtr
directly. Then use Marshal.PtrToString
in order to get to a managed String value. You may still need to free the native memory but that will dependent upon the implementation of foo.
P/Invoke Tutorial: Passing parameters (Part 3)
P/Invoke tries to make your life easier by automatically converting (“marshalling”) data types from managed code to native code and the other way around.
Marshalling Primitive Data Types
Primitive data types (bool
, int
, double
, …) are the easiest to use. They map directly to their native counterparts.
C# type | C/C++ type | Bytes | Range |
---|---|---|---|
bool |
bool (with int fallback) |
usually 1 | true or false |
char |
wchar_t (or char if necessary) |
2 (1) | Unicode BMP |
byte |
unsigned char |
1 | 0 to 255 |
sbyte |
char |
1 | -128 to 127 |
short |
short |
2 | -32,768 to 32,767 |
ushort |
unsigned short |
2 | 0 to 65,535 |
int |
int |
4 | -2 billion to 2 billion |
uint |
unsigned int |
4 | 0 to 4 billion |
long |
__int64 |
8 | -9 quintillion to 9 quintillion |
ulong |
unsigned __int64 |
8 | 0 to 18 quintillion |
float |
float |
4 | 7 significant decimal digits |
double |
double |
8 | 15 significant decimal digits |
Marshalling Strings
For passing strings, it’s recommended that you pass them as Unicode strings (if possible). For this, you need to specify Char.Unicode
like this:
[DllImport("NativeLib.dll", CharSet = CharSet.Unicode)]
private static extern void do_something(string str);
This requires the C/C++ parameter type be a wchar_t*
:
void do_something(const wchar_t* str);
For more details, see P/Invoke Tutorial: Passing strings (Part 2).
Marshalling Arrays
Arrays of primitive types can be passed directly.
[DllImport("NativeLib.dll")]
private static extern void do_something(byte[] data);
Marshalling Objects
To be able to pass objects you need to make their memory layout sequential:
[StructLayout(LayoutKind.Sequential)]
class MyClass {
...
}
This ensures that the fields are stored in the same order they’re written in code. (Without this attribute the C# compiler reorder fields around to optimize the data structure.)
Then simply use the object’s class directly:
[DllImport("NativeLib.dll")]
private static extern void do_something(MyClass data);
The object will be passed by reference (either as struct*
or struct&
) to the C function:
typedef struct {
...
} MyClass;
void do_something(MyClass* data);
Note: Obviously the order of the fields in the native struct and the managed class must be exactly the same.
Marshalling Structs
Marshalling managed struct
s is almost identical to marshalling objects with only one difference: struct
s are passed by copy by default.
So for struct
s the C/C++ function signature reads:
void do_something(MyClass data);
Of course, you can pass the struct
also by reference. In this case, use (MyClass* data)
or (MyClass& data)
in C/C++ and (ref MyClass data)
in C#.
Marshalling Delegates
Delegates are marshalled directly. The only thing you need to take care of is the “calling convention”. The default calling convention is Winapi
(which equals to StdCall
on Windows). If your native library uses a different calling convention, you need to specify it like this:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyDelegate(IntPtr value);
Marshalling Arbitrary Pointers
Arbitrary pointers (like void*
) are marshalled as IntPtr
objects.
So this C function:
void do_something(void* ptr);
becomes:
[DllImport("NativeLib.dll")] private static extern void do_something(IntPtr ptr);
P/Invoke Tutorial: Pinning (Part 4)
Sometimes a C/C++ function needs to store data you pass to it for later reference. If such data is a managed object (like a string
or class
) you need to make sure that the garbage collector doesn’t delete it while it’s still used/stored in the native code.
That’s what pinning is for. It prevents the garbage collector from deleting and moving the object.
Pinning an Object
To pin a managed object, use GCHandle.Alloc()
:
// Pin "objectToBePinned"
GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
The objectToBePinned
remains pinned until you call Free()
on the handle:
// Unpin "objectToBePinned"
handle.Free();
After unpinning the object it can again be:
- moved by the garbage collector (to optimize memory layout)
-
deleted, if no more references exist to it
Notes:
Free()
will never be called automatically. If you don’t call it manually, the memory of the pinned object will never be freed (i.e. you create a memory leak).- You only need to pin objects (including strings). You can’t pin primitive types (like
int
) and structs, as they reside on the stack and are passed by copy. If you try pin astruct
, a copy of the struct will be pinned. - Classes and structs must have the attribute
[StructLayout(LayoutKind.Sequential)]
to control the layout of their fields. OtherwiseGCHandle.Alloc()
will throw anArgumentException
reading: “Object contains non-primitive or non-blittable data.” -
If the method you’re calling doesn’t store a reference to the passed object for later reference, you don’t need to pin this object. P/Invoke automatically pins objects before the C/C++ function is called and unpins them after the function has returned. So, manually pinning an object is actually about the (time of) unpinning.
Note also that you don’t need (and actually can’t) pin delegates. You need, however, to extend the lifetime of the delegate for as long as it can be called from unmanaged code. To cite an MSDN blog entry on this:
Along the same lines, managed Delegates can be marshaled to unmanaged code, where they are exposed as unmanaged function pointers. Calls on those pointers will perform an unmanaged to managed transition; a change in calling convention; entry into the correct
AppDomain
; and any necessary argument marshaling. Clearly the unmanaged function pointer must refer to a fixed address. It would be a disaster if the GC were relocating that! This leads many applications to create a pinning handle for the delegate. This is completely unnecessary. The unmanaged function pointer actually refers to a native code stub that we dynamically generate to perform the transition & marshaling. This stub exists in fixed memory outside of the GC heap.However, the application is responsible for somehow extending the lifetime of the delegate until no more calls will occur from unmanaged code. The lifetime of the native code stub is directly related to the lifetime of the delegate. Once the delegate is collected, subsequent calls via the unmanaged function pointer will crash or otherwise corrupt the process.
Passing a Pinned Object
Now that you’ve pinned your object you surely want to pass it to a C/C++ function.
The easiest way to do this is to specify managed type directly on the P/Invoke method:
// Directly using "MyType" as parameter type
[DllImport("NativeLib")]
private static extern void do_something(MyType myType);
Then call this method:
GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
do_something(objectToBePinned);
// NOTE: Usually you wouldn't free the handle here if "do_something()"
// stored the pointer to "objectToBePinned".
handle.Free();
The alternative is to pass it as IntPtr
(although it’s no different from the direct approach):
[DllImport("NativeLib")]
private static extern void do_something(IntPtr myType);
...
GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
do_something(handle.AddrOfPinnedObject());
// NOTE: Usually you wouldn't free the handle here if "do_something()"
// stored the pointer to "objectToBePinned".
handle.Free();
Pinning and Passing Strings
Pinning strings is the same as pinning objects with one exception:
You must specify the CharSet.Unicode
. when passing pinned strings!
Otherwise P/Invoke will convert the string into an ASCII string (thereby copying it).
Assume this C function:
void do_something(void* str1, void* str2) {
// Check whether the pointers point to the same address
printf("Equals: %s\n", (str1 == str2 ? "true" : "false"));
}
Then:
// WRONG! Will print "false"
[DllImport("NativeLib", EntryPoint="do_something")]
private static extern void do_something1(string str1, IntPtr str2);
// CORRECT! Will print "true"
[DllImport("NativeLib", CharSet = CharSet.Unicode, EntryPoint="do_something")]
private static extern void do_something2(string str1, IntPtr str2);
...
string text = "my text";
GCHandle handle = GCHandle.Alloc(text, GCHandleType.Pinned);
// Will print "false"
do_something1(text, handle.AddrOfPinnedObject());
// Will print "true"
do_something2(text, handle.AddrOfPinnedObject());
Verifying the Pinned Object is passed
As mentioned in the previous section P/Invoke may create a copy of an object instead of passing it by reference directly.
You can easily verify this by comparing the pointer adresses. In C# use handle.AddrOfPinnedObject().ToString()
to obtain the address of the pinned object.
Convert from System::String to Char in Visual C++
This article describes several ways to convert from System::String*
to char*
by using managed extensions in Visual C++.
Original product version: Visual C++
Original KB number: 311259
Summary
This article refers to the following Microsoft .NET Framework Class Library namespaces:
System::Runtime::InteropServices
Msclr::interop
This article discusses several ways to convert from System::String*
to char*
by using the following:
- Managed extensions for C++ in Visual C++ .NET 2002 and in Visual C++ .NET 2003
- C++/CLI in Visual C++ 2005 and in Visual C++ 2008
Method 1
PtrToStringChars
gives you an interior pointer to the actual String
object. If you pass this pointer to an unmanaged function call, you must first pin the pointer to ensure that the object does not move during an asynchronous garbage collection process:
//#include <vcclr.h>
System::String * str = S"Hello world\n";
const __wchar_t __pin * str1 = PtrToStringChars(str);
wprintf(str1);
Method 2
StringToHGlobalAnsi
copies the contents of a managed String
object into native heap, and then converts it into American National Standards Institute (ANSI) format on the fly. This method allocates the required native heap memory:
//using namespace System::Runtime::InteropServices;
System::String * str = S"Hello world\n";
char* str2 = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
printf(str2);
Marshal::FreeHGlobal(str2);
Note
In Visual C++ 2005 and in Visual C++ 2008, you must add the common language runtime support compiler option (/clr:oldSyntax) to successfully compile the previous code sample. To add the common language runtime support compiler option, follow these steps:
-
Click Project, and then click ProjectName Properties.
Note
ProjectName is a placeholder for the name of the project. -
Expand Configuration Properties, and then click General.
-
In the right pane, click to select Common Language Runtime Support, Old Syntax (/clr:oldSyntax) in the Common Language Runtime support project settings.
-
Click Apply, and then click OK.
For more information about common language runtime support compiler options, visit the following Microsoft Developer Network (MSDN) Web site:
/clr (Common Language Runtime Compilation)
These steps apply to the whole article.
Method 3
The VC7 CString
class has a constructor that takes a managed String pointer and loads the CString
with its contents:
//#include <atlstr.h>
System::String * str = S"Hello world\n";
CString str3(str);
printf(str3);
Method 4
Visual C++ 2008 introduces the marshal_as<T>
marshal help class and the marshal_context()
marshal helper class.
//#include <msclr/marshal.h>
//using namespace msclr::interop;
marshal_context ^ context = gcnew marshal_context();
const char* str4 = context->marshal_as<const char*>(str);
puts(str4);
delete context;
Note
This code does not compile by using managed extensions for C++ in Visual C++ .NET 2002 or in Visual C++ .NET 2003. It uses the new C++/CLI syntax that was introduced in Visual C++ 2005 and the new msclr namespace code that was introduced in Visaul C++ 2008. To successfully compile this code, you must use the /clr C++ compiler switch in Visual C++ 2008.
Managed Extensions for C++ sample code (Visual C++ 2002 or Visual C++ 2003)
//compiler option: cl /clr
#include <vcclr.h>
#include <atlstr.h>
#include <stdio.h>
#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
int _tmain(void)
{
System::String * str = S"Hello world\n";
//method 1
const __wchar_t __pin * str1 = PtrToStringChars(str);
wprintf(str1);
//method 2
char* str2 = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
printf(str2);
Marshal::FreeHGlobal(str2);
//method 3
CString str3(str);
wprintf(str3);
return 0;
}
C++/CLI sample code (Visual C++ 2005 and Visual C++ 2008)
//compiler option: cl /clr
#include <atlstr.h>
#include <stdio.h>
#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
#if _MSC_VER > 1499 // Visual C++ 2008 only
#include <msclr/marshal.h>
using namespace msclr::interop;
#endif
int _tmain(void)
{
System::String ^ str = "Hello world\n";
//method 1
pin_ptr<const wchar_t> str1 = PtrToStringChars(str);
wprintf(str1);
//method 2
char* str2 = (char*)Marshal::StringToHGlobalAnsi(str).ToPointer();
printf(str2);
Marshal::FreeHGlobal((IntPtr)str2);
//method 3
CString str3(str);
wprintf(str3);
//method 4
#if _MSC_VER > 1499 // Visual C++ 2008 only
marshal_context ^ context = gcnew marshal_context();
const char* str4 = context->marshal_as<const char*>(str);
puts(str4);
delete context;
#endif
return 0;
}
使用 ADO.NET 的数据访问 (C++/CLI)
本文内容
ADO.NET 是用于数据访问的 .NET Framework API,提供了强大的功能并易于使用,是以前的数据访问解决方案无法比拟的。 本部分介绍一些涉及 ADO.NET 的问题,这些问题专门针对 Visual C++ 用户,例如封送本机类型。
ADO.NET 在公共语言运行时 (CLR) 下运行。 因此,与 ADO.NET 交互的任何应用程序也必须面向 CLR。 但这并不意味着本机应用程序不能使用 ADO.NET。 这些示例将演示如何从本机代码与 ADO.NET 数据库进行交互。
为 ADO.NET 封送 ANSI 字符串
演示如何将本机字符串 (char *
) 添加到数据库,以及如何将 System.String 从数据库封送到本机字符串。
示例
在此示例中,将创建 DatabaseClass 类来与 ADO.NET DataTable 对象交互。 请注意,此类为本机 C++ class
(与 ref class
或 value class
相比)。 这是必需的,因为我们需要从本机代码使用此类,并且不能在本机代码中使用托管类型。 此类将编译为面向 CLR,如类声明前面的 #pragma managed
指令所示。 有关此指令的详细信息,请参阅 managed、unmanaged。
请注意 DatabaseClass 类的私有成员:gcroot<DataTable ^> table
。 由于本机类型不能包含托管类型,因此 gcroot
关键字是必需的。 有关 gcroot
的详细信息,请参阅如何:使用本机类型声明句柄。
此示例中其余代码为本机 C++ 代码,如 main
前面的 #pragma unmanaged
指令所示。 在此示例中,我们将创建 DatabaseClass 的新实例、调用其方法来创建表并填充表中的某些行。 请注意,本机 C++ 字符串将作为数据库列 StringCol 的值传递。 在 DatabaseClass 中,通过使用 System.Runtime.InteropServices 命名空间中的封送功能将这些字符串封送到托管字符串。 具体而言,PtrToStringAnsi 方法用于将 char *
封送到 String;StringToHGlobalAnsi 方法用于将 String 封送到 char *
。
备注
StringToHGlobalAnsi 分配的内存必须通过调用 FreeHGlobal 或 GlobalFree
来解除分配。
// adonet_marshal_string_native.cpp
// compile with: /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll
#include <comdef.h>
#include <gcroot.h>
#include <iostream>
using namespace std;
#using <System.Data.dll>
using namespace System;
using namespace System::Data;
using namespace System::Runtime::InteropServices;
#define MAXCOLS 100
#pragma managed
class DatabaseClass
{
public:
DatabaseClass() : table(nullptr) { }
void AddRow(char *stringColValue)
{
// Add a row to the table.
DataRow ^row = table->NewRow();
row["StringCol"] = Marshal::PtrToStringAnsi(
(IntPtr)stringColValue);
table->Rows->Add(row);
}
void CreateAndPopulateTable()
{
// Create a simple DataTable.
table = gcnew DataTable("SampleTable");
// Add a column of type String to the table.
DataColumn ^column1 = gcnew DataColumn("StringCol",
Type::GetType("System.String"));
table->Columns->Add(column1);
}
int GetValuesForColumn(char *dataColumn, char **values,
int valuesLength)
{
// Marshal the name of the column to a managed
// String.
String ^columnStr = Marshal::PtrToStringAnsi(
(IntPtr)dataColumn);
// Get all rows in the table.
array<DataRow ^> ^rows = table->Select();
int len = rows->Length;
len = (len > valuesLength) ? valuesLength : len;
for (int i = 0; i < len; i++)
{
// Marshal each column value from a managed string
// to a char *.
values[i] = (char *)Marshal::StringToHGlobalAnsi(
(String ^)rows[i][columnStr]).ToPointer();
}
return len;
}
private:
// Using gcroot, you can use a managed type in
// a native class.
gcroot<DataTable ^> table;
};
#pragma unmanaged
int main()
{
// Create a table and add a few rows to it.
DatabaseClass *db = new DatabaseClass();
db->CreateAndPopulateTable();
db->AddRow("This is string 1.");
db->AddRow("This is string 2.");
// Now retrieve the rows and display their contents.
char *values[MAXCOLS];
int len = db->GetValuesForColumn(
"StringCol", values, MAXCOLS);
for (int i = 0; i < len; i++)
{
cout << "StringCol: " << values[i] << endl;
// Deallocate the memory allocated using
// Marshal::StringToHGlobalAnsi.
GlobalFree(values[i]);
}
delete db;
return 0;
}
StringCol: This is string 1.
StringCol: This is string 2.
编译代码
-
若要从命令行编译代码,请将代码示例保存在名为 adonet_marshal_string_native.cpp 的文件中,并输入以下语句:
cl /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll adonet_marshal_string_native.cpp
为 ADO.NET 封送 BSTR 字符串
演示如何将 COM 字符串 (BSTR
) 添加到数据库,以及如何将 System.String 从数据库封送到 BSTR
。
示例
在此示例中,将创建 DatabaseClass 类来与 ADO.NET DataTable 对象交互。 请注意,此类为本机 C++ class
(与 ref class
或 value class
相比)。 这是必需的,因为我们需要从本机代码使用此类,并且不能在本机代码中使用托管类型。 此类将编译为面向 CLR,如类声明前面的 #pragma managed
指令所示。 有关此指令的详细信息,请参阅 managed、unmanaged。
请注意 DatabaseClass 类的私有成员:gcroot<DataTable ^> table
。 由于本机类型不能包含托管类型,因此 gcroot
关键字是必需的。 有关 gcroot
的详细信息,请参阅如何:使用本机类型声明句柄。
此示例中其余代码为本机 C++ 代码,如 main
前面的 #pragma unmanaged
指令所示。 在此示例中,我们将创建 DatabaseClass 的新实例、调用其方法来创建表并填充表中的某些行。 请注意,COM 字符串将作为数据库列 StringCol 的值传递。 在 DatabaseClass 中,通过使用 System.Runtime.InteropServices 命名空间中的封送功能将这些字符串封送到托管字符串。 具体而言,PtrToStringBSTR 方法用于将 BSTR
封送到 String;StringToBSTR 方法用于将 String 封送到 BSTR
。
备注
StringToBSTR 分配的内存必须通过调用 FreeBSTR 或 SysFreeString
来解除分配。
// adonet_marshal_string_bstr.cpp
// compile with: /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll
#include <comdef.h>
#include <gcroot.h>
#include <iostream>
using namespace std;
#using <System.Data.dll>
using namespace System;
using namespace System::Data;
using namespace System::Runtime::InteropServices;
#define MAXCOLS 100
#pragma managed
class DatabaseClass
{
public:
DatabaseClass() : table(nullptr) { }
void AddRow(BSTR stringColValue)
{
// Add a row to the table.
DataRow ^row = table->NewRow();
row["StringCol"] = Marshal::PtrToStringBSTR(
(IntPtr)stringColValue);
table->Rows->Add(row);
}
void CreateAndPopulateTable()
{
// Create a simple DataTable.
table = gcnew DataTable("SampleTable");
// Add a column of type String to the table.
DataColumn ^column1 = gcnew DataColumn("StringCol",
Type::GetType("System.String"));
table->Columns->Add(column1);
}
int GetValuesForColumn(BSTR dataColumn, BSTR *values,
int valuesLength)
{
// Marshal the name of the column to a managed
// String.
String ^columnStr = Marshal::PtrToStringBSTR(
(IntPtr)dataColumn);
// Get all rows in the table.
array<DataRow ^> ^rows = table->Select();
int len = rows->Length;
len = (len > valuesLength) ? valuesLength : len;
for (int i = 0; i < len; i++)
{
// Marshal each column value from a managed string
// to a BSTR.
values[i] = (BSTR)Marshal::StringToBSTR(
(String ^)rows[i][columnStr]).ToPointer();
}
return len;
}
private:
// Using gcroot, you can use a managed type in
// a native class.
gcroot<DataTable ^> table;
};
#pragma unmanaged
int main()
{
// Create a table and add a few rows to it.
DatabaseClass *db = new DatabaseClass();
db->CreateAndPopulateTable();
BSTR str1 = SysAllocString(L"This is string 1.");
db->AddRow(str1);
BSTR str2 = SysAllocString(L"This is string 2.");
db->AddRow(str2);
// Now retrieve the rows and display their contents.
BSTR values[MAXCOLS];
BSTR str3 = SysAllocString(L"StringCol");
int len = db->GetValuesForColumn(
str3, values, MAXCOLS);
for (int i = 0; i < len; i++)
{
wcout << "StringCol: " << values[i] << endl;
// Deallocate the memory allocated using
// Marshal::StringToBSTR.
SysFreeString(values[i]);
}
SysFreeString(str1);
SysFreeString(str2);
SysFreeString(str3);
delete db;
return 0;
}
StringCol: This is string 1.
StringCol: This is string 2.
编译代码
-
若要从命令行编译代码,请将代码示例保存在名为 adonet_marshal_string_native.cpp 的文件中,并输入以下语句:
cl /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll adonet_marshal_string_native.cpp
为 ADO.NET 封送 Unicode 字符串
演示如何将本机 Unicode 字符串 (wchar_t *
) 添加到数据库,以及如何将 System.String 从数据库封送到本机 Unicode 字符串。
示例
在此示例中,将创建 DatabaseClass 类来与 ADO.NET DataTable 对象交互。 请注意,此类为本机 C++ class
(与 ref class
或 value class
相比)。 这是必需的,因为我们需要从本机代码使用此类,并且不能在本机代码中使用托管类型。 此类将编译为面向 CLR,如类声明前面的 #pragma managed
指令所示。 有关此指令的详细信息,请参阅 managed、unmanaged。
请注意 DatabaseClass 类的私有成员:gcroot<DataTable ^> table
。 由于本机类型不能包含托管类型,因此 gcroot
关键字是必需的。 有关 gcroot
的详细信息,请参阅如何:使用本机类型声明句柄。
此示例中其余代码为本机 C++ 代码,如 main
前面的 #pragma unmanaged
指令所示。 在此示例中,我们将创建 DatabaseClass 的新实例、调用其方法来创建表并填充表中的某些行。 请注意,Unicode C++ 字符串将作为数据库列 StringCol 的值传递。 在 DatabaseClass 中,通过使用 System.Runtime.InteropServices 命名空间中的封送功能将这些字符串封送到托管字符串。 具体而言,PtrToStringUni 方法用于将 wchar_t *
封送到 String;StringToHGlobalUni 方法用于将 String 封送到 wchar_t *
。
备注
StringToHGlobalUni 分配的内存必须通过调用 FreeHGlobal 或 GlobalFree
来解除分配。
// adonet_marshal_string_wide.cpp
// compile with: /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll
#include <comdef.h>
#include <gcroot.h>
#include <iostream>
using namespace std;
#using <System.Data.dll>
using namespace System;
using namespace System::Data;
using namespace System::Runtime::InteropServices;
#define MAXCOLS 100
#pragma managed
class DatabaseClass
{
public:
DatabaseClass() : table(nullptr) { }
void AddRow(wchar_t *stringColValue)
{
// Add a row to the table.
DataRow ^row = table->NewRow();
row["StringCol"] = Marshal::PtrToStringUni(
(IntPtr)stringColValue);
table->Rows->Add(row);
}
void CreateAndPopulateTable()
{
// Create a simple DataTable.
table = gcnew DataTable("SampleTable");
// Add a column of type String to the table.
DataColumn ^column1 = gcnew DataColumn("StringCol",
Type::GetType("System.String"));
table->Columns->Add(column1);
}
int GetValuesForColumn(wchar_t *dataColumn, wchar_t **values,
int valuesLength)
{
// Marshal the name of the column to a managed
// String.
String ^columnStr = Marshal::PtrToStringUni(
(IntPtr)dataColumn);
// Get all rows in the table.
array<DataRow ^> ^rows = table->Select();
int len = rows->Length;
len = (len > valuesLength) ? valuesLength : len;
for (int i = 0; i < len; i++)
{
// Marshal each column value from a managed string
// to a wchar_t *.
values[i] = (wchar_t *)Marshal::StringToHGlobalUni(
(String ^)rows[i][columnStr]).ToPointer();
}
return len;
}
private:
// Using gcroot, you can use a managed type in
// a native class.
gcroot<DataTable ^> table;
};
#pragma unmanaged
int main()
{
// Create a table and add a few rows to it.
DatabaseClass *db = new DatabaseClass();
db->CreateAndPopulateTable();
db->AddRow(L"This is string 1.");
db->AddRow(L"This is string 2.");
// Now retrieve the rows and display their contents.
wchar_t *values[MAXCOLS];
int len = db->GetValuesForColumn(
L"StringCol", values, MAXCOLS);
for (int i = 0; i < len; i++)
{
wcout << "StringCol: " << values[i] << endl;
// Deallocate the memory allocated using
// Marshal::StringToHGlobalUni.
GlobalFree(values[i]);
}
delete db;
return 0;
}
StringCol: This is string 1.
StringCol: This is string 2.
编译代码
-
若要从命令行编译代码,请将代码示例保存在名为 adonet_marshal_string_wide.cpp 的文件中,并输入以下语句:
cl /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll adonet_marshal_string_wide.cpp
为 ADO.NET 封送 VARIANT
演示如何将本机 VARIANT
添加到数据库,以及如何将 System.Object 从数据库封送到本机 VARIANT
。
示例
在此示例中,将创建 DatabaseClass 类来与 ADO.NET DataTable 对象交互。 请注意,此类为本机 C++ class
(与 ref class
或 value class
相比)。 这是必需的,因为我们需要从本机代码使用此类,并且不能在本机代码中使用托管类型。 此类将编译为面向 CLR,如类声明前面的 #pragma managed
指令所示。 有关此指令的详细信息,请参阅 managed、unmanaged。
请注意 DatabaseClass 类的私有成员:gcroot<DataTable ^> table
。 由于本机类型不能包含托管类型,因此 gcroot
关键字是必需的。 有关 gcroot
的详细信息,请参阅如何:使用本机类型声明句柄。
此示例中其余代码为本机 C++ 代码,如 main
前面的 #pragma unmanaged
指令所示。 在此示例中,我们将创建 DatabaseClass 的新实例、调用其方法来创建表并填充表中的某些行。 请注意,本机 VARIANT
类型将作为数据库列 StringCol 的值传递。 在 DatabaseClass 中,通过使用 System.Runtime.InteropServices 命名空间中的封送功能将这些 VARIANT
类型封送到托管对象。 具体而言,GetObjectForNativeVariant 方法用于将 VARIANT
封送到 Object;GetNativeVariantForObject 方法用于将 Object 封送到 VARIANT
。
// adonet_marshal_variant.cpp
// compile with: /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll
#include <comdef.h>
#include <gcroot.h>
#include <iostream>
using namespace std;
#using <System.Data.dll>
using namespace System;
using namespace System::Data;
using namespace System::Runtime::InteropServices;
#define MAXCOLS 100
#pragma managed
class DatabaseClass
{
public:
DatabaseClass() : table(nullptr) { }
void AddRow(VARIANT *objectColValue)
{
// Add a row to the table.
DataRow ^row = table->NewRow();
row["ObjectCol"] = Marshal::GetObjectForNativeVariant(
IntPtr(objectColValue));
table->Rows->Add(row);
}
void CreateAndPopulateTable()
{
// Create a simple DataTable.
table = gcnew DataTable("SampleTable");
// Add a column of type String to the table.
DataColumn ^column1 = gcnew DataColumn("ObjectCol",
Type::GetType("System.Object"));
table->Columns->Add(column1);
}
int GetValuesForColumn(wchar_t *dataColumn, VARIANT *values,
int valuesLength)
{
// Marshal the name of the column to a managed
// String.
String ^columnStr = Marshal::PtrToStringUni(
(IntPtr)dataColumn);
// Get all rows in the table.
array<DataRow ^> ^rows = table->Select();
int len = rows->Length;
len = (len > valuesLength) ? valuesLength : len;
for (int i = 0; i < len; i++)
{
// Marshal each column value from a managed object
// to a VARIANT.
Marshal::GetNativeVariantForObject(
rows[i][columnStr], IntPtr(&values[i]));
}
return len;
}
private:
// Using gcroot, you can use a managed type in
// a native class.
gcroot<DataTable ^> table;
};
#pragma unmanaged
int main()
{
// Create a table and add a few rows to it.
DatabaseClass *db = new DatabaseClass();
db->CreateAndPopulateTable();
BSTR bstr1 = SysAllocString(L"This is a BSTR in a VARIANT.");
VARIANT v1;
v1.vt = VT_BSTR;
v1.bstrVal = bstr1;
db->AddRow(&v1);
int i = 42;
VARIANT v2;
v2.vt = VT_I4;
v2.lVal = i;
db->AddRow(&v2);
// Now retrieve the rows and display their contents.
VARIANT values[MAXCOLS];
int len = db->GetValuesForColumn(
L"ObjectCol", values, MAXCOLS);
for (int i = 0; i < len; i++)
{
switch (values[i].vt)
{
case VT_BSTR:
wcout << L"ObjectCol: " << values[i].bstrVal << endl;
break;
case VT_I4:
cout << "ObjectCol: " << values[i].lVal << endl;
break;
default:
break;
}
}
SysFreeString(bstr1);
delete db;
return 0;
}
ObjectCol: This is a BSTR in a VARIANT.
ObjectCol: 42
编译代码
-
若要从命令行编译代码,请将代码示例保存在名为 adonet_marshal_variant.cpp 的文件中,并输入以下语句:
cl /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll adonet_marshal_variant.cpp
为 ADO.NET 封送 SAFEARRAY
演示如何将本机 SAFEARRAY
添加到数据库,以及如何将托管数组从数据库封送到本机 SAFEARRAY
。
示例
在此示例中,将创建 DatabaseClass 类来与 ADO.NET DataTable 对象交互。 请注意,此类为本机 C++ class
(与 ref class
或 value class
相比)。 这是必需的,因为我们需要从本机代码使用此类,并且不能在本机代码中使用托管类型。 此类将编译为面向 CLR,如类声明前面的 #pragma managed
指令所示。 有关此指令的详细信息,请参阅 managed、unmanaged。
请注意 DatabaseClass 类的私有成员:gcroot<DataTable ^> table
。 由于本机类型不能包含托管类型,因此 gcroot
关键字是必需的。 有关 gcroot
的详细信息,请参阅如何:使用本机类型声明句柄。
此示例中其余代码为本机 C++ 代码,如 main
前面的 #pragma unmanaged
指令所示。 在此示例中,我们将创建 DatabaseClass 的新实例、调用其方法来创建表并填充表中的某些行。 请注意,本机 SAFEARRAY
类型将作为数据库列 ArrayIntsCol 的值传递。 在 DatabaseClass 中,通过使用 System.Runtime.InteropServices 命名空间中的封送功能将这些 SAFEARRAY
类型封送到托管对象。 具体而言,Copy 方法用于将 SAFEARRAY
封送到托管整数数组;Copy 方法用于将托管整数数组封送到 SAFEARRAY
。
// adonet_marshal_safearray.cpp
// compile with: /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll
#include <comdef.h>
#include <gcroot.h>
#include <iostream>
using namespace std;
#using <System.Data.dll>
using namespace System;
using namespace System::Data;
using namespace System::Runtime::InteropServices;
#define MAXCOLS 100
#pragma managed
class DatabaseClass
{
public:
DatabaseClass() : table(nullptr) { }
void AddRow(SAFEARRAY *arrayIntsColValue)
{
// Add a row to the table.
DataRow ^row = table->NewRow();
int len = arrayIntsColValue->rgsabound[0].cElements;
array<int> ^arr = gcnew array<int>(len);
int *pData;
SafeArrayAccessData(arrayIntsColValue, (void **)&pData);
Marshal::Copy(IntPtr(pData), arr, 0, len);
SafeArrayUnaccessData(arrayIntsColValue);
row["ArrayIntsCol"] = arr;
table->Rows->Add(row);
}
void CreateAndPopulateTable()
{
// Create a simple DataTable.
table = gcnew DataTable("SampleTable");
// Add a column of type String to the table.
DataColumn ^column1 = gcnew DataColumn("ArrayIntsCol",
Type::GetType("System.Int32[]"));
table->Columns->Add(column1);
}
int GetValuesForColumn(wchar_t *dataColumn, SAFEARRAY **values,
int valuesLength)
{
// Marshal the name of the column to a managed
// String.
String ^columnStr = Marshal::PtrToStringUni(
(IntPtr)dataColumn);
// Get all rows in the table.
array<DataRow ^> ^rows = table->Select();
int len = rows->Length;
len = (len > valuesLength) ? valuesLength : len;
for (int i = 0; i < len; i++)
{
// Marshal each column value from a managed array
// of Int32s to a SAFEARRAY of type VT_I4.
values[i] = SafeArrayCreateVector(VT_I4, 0, 10);
int *pData;
SafeArrayAccessData(values[i], (void **)&pData);
Marshal::Copy((array<int> ^)rows[i][columnStr], 0,
IntPtr(pData), 10);
SafeArrayUnaccessData(values[i]);
}
return len;
}
private:
// Using gcroot, you can use a managed type in
// a native class.
gcroot<DataTable ^> table;
};
#pragma unmanaged
int main()
{
// Create a table and add a few rows to it.
DatabaseClass *db = new DatabaseClass();
db->CreateAndPopulateTable();
// Create a standard array.
int originalArray[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Create a SAFEARRAY.
SAFEARRAY *psa;
psa = SafeArrayCreateVector(VT_I4, 0, 10);
// Copy the data from the original array to the SAFEARRAY.
int *pData;
HRESULT hr = SafeArrayAccessData(psa, (void **)&pData);
memcpy(pData, &originalArray, 40);
SafeArrayUnaccessData(psa);
db->AddRow(psa);
// Now retrieve the rows and display their contents.
SAFEARRAY *values[MAXCOLS];
int len = db->GetValuesForColumn(
L"ArrayIntsCol", values, MAXCOLS);
for (int i = 0; i < len; i++)
{
int *pData;
SafeArrayAccessData(values[i], (void **)&pData);
for (int j = 0; j < 10; j++)
{
cout << pData[j] << " ";
}
cout << endl;
SafeArrayUnaccessData(values[i]);
// Deallocate the memory allocated using
// SafeArrayCreateVector.
SafeArrayDestroy(values[i]);
}
SafeArrayDestroy(psa);
delete db;
return 0;
}
0 1 2 3 4 5 6 7 8 9
编译代码
-
若要从命令行编译代码,请将代码示例保存在名为 adonet_marshal_safearray.cpp 的文件中,并输入以下语句:
cl /clr /FU System.dll /FU System.Data.dll /FU System.Xml.dll adonet_marshal_safearray.cpp
.NET Framework 安全性
有关涉及 ADO.NET 的安全问题的信息,请参阅保护 ADO.NET 应用程序。
相关章节
部分 | 说明 |
---|---|
ADO.NET | 提供了 ADO.NET 的概述,它是一组向 .NET 程序员公开数据访问服务的类。 |
另请参阅
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.
Pingback: Help with using c++ class called with P/Invoke
Thanks a lot for this clear and coincise post…. The only small add I would suggest is to enable debug of unmanaged code from C#. To enable it the user should go to the “Debug” tab Check “Enable Unmanaged code debugging”. Best Regards
Good intro. However, some (like me) might wonder why and how the Unicode C# string winds up coming into the DLL as ASCII. Changing the C++ code to use WCHAR and wprintf results in an unusable string. In order to make this work, you have to specify the character set when constructing the DllImportAttribute class, as in
[DllImportAttribute("NativeLib.dll", CharSet = CharSet.Unicode)]
“The problem here is that Visual Studio doesn’t copy native DLLs to the output directory of .NET projects.”
This explains why I get cant find dll error for pinvoking used at large codebases.
For that, you can add a link to the unmanaged DLL as a file in the C# project, and set Build Action to None and Copy to Output Directory to Copy If Newer.
Hi, I still have the DllNotFoundException problem. Could you explain me exactly what I need to do to solve that problem?
Thanks
This is good. Please share us if you examples for Reverse PInvoke.
Pingback: A WPF GUI for a C++ DLL: A Complex PInvoke Construct | Clatter from the Byte Kitchen
Hope you guyes can help me.
I simply cant get this stuff working.
I have some vb code, which is used somewhere else in our organisation.
Now I need to use that particular dll from c#.
*********************
*** vb code snippet.
Const lenname = 32
Const lenstatus = 14
Type personType
FirstName As String * lenname
LastName As String * lenname
PersonStatus As String * lenstatus
End Type
Declare Function GetPersonInfo Lib “ksxn.DLL” Alias “GetPersonInfo” (ByVal a1 As String, a2 As kpersoninfotype,ByVal a3 As String) As Integer
*** vb code snippet.
*********************
*********************
*** My C# code snippet.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace UnManagedCS
{
class Program
{
/* GetPersonInfo */
DllImport(“ksxn.DLL”, SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetPersonInfo(string a1, out string a2, out string a3);
static void Main()
{
string a1 = “”;
string a2 = “”;
string a3 = “”;
Console.WriteLine(“GetPersonInfo {0} “, Convert.ToString(GetPersonInfo(a1, out a2, out a3)));
Console.ReadLine();
}
}
}
*** My C# code snippet.
*********************
Thanks in advance.
Good Article for the Beginners…
Helpful and handy, thanks for your nice blog,
Section “Stack Imbalance ” helped me especially. Had no Info what correct calling convention to use . Changed to Cdecl and it worked.
One small tip. In Visual Studio, enable native code debugging on the C# project and you can step in to C code from C#.
https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-debug-in-mixed-mode?view=vs-2019
Pingback: P/Invoke and .NET Target Framework – Windows Questions