Managed C# Byte Arrays to Un-Managed C++ Byte Arrays

发布时间 2023-08-29 10:02:15作者: 不及格的程序员-八神

Managed C# Byte Arrays to Un-Managed C++ Byte Arrays

 

 Posted in software by Christopher R. Wirz on Sat Jul 08 2017

Bytes are very foundational in C#, whether streams, image files, or cryptography. Sometimes, you want to re-use unmanaged code (it can compile almost anywhere), but don't want to write it all in C#, just pass the bytes. Fortunately, passing byte arrays can be done!

Note: This article assumes three separate files:
  • An unmanaged C++ lib
  • A managed C++ DLL
  • A managed C# executable

But it could also be done in two files:
  • A managed C++ DLL
  • A managed C# executable

If possible, compile the native C++ into a lib file so it can be directly embedded in an application or managed C++ DLL.


// "NativeClass.h"
using namespace std;

namespace NativeCode 
{
	class NativeClass 
	{
		public:
			NativeClass(std::vector<unsigned int> items);
			int length();
			unsigned int operator[] (int index);
			unsigned char* GetBytes();
		
		private:
			std::vector<unsigned int> items;
	};
}


// "NativeClass.cpp"
#include "NativeClass.h"
namespace NativeCode 
{
	NativeClass::NativeClass(std::vector<unsigned int> items) {
		this->items = items;
	}
	int NativeClass::length() {
		return (int)(this->items.size());
	}
	unsigned int NativeClass::operator[] (int index) {
		if (index < 0) {
			return 0;
		}
		if (this->length() <= 0) {
			return 0;
		}
		if (index >= this->length()) {
			return 0;
		}
		return this->items[index];
	}
	unsigned char* NativeClass::GetBytes() {
		unsigned char* ret = new unsigned char[this->length()];
		// item-wise, to prove a point
		for (int n = 0; n < this->length(); n++) {
			ret[n] = (*this)[n];
		}
		return ret;
	}

}

Once the lib file is generated, link it to a manged C++ DLL. Managed DLLs are made using the compiler flag /cli.

The thing to note is that you can't pass managed objects into unmanaged memory in most cases. So, to be safe, copies must be made. The managed C++ class holds a pointer to the unmanaged class - it is just a wrapper and has no real logic; only data copying.


// "ManagedClass.h"
#include "../NativeCode/NativeClass.h"
namespace ManagedCode 
{
	public ref class ManagedClass
	{
		public:
			ManagedClass(cli::array<System::Byte>^ bytes);
			property int Length {
				int get();
			}
			unsigned int operator[] (int index);
			cli::array<System::Byte>^ GetBytes();
			
			// Dispose method will make IDisposable
			~ManagedClass();
		
		private:
			NativeCode::NativeClass* _nativePtr;
	};
}


// "ManagedClass.cpp"
#include "stdafx.h"
#include <string>
#include <msclr\marshal_cppstd.h>
#include "ManagedClass.h"
namespace ManagedCode 
{
	ManagedClass::ManagedClass(cli::array<ystem::Byte>^ bytes) {
		pin_ptr<System::Byte> p = &bytes[0];
		unsigned char* uchars = p;
		char* chars = reinterpret_cast<char*>(uchars);
		int sz = strlen(chars);
		std::vector<unsigned int> items = std::vector<unsigned int>(sz);
		for (int n = sz - 1; n >= 0; n--) {
			items[n] = (unsigned int)chars[n];
		}
		_nativePtr = new NativeCode::NativeClass(items);
	}
	int ManagedClass::Length::get()
	{
		return _nativePtr->length();
	}
	unsigned int ManagedClass::operator[](unsigned int index)
	{
		return (*_nativePtr)[index];
	}
	cli::array<System::Byte>^ ManagedClass::GetBytes() {
		unsigned char* buf = _nativePtr->GetBytes();
		int len = _nativePtr->length();
		cli::array<System::Byte>^ byteArray = gcnew cli::array<System::Byte>(len);
		System::Runtime::InteropServices::Marshal::Copy((IntPtr)buf, byteArray, 0, len);
		delete buf;
		return byteArray;
	}
	ManagedClass::~ManagedClass() { delete _nativePtr; }
}

Since the managed C++ DLL is done, it can be referenced from a C# project easily (making certain of x64 vs x86 compatibility).

There is one little trick to do at the end if you want subsetting. The trick is to create a child class.


// "DotNetClass.cs"
namespace MyApplication
{
	public class DotNetClass : ManagedCode.ManagedClass
	{
		public DotNetClass(byte[] bytes) : base(bytes){}
		
		// Since subscript indexing does not translate, this must be done
		public uint this[int key] => base.op_Subscript(key);
	}
}

Now byte arrays can pass between managed and unmanaged code.


return char array c++ dll to c#

你的问题似乎是如何在 C++ DLL 中返回一个字符数组,并且如何在 C# 中调用这个 DLL。下面是针对这个问题的回答。

首先,要在 C++ DLL 中返回一个字符数组,可以使用以下方法:

 
cpp
复制代码
// 定义一个函数,返回 char* 类型的指针
extern "C" __declspec(dllexport) char* myFunc()
{
    // 定义一个 char 数组,存放要返回的字符串
    char myString[] = "Hello World";
    
    // 在函数内部动态分配内存,以便返回 char* 指针
    char* strPtr = new char[strlen(myString) + 1];
    strcpy_s(strPtr, strlen(myString) + 1, myString);
    
    // 返回 char* 指针
    return strPtr;
}

在上述代码中,我们先定义了一个 char 数组 myString,它存放了要返回的字符串。然后我们在函数内部动态分配了一块内存,将 myString 中的字符串复制到这个内存块中,并返回指向该内存块的指针。

接下来,我们可以在 C# 中调用这个 C++ DLL,如下所示:

 
csharp
复制代码
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr myFunc();

// 在某个方法中调用 myFunc
IntPtr strPtr = myFunc();
string myString = Marshal.PtrToStringAnsi(strPtr);

在上述代码中,我们首先声明了一个名为 myFunc 的函数,它使用 DllImport 特性指定了要调用的 DLL 名称,以及调用约定。在调用 myFunc 函数时,我们可以得到返回的 char* 指针,并使用 Marshal.PtrToStringAnsi 方法将其转换为 C# 中的字符串。

需要注意的是,在 C++ 中使用 new 进行内存分配时,必须使用相应的 delete 释放内存。在 C# 中使用 IntPtr 获取到的指针,在使用完毕后也需要调用 Marshal.FreeHGlobal 释放内存。