Marshalling an unsigned char * from a COM interface - visual-c++

I have a COM interface defined in the file IPlan.idl that defines a property for an unsigned char* buffer.
[
object,
uuid(...),
dual,
helpstring("..."),
pointer_default(unique)
]
interface IPlan : IDispatch
{
...
[id(28), helpstring("method SetByte")
HRESULT SetByte([in] long index, [in] unsigned char buffer);
[id(29), helpstring("method GetByte")
HRESULT GetByte([in] long index, [out, retval] unsigned char * pBuffer);
[id(30), propget, helpstring("property Buffer")]
HRESULT Buffer([out, retval] unsigned char** pBuffer);
[id(30), propput, helpstring("property Buffer")]
HRESULT Buffer([in] unsigned char* newBuffer);
...
}
My implementation class, MyPlan, is defined in C#. This class talks to a C++ COM class, CProcessor. CProcessor calculates some data into a unsigned char* buffer. This data is loaded into a MyPlan object once all the data in CProcessor has been stored in its buffer.
The metadata for IPlan defines the following function signatures:
public interface IPlan
{
...
byte GetByte(int index);
void SetByte(int index, byte buffer);
IntPtr get_Buffer();
void set_Buffer(ref byte pBuffer);
...
}
I am able to use GetByte and SetByte from CProcessor to access and modify a byte[] variable in MyPlan. I am also able to use get_Buffer() by marshalling the byte[] variable.
My question is how can I utilize the
set_Buffer(ref byte pBuffer)
function? When I call it in CProcessor, pBuffer only contains the first byte of the buffer (like a call to SetByte(0, data)). I assume that I need to marshal the setter just as I did in the getter but my searches have come up empty. I have tried
set_Buffer([MarshalAs(UnmanagedType.LPStr)] ref byte pBuffer);
but that doesn't seem to do anything to pass in the entire array.
MarshalAs(UnmanagedType.LPStr) - how does this convert utf-8 strings to char*
Problem using dll in c#

I was able to use
[id(1), propget, helpstring("Buffer")]
HRESULT Buffer([out, retval] VARIANT* pBuffer);
[id(1), propput, helpstring("Buffer")]
HRESULT Buffer([in] VARIANT pBuffer);
to pass an unsigned char array from C++ to C#. The C# just requires a cast to byte[]
this.buffer = (byte[])myPlan.Buffer;

Related

How to create a file with content using debugfs in kernel module?

With this debugfs API I can create a file in /sys/kernel/debug/parent/name, but it's empty, no matter which data I put in void *data parameter
struct dentry *debugfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, struct file_operations *fops);
According to documentation we need to implement file_operations ourself to handle file open and write.
A snippet of code from mine:
static ssize_t myreader(struct file *fp, char __user *user_buffer,
size_t count, loff_t *position)
{
return simple_read_from_buffer(user_buffer, count, position, ker_buf, len);
}
static ssize_t mywriter(struct file *fp, const char __user *user_buffer,
size_t count, loff_t *position)
{
if(count > len )
return -EINVAL;
return simple_write_to_buffer(ker_buf, len, position, user_buffer, count);
}
static const struct file_operations fops_debug = {
.read = myreader,
.write = mywriter,
};
static int __init init_debug(void)
{
dirret = debugfs_create_dir("dell", NULL);
fileret = debugfs_create_file("text", 0644, dirret, "HELLO WORLD", &fops_debug);
debugfs_create_u64("number", 0644, dirret, &intvalue);
return (0);
}
After installing this module to kernel, two files 'text' and 'number' will be created in the folder 'dell'. File 'number' contains the number I passed in as 'intvalue' as expected, but the other file 'text' is empty.
It's written in document that data will be stored in the i_private field of the resulting inode structure
My expectation: The string "HELLO WORLD" will be written in the file after module is loaded.
I think that the problem should be in the read and write operation functions. Is it possible to create a file with a particular content with the debugfs_create_file method?
To answer your question, whatever you are expecting from your code is correct but it is not going to produce the expected result. I believe there are other more efficient and correct ways of doing it, but to explain the current behavior:
You are initializing data as content of file text but you are reading from buffer ker_buf in user_buffer instead of file pointer using simple_read_from_buffer(user_buffer, count, position, ker_buf, len);
Similarly you are writing to kern_buf from user_buffer using simple_write_to_buffer(ker_buf, len, position, user_buffer, count);
With the existing code, if you want to achieve what you are trying to do, then you have to copy the string "HELLO WORLD" to kern_buf in init_debug()
Something like:
strscpy(kern_buf, "HELLO WORLD", strlen("HELLO WORLD") + 1);
or in form of complete function:
static int __init init_debug(void)
{
dirret = debugfs_create_dir("dell", NULL);
fileret = debugfs_create_file("text", 0644, dirret, NULL, &fops_debug);
debugfs_create_u64("number", 0644, dirret, &intvalue);
strscpy(kern_buf, "HELLO WORLD", strlen("HELLO WORLD") + 1);
return (0);
}
Edit:
Referred some online materials and found out that the void *data provided to debugfs_create_file() during initialization gets stored in the i_private field and can be later retrieved from the i_private field of the resulting inode structure.
The inode of the respective file can be fetched from struct file *fp which is the first argument of read() or write() operations.
The struct inode is a member of struct file and i_private is a member of struct inode
To fetch void *data provided during file creation via debugfs_create_file() in read() you can do something similar to as shown below:
static ssize_t myreader(struct file *fp, char __user *user_buffer,
size_t count, loff_t *position)
{
struct inode *l_inode = fp->f_inode;
strscpy(user_buffer, (char *)l_inode->i_private, PAGE_SIZE);
...
}

C++ to C# char[]

C# code:
class Hello{
public void helloWorld(char[] chars){
//do something
}
}
C++ code to call C#:
MyCSDLL::Hello* hello;
//init hello, some calls are ok.
char* myCharPtr;
//init with message
HRESULT result = hello->helloWorld(safeArray, (MyCSDLL::_MyRetVal) _retValPtr);
Adapting from How to create and initialize SAFEARRAY of doubles in C++ to pass to C#
void createSafeArray(SAFEARRAY** saData, char* charPtr)
{
char* iterator = charPtr;
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = 10;
*saData = SafeArrayCreate(VT_R8, 1, &Bound);
char HUGEP *pdFreq;
HRESULT hr = SafeArrayAccessData(*saData, (void HUGEP* FAR*)&pdFreq);
if (SUCCEEDED(hr))
{
do {
*pdFreq++ = *iterator;
} while (*iterator++);
}
}
How to call hello->helloWorld()? it is expecting SAFEARRAY*. The current code gives 80131538 error. How to fix it?
C++ Project is not CLR.
Let's suppose the C# code is this:
namespace ClassLibrary1
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Hello
{
public void helloWorld(char[] chars)
{
...
}
}
}
Then, you can call it with this C/C++ code, for example:
#import "C:\mycode\ClassLibrary1\bin\Debug\classlibrary1.tlb" raw_interfaces_only
using namespace ClassLibrary1;
HRESULT CallHello(wchar_t* charPtr, int count)
{
CComPtr<_Hello> p;
HRESULT hr = p.CoCreateInstance(__uuidof(Hello));
if (FAILED(hr))
return hr;
SAFEARRAY* psa = SafeArrayCreateVector(VT_UI2, 0, count);
if (!psa)
return E_OUTOFMEMORY;
LPVOID pdata;
hr = SafeArrayAccessData(psa, &pdata);
if (SUCCEEDED(hr))
{
CopyMemory(pdata, charPtr, count * 2); // count is the number of chars
SafeArrayUnaccessData(psa);
hr = p->helloWorld(psa);
}
SafeArrayDestroy(psa);
return hr;
}
.NET's char type is unicode, so the binary size is two bytes, the C equivalent is wchar_t (or unsigned short, etc...). So the safearray element type must match that, that's why I used VT_UI2 (VT_R8 that you used is Real of size 8 bytes, so it's equivalent to .NET's double type).
If you really want to use C's char, then you must do some kind of conversion to a 2-byte character.
Also, you can use the SafeArrayCreateVector function which directly allocates a 1-dimension safe array. Don't forget to call cleanup methods.

LARGE_INTEGER and BYTE types in Linux?

I am studying one Windows code which converting to Linux and this answer here.
It is possible that there are no injective datatypes between LARGE_INTEGER and BYTE in Linux so special situations may need to be considered.
The two data types could possibly replaced by the following two in Linux
uint64_t
unsigned char
where the first one is a part of stdint.h.
I would propose replace both LARGE_INTEGER and BYTE by uint64_t but not certain.
Which Linux datatypes can use in place of LARGE_INTEGER and BYTE?
The following file is a case example of National Instruments' driver for MCA 8000A described here.
MictoTime.h
//////////////////////////////////////////////////////////////////////
//
// MicroTime.h: interface for the MicroTime class.
//
// This header file is a part of the PMCA COM package
//
// Amptek Inc. 2000
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_MICROTIME_H__495147C7_F0B7_11D1_B62F_9CFF0CC10000__INCLUDED_)
#define AFX_MICROTIME_H__495147C7_F0B7_11D1_B62F_9CFF0CC10000__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MicroTimeType
{
LARGE_INTEGER m_startTime;
double m_microScale;
BOOL m_waiting;
public:
MicroTimeType();
LARGE_INTEGER GetCounter();
double Get(void);
double Wait(double microSec);
void CancelWait(void);
};
#endif // !defined(AFX_MICROTIME_H__495147C7_F0B7_11D1_B62F_9CFF0CC10000__INCLUDED_)
Do you need to add here new types of this answer?
Do you need to define uint8_t anywhere else?
You can see the reference for data types in C supported by Windows API and toolchain.
LARGE_INTEGER is actually a union representing signed 64-bit integers which has been historically used in some APIs. Simple typedef int64_t LARGE_INTEGER; won't work as expected.
You will need something like this:
#include <stdint.h>
typedef uint8_t BYTE;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef int64_t LONGLONG;
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

Conversion from string to wstring is causing ú to lose encoding

The variable filepath which is a string contains the value Música. I have the following code:
wstring fp(filepath.length(), L' ');
copy(filepath.begin(), filepath.end(), fp.begin());
fp then contains the value M?sica. How do I convert filepath to fp without losing the encoding for the ú character?
Use the function MultiByteToWideChar.
Sample code:
std::string toStdString(const std::wstring& s, UINT32 codePage)
{
unsigned int bufferSize = (unsigned int)s.length()+1;
char* pBuffer = new char[bufferSize];
memset(pBuffer, 0, bufferSize);
WideCharToMultiByte(codePage, 0, s.c_str(), (int)s.length(), pBuffer, bufferSize, NULL, NULL);
std::string retVal = pBuffer;
delete[] pBuffer;
return retVal;
}
std::wstring toStdWString(const std::string& s, UINT32 codePage)
{
unsigned int bufferSize = (unsigned int)s.length()+1;
WCHAR* pBuffer = new WCHAR[bufferSize];
memset(pBuffer, 0, bufferSize*sizeof(WCHAR));
MultiByteToWideChar(codePage, 0, s.c_str(), (int)s.length(), pBuffer, bufferSize);
std::wstring retVal = pBuffer;
delete[] pBuffer;
return retVal;
}
Since you are using MFC, you have access to the ATL String Conversion Macros.
This greatly simplifies the conversion vs. using MultiByteToWideChar. Assuming that filepath is encoded in your system's default code page, this should do the trick:
CA2W wideFilepath(filepath.c_str());
wstring fp(static_cast<const wchar_t*>(wideFilepath));
If filepath is not in your system's default code page (let's say it's in UTF-8), then you can specify the encoding to convert from:
CA2W wideFilepath(filepath.c_str(), CP_UTF8);
wstring fp(static_cast<const wchar_t*>(wideFilepath));
To convert the other way, from std::wstring to std::string, you would do this:
// Convert from wide (UTF-16) to UTF-8
CW2A utf8Filepath(fp.c_str(), CP_UTF8);
string utf8Fp(static_cast<const char*>(utf8Filepath));
// Or, convert from wide (UTF-16) to your system's default code page.
CW2A narrowFilepath(fp.c_str(), CP_UTF8);
string narrowFp(static_cast<const char*>(narrowFilepath));

multithreaded using _beginthreadex passing parameters

Visual Studio C++ 2008
I am using threads. However, I have this warnings and not sure what I am doing wrong.
1. unsigned int (__stdcall *)(void *) differs in levels of indirection from 'void *(__stdcall *)(void *)'
2. _beginthreadex different types for formal and actual parameters 3
/* Function prototype */
void* WINAPI get_info(void *arg_list)
DWORD thread_hd;
DWORD thread_id;
/* Value to pass to the function */
int th_sock_desc = 0;
/* Create thread */
thread_hd = _beginthreadex(NULL, 0, get_info, (void*) th_sock_desc, 0, &thread_id);
if(thread_hd == 0)
{
/* Failed to create thread */
fprintf(stderr, "[ %s ] [ %s ] [ %d ]\n", strerror(errno), __func__, __LINE__);
return 1;
}
the Thread function that you pass to _beginthreadex has a different prototype than the one you pass to _beginthread
uintptr_t _beginthread(
void( *start_address )( void * ),
unsigned stack_size,
void *arglist
);
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
It's the same as what CreateThread expects,
DWORD WINAPI ThreadProc(
__in LPVOID lpParameter
);
So you need to change the function signature of your thread proc to
unsigned WINAPI get_info(void *arg_list)
remove WINAPI and change the return type.
Edit :
WINAPI is actually needed, the docs show the wrong prototype for _beginthredex, but they explicitly state that __stdcall is needed. Your problem is just the return type. Also, the error message, says that __stdcall is expected, so that settles it.

Resources