To all:
Issue: Does this managed method allocate memory on the native Windows heap with GlobalAlloc() (or LocalAlloc()) where the the GlobalAlloc flags include GMEM_FIXED (or LMEM_FIXED); in other words, the memory is pinned and the HGLOBAL return value is in fact a dereferenced void pointer to the actual object
Assumptions:
- GlobalAlloc() and LocalAlloc() perform the same under the 32-bit flat memory model.
- Similarly, GlobalFree() and LocalFree() perform the same under the 32-bit memory model.
- "IntPtr Marshal.AllocHGlobal(int)" allocates native memory in managed code.
- IntPtr in managed code corresponds to an HGLOBAL type in native code; in other words, IntPtr is windows handle to the address of the (address) memory allocated for the object (ie. struct **)
- "IntPtr Marshal.AllocHGlobal(int)" calls GlobalAlloc() in the kernel32 library with flag=GMEM_FIXED.
- If GMEM_FIXED applies, IntPtr in managed code corresponds to a pointer to void type in native code; in other words, IntPtr is pointer to the actual object (ie. struct *). This then would be similar to GlobalAllocPtr(), a macro in windowx.h, that allocates the memory and locks the memory (ie. GlobalLock()) to obtain the pointer to the object. Correspondingly, GlobalUnlock() would unlock the pointer to the memory. However, where GMEM_FIXED (or LMEM_FIXED) is used no lock count is maintained. Normally, a lock count other than zero would prevent a user from deleting the memory associated with the object. Without a lock count maintained, memory could be deleted directly at any time on the native side (ie. "delete p;"), versus using ::GlobalFree() on the native side, or even "Marshal.FreeHGlobal()" on the managed side.
- "IntPtr Marshal.AllocHGlobal(int)" expects corresponding or matching call to "Marshal.FreeHGlobal()" in managed code.
- "Marshal.FreeHGlobal()" wraps a call to ::GlobalFree() in the Win32 Platform SDK.
Actions:
- I have implemented use of "IntPtr Marshal.AllocHGlobal(int)" in managed code.
- I implemented a COM Class and COM interface in managed code. The interface exposes a method that calls "IntPtr Marshal.AllocHGlobal(int)".
- Native code receives the IntPtr and casts it to an HGLOBAL type. (reinterpret_cast<HGLOBAL>)
- I then call ::GlobalLock() to dereference the handle and get the pointer to the object, a structure.
- Instead of using garbage collection in managed code with a call to "Marshal.FreeHGlobal()" in the COM Class, I instead release the interface and expect to call ::GlobalUnlock() and ::GlobalFree() to free the handle and memory associated with the object structure. -- This all works to far...
- I then tried another round in using the method I exposed in the managed code interface. Instead of casting to an HGLOBAL in native code, I instead casted the return value to the actual object structure type in question. (reinterpret_cast<MyNamespace::MyStruct *>) -- This unexpectedly works giving me the impression that the native memory allocated in managed code is actually fixed (or pinned) native memory on the Windows heap, although I cannot find documentation on this fact under .NET Framework version 2.0.
- Adam Nathan's book, ".NET and COM, The Complete Inrteroperability Guide" provides a sample use of "IntPtr Marshal.AllocHGlobal(int)", pages 258-262. My understanding from his writings is that an IntPtr is an HGLOBAL, a double-pointer ("pointer-to-pointer-to-struct").
I would appreciate it if anyone can shed some light on this area.
James
Dallas, Texas
jmsigler2@hotmail.com

IntPtr Marshal.AllocHGlobal(int) - Passing record from Managed to Unmanaged Code, IntPtr --> HGLOBAL or PVOID?
Dan F. Jansson
Managed Code Sample For The Issue:
using
System;using
System.Configuration;using
System.Data;using
System.Text;using
System.Collections.Generic;using
System.Runtime.InteropServices;namespace
Sample.Test{
#region
public struct MYREC#region
GUID Information {70A7CAE9-093E-4f28-B103-2F554E533ED1}#if
false// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0x70a7cae9, 0x93e, 0x4f28, 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1);
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
DEFINE_GUID(<<name>>,
0x70a7cae9, 0x93e, 0x4f28, 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1);
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
static const GUID <<name>> =
{ 0x70a7cae9, 0x93e, 0x4f28, { 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1 } };
#endif
// false#endregion
// GUID Information {70A7CAE9-093E-4f28-B103-2F554E533ED1} /// <remarks> /// COM-Callable Wrapper - public struct MYREC /// Managed structure to be registered and defined in the IDL file. /// </remarks> /*** Exlicity expose this structure type to COM components.
** Two ways of defining the GUID attribute assigned to a COM component interface.
** [System.Runtime.InteropServices.Guid("70A7CAE9-093E-4f28-B103-2F554E533ED1")]
** [System.Runtime.InteropServices.GuidAttribute("70A7CAE9-093E-4f28-B103-2F554E533ED1")]
*/
/*** See AssemblyInfo.cs for global setting [assembly: ComVisible(false)].
** Set ComVisible attribute to false making this assembly type invisible to COM components.
** Two ways of defining the COM visibility assigned to a COM component interface.
** [System.Runtime.InteropServices.ComVisible(true)]
** [System.Runtime.InteropServices.ComVisibleAttribute(true)]
*/
/*** Options for StructLayout:
** [System.Runtime.InteropServices.StructLayout(LayoutKind.Auto)]
** [System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
** [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
** [System.Runtime.InteropServices.StructLayoutAttribute(LayoutKind.Auto)]
** [System.Runtime.InteropServices.StructLayoutAttribute(LayoutKind.Explicit)]
** [System.Runtime.InteropServices.StructLayoutAttribute(LayoutKind.Sequential)]
*/
[System.Runtime.InteropServices.
GuidAttribute("70A7CAE9-093E-4f28-B103-2F554E533ED1"),System.Runtime.InteropServices.
ComVisibleAttribute(true),System.Runtime.InteropServices.
StructLayoutAttribute(LayoutKind.Sequential/*, Size = XXX*/)] public struct MYREC{
public int m_EMP_ID_LEN; public int m_EMP_FULLNAME_LEN; public int m_EMP_HOMEADDRESS_LEN; public int m_EMP_EMAIL_LEN;}
#endregion
// public struct MYREC#region
interface IMyRec#region
GUID Information {3E9F640C-107E-4bba-9B8C-2E6E2D88D481}#if
false// {3E9F640C-107E-4bba-9B8C-2E6E2D88D481}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0x3e9f640c, 0x107e, 0x4bba, 0x9b, 0x8c, 0x2e, 0x6e, 0x2d, 0x88, 0xd4, 0x81);
// {3E9F640C-107E-4bba-9B8C-2E6E2D88D481}
DEFINE_GUID(<<name>>,
0x3e9f640c, 0x107e, 0x4bba, 0x9b, 0x8c, 0x2e, 0x6e, 0x2d, 0x88, 0xd4, 0x81);
// {3E9F640C-107E-4bba-9B8C-2E6E2D88D481}
static const GUID <<name>> =
{ 0x3e9f640c, 0x107e, 0x4bba, { 0x9b, 0x8c, 0x2e, 0x6e, 0x2d, 0x88, 0xd4, 0x81 } };
#endif
// false#endregion
// GUID Information {3E9F640C-107E-4bba-9B8C-2E6E2D88D481} /// <remarks> /// COM-Callable Wrapper and Interface /// Managed COM interface to be registered, launched, and accessed from native code. /// </remarks> /*** Exlicity expose this structure type to COM components.
** Two ways of defining the GUID attribute assigned to a COM component interface.
** [System.Runtime.InteropServices.Guid("3E9F640C-107E-4bba-9B8C-2E6E2D88D481")]
** [System.Runtime.InteropServices.GuidAttribute("3E9F640C-107E-4bba-9B8C-2E6E2D88D481")]
*/
/*** Interface is exposed to COM component as Dual, IDispatch or IUnknown.
** [System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsDual)]
** [System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
** [System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
** [System.Runtime.InteropServices.InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
** [System.Runtime.InteropServices.InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
** [System.Runtime.InteropServices.InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
*/
/*** See AssemblyInfo.cs for global setting [assembly: ComVisible(false)].
** Set ComVisible attribute to false making this assembly type invisible to COM components.
** Two ways of defining the COM visibility assigned to a COM component interface.
** [System.Runtime.InteropServices.ComVisible(true)]
** [System.Runtime.InteropServices.ComVisibleAttribute(true)]
*/
[System.Runtime.InteropServices.
GuidAttribute("3E9F640C-107E-4bba-9B8C-2E6E2D88D481"),System.Runtime.InteropServices.
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual),System.Runtime.InteropServices.
ComVisibleAttribute(true)] public interface IMyRec{
void GetRecord(ref object objMyRec); IntPtr GetRecordPtr();#region
MyRec Individual Property Accessor Methods int GetEmpIdLen(); int GetEmpFullnameLen(); int GetEmpHomeaddressLen(); int GetEmpEmailLen();#endregion
// MyRec Individual Property Accessor Methods}
#endregion
// interface IMyRec#region
public sealed class MyRec#region
GUID Information {7FB574E5-3D27-4c7d-8531-83B7B6D02DB6}#if
false// {7FB574E5-3D27-4c7d-8531-83B7B6D02DB6}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0x7fb574e5, 0x3d27, 0x4c7d, 0x85, 0x31, 0x83, 0xb7, 0xb6, 0xd0, 0x2d, 0xb6);
// {7FB574E5-3D27-4c7d-8531-83B7B6D02DB6}
DEFINE_GUID(<<name>>,
0x7fb574e5, 0x3d27, 0x4c7d, 0x85, 0x31, 0x83, 0xb7, 0xb6, 0xd0, 0x2d, 0xb6);
// {7FB574E5-3D27-4c7d-8531-83B7B6D02DB6}
static const GUID <<name>> =
{ 0x7fb574e5, 0x3d27, 0x4c7d, { 0x85, 0x31, 0x83, 0xb7, 0xb6, 0xd0, 0x2d, 0xb6 } };
#endif
// false#endregion
// GUID Information {7FB574E5-3D27-4c7d-8531-83B7B6D02DB6} /// <remarks> /// Class: MyRec /// Class Visibility: Public /// Visibility to COM Components: true /// Interfaces Implemented: IMyRec /// Description: Native COM components access this class through IMyRec. /// </remarks> /*** Note: At this time, this class type is not exlicity exposed to COM components.
** Two ways of defining the GUID attribute assigned to a COM component interface.
** [System.Runtime.InteropServices.Guid("7FB574E5-3D27-4c7d-8531-83B7B6D02DB6")]
** [System.Runtime.InteropServices.GuidAttribute("7FB574E5-3D27-4c7d-8531-83B7B6D02DB6")]
*/
/*** Both AutoDual and AutoDispatch auto generate interfaces with unique GUID.
** Both auto generated interface types by-design take the name _MyRec.
** [System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.AutoDual)]
** [System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.AutoDispatch)]
** [System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.None)]
** [System.Runtime.InteropServices.ClassInterfaceAttribute(ClassInterfaceType.AutoDual)]
** [System.Runtime.InteropServices.ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
** [System.Runtime.InteropServices.ClassInterfaceAttribute(ClassInterfaceType.None)]
*/
/*** See AssemblyInfo.cs for global setting [assembly: ComVisible(false)].
** Set ComVisible attribute to false making this assembly type invisible to COM components.
** Two ways of defining the COM visibility assigned to a COM component interface.
** [System.Runtime.InteropServices.ComVisible(true)]
** [System.Runtime.InteropServices.ComVisibleAttribute(true)]
*/
[System.Runtime.InteropServices.
GuidAttribute("7FB574E5-3D27-4c7d-8531-83B7B6D02DB6")][System.Runtime.InteropServices.
ProgIdAttribute("Sample.Test.MyRec")][System.Runtime.InteropServices.
ClassInterfaceAttribute(ClassInterfaceType.None)][System.Runtime.InteropServices.
ComVisibleAttribute(true)] public sealed class MyRec : IMyRec{
[System.Runtime.InteropServices.
ComVisibleAttribute(false)] readonly MYREC _record; // = new MYREC(); public IntPtr _ppMyRecord = IntPtr.Zero; // hglobalMyRecord = IntPtr.Zero;#region
Initialize Constant Field Lengths /*** Note: COM Interop does not permit exporting of const or static values in the IDL file.
** There is a concept of declaring module(s) in IDL files for constant values. I believe
** these values would have to be manually added to an IDL file. A new typelib would then
** be generated with the MIDL compiler versus use of mktyplib<203>.exe (mktypelib version
** 2.03, the last version of the legacy compiler. The MIDL compiler also provides a switch
** "/mktyplib203" for backward compatibility.
*/
public const int EMP_ID_LEN = 20; public const int EMP_FULLNAME_LEN = 60; public const int EMP_HOMEADDRESS_LEN = 60; public const int EMP_EMAIL_LEN = 40;#endregion
// Initialize Constant Field Lengths internal static void PopulateRecord(ref MYREC myrecstr){
myrecstr.m_EMP_ID_LEN = EMP_ID_LEN;
myrecstr.m_EMP_FULLNAME_LEN = EMP_FULLNAME_LEN;
myrecstr.m_EMP_HOMEADDRESS_LEN = EMP_HOMEADDRESS_LEN;
myrecstr.m_EMP_EMAIL_LEN = EMP_EMAIL_LEN;
}
public MyRec(){
PopulateRecord(
ref _record); // Initialize unmanaged memory to hold structure. //_ppMyRecord = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MYREC)));_ppMyRecord =
Marshal.AllocHGlobal(Marshal.SizeOf(_record)); // Copy the struct to unmanaged memory Marshal.StructureToPtr(_record, _ppMyRecord, false);}
~MyRec()
{
/*** Notes: The current approach is for the native client to cleanup and delete the unmanaged memory.
** Otherwise, if the code to free the memory for the structure is activated (uncommented below),
** effective activating garbage collection on the managed side, then the interface must remain active
** on the native client side until shutdown. Also, in this case, deleting the pointer to the structure
** prematurely or otherwise on the client side, will likely lead to unknown or ambiguous side effects.
** Garbage collection on the managed side, if active, expects a pointer to valid memory when the
** destructor is finally called.
*/
//if(IntPtr.Zero != _ppEmpFldLenRec) //{ // // Free the unmanaged memory. // Marshal.FreeHGlobal(_ppEmpFldLenRec); //}}
#region
IMyRec Members public void GetRecord(ref object objMyRec){
MYREC record; // Unbox Typerecord = (
MYREC)objMyRec;PopulateRecord(
ref record); // Box TypeobjMyRec = (
object)record;}
public IntPtr GetRecordPtr(){
//if(IntPtr.Zero == _ppMyRecord) //{ // // Initialize unmanaged memory to hold structure to hold memory. // _ppMyRecord = Marshal.AllocHGlobal(Marshal.SizeOf(_record)); // // // Copy the struct to unmanaged memory // Marshal.StructureToPtr(_record, _ppMyRecord, false); //} return _ppMyRecord;}
#region
Individual Accessor Methods public int GetEmpIdLen() { return EMP_ID_LEN; } public int GetEmpFullnameLen() { return EMP_FULLNAME_LEN; } public int GetEmpHomeaddressLen() { return EMP_HOMEADDRESS_LEN; } public int GetEmpEmailLen() { return EMP_EMAIL_LEN; }#endregion
// Individual Accessor Methods#endregion
// IMyRec Members}
#endregion
// public sealed class MyRec} // end namespace Sample.Test
LadyReader
Native Code Sample For The Issue:
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#if !defined(AFX_STDAFX_H__E9D3F8FE_E7B1_4CEC_8F18_DA2D5FDF4A00__INCLUDED_)
#define AFX_STDAFX_H__E9D3F8FE_E7B1_4CEC_8F18_DA2D5FDF4A00__INCLUDED_
#ifndef WINVER
#define WINVER 0x0500
#endif
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
#define WIN32_LEAN_AND_MEAN
#pragma warning(disable: 4786)
////////////////////////////////////////////
// MFC includes
#include <afx.h>
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
#include <iostream>
// Standard Header Files
#include <stdio.h>
#include <assert.h>
// STL Header Files
#include <string>
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
// For ATL Conversion Macros
#include <atlbase.h> // includes VARIANT ATL wrapper class CCOMVariant
#include <atlconv.h>
//#include <atlcom.h>
// COM Supporting Header Files
#include <rpc.h> // Master include file for RPC applications.
#include <rpcndr.h> // Definitions for stub data structures and prototypes of helper functions.
#include <objbase.h> // Component object model defintions.
#include <oleauto.h> // requires oleaut32.lib
#include <ole2.h> // requires ole32.lib
#include <initguid.h> // Definitions for controlling GUID initialization
#include <comip.h> // COM Wrapper class(es): _com_ptr_t (for Smart Pointers)
#include <comdef.h> // includes VARIANT wrapper class _variant_t
#include <comutil.h> // COM Wrapper class(es): _variant_t, _bstr_t, _com_ptr_t
#include <oaidl.h> // ISupportErrorInfo, IRecordInfo, IErrorInfo, VARIANT
#include <wtypes.h> // RPC-COM Support for interfaces
#include <winnt.h> // TCHAR
#include <tchar.h> // _TCHAR
#include <ctype.h> // typedef unsigned short wchar_t;
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__E9D3F8FE_E7B1_4CEC_8F18_DA2D5FDF4A00__INCLUDED_)
// VC6MfcConsoleTestApp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using namespace std;
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////
// Import Registered Type Libraries from Managed Code
//////////////////////////////////////////////////////////////////////////////////////////////////
#import "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb" no_namespace named_guids raw_interfaces_only
#import "..\..\..\NativeInterface\Sample.Test\bin\Debug\Sample.Test.tlb" named_guids //no_namespace
#if 0
#include ".\Debug\Sample.Test.tlh"
#endif
//////////////////////////////////////////////////////////////////////////
// Function Prototypes for Implementation
/////////////////////////////////////////////////////////////////////////
// Return dynamically allocated pointer. Client is expected to delete allocated memory on shutdown.
const Sample_Test::MyRec *GetMyRecordPtr();
void FreeRecordPtr();
////////////////////////////////////////////////////////////////////////////////////
// Manually Defined Structures need for IRecInfo
////////////////////////////////////////////////////////////////////////////////////
// GUID Information for MYREC
#if 0
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0x70a7cae9, 0x93e, 0x4f28, 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1);
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
DEFINE_GUID(<<name>>,
0x70a7cae9, 0x93e, 0x4f28, 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1);
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
static const GUID <<name>> =
{ 0x70a7cae9, 0x93e, 0x4f28, { 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1 } };
#endif
// {70A7CAE9-093E-4f28-B103-2F554E533ED1}
extern "C" const GUID __declspec(selectany) UDTID_MYREC =
{ 0x70a7cae9, 0x93e, 0x4f28, { 0xb1, 0x3, 0x2f, 0x55, 0x4e, 0x53, 0x3e, 0xd1 } };
//////////////////////////////////////////////////////////////////////
// Global Class to Enable COM Support
//////////////////////////////////////////////////////////////////////
struct COMEngine
{
COMEngine() { ::CoInitialize(NULL); }
~COMEngine() { ::CoUninitialize(); }
};
COMEngine gCOMEngine;
HGLOBAL hglobalRecord = NULL;
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
//int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
int main(int argc, char* argv[])
{
int nRetCode = 0;
NEC_OpenWorX_Native::EmpFldLenRec empfldlenrec;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << /*_T*/("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
// Make the call to managed code here...
const Sample_Test::MyRec *pEmpFldLenRec = GetMyRecordPtr();
return nRetCode;
}
const Sample_Test::MyRec *GetMyRecordPtr()
{
HRESULT hResult = S_OK;
Sample_Test::IMyRec *pIMyRec = NULL;
try
{
/*
** CLSID_MyRec has only one interface (non-aggregate) Sample_Test::IID_IMyRec.
** Therefore, obtaining the IUnknown pointer is unnecessary; just get the Sample_Test::IID_IMyRec interface.
*/
hResult = ::CoCreateInstance(Sample_Test::CLSID_MyRec,
NULL,
CLSCTX_INPROC_SERVER,
Sample_Test::IID_IMyRec,
reinterpret_cast<void**>(&pIMyRec));
if(FAILED(hResult))
_com_raise_error(hResult);
// GMEM_FIXED
// Sample_Test::MyRec *pRecord =
// reinterpret_cast<Sample_Test::EmpFldLenRec*>(pIEmpDBFieldLengths->GetRecordPtr());
hglobalRecord = reinterpret_cast<HGLOBAL>(pIEmpDBFieldLengths->GetRecordPtr());
hResult = pIEmpDBFieldLengths->Release();
if(FAILED(hResult))
_com_raise_error(hResult);
pIEmpDBFieldLengths = NULL;
#ifdef _DEBUG
Sample_Test::MYREC *pRecord = reinterpret_cast<Sample_Test::MYREC *>(GlobalLock(hglobalRecord));
return pRecord;
#else
return reinterpret_cast<Sample_Test::MYREC *>(GlobalLock(hglobalRecord));
#endif
}
catch (_com_error)
{
if(NULL != hglobalRecord)
{
hglobalRecord = ::GlobalFree(hglobalRecord);
assert(NULL == hglobalRecord);
}
return NULL;
}
}
/*
** Note: Two options are available to free the unmanaged memory for the structure.
**
** First: Calling Release() on the interface, allow the managed code destructor to free the
** unmanaged memory with a call to Marshal.FreeHGlobal(). This would be consistent with the
** general rule that the whichever client or server allocates the memory also deallocates the
** memory. This rule, however, is not absolute when working with structures. .NET documentation
** does recommend a call to Marshal.AllocHGlobal() be met with a call to Marshal.FreeHGlobal().
**
** Second: Marshal.AllocHGlobal() and Marshal.FreeHGlobal() use the kernel32 library with Platform
** API calls to GlobalAlloc() and GlobalFree() respectively. Therefore, the native code can unlock
** the memory to a lock count of zero and then call ::GlobalFree() to deallocate the unmanaged memory
** associated with the structure. In this case, the managed code destructure cannot also call
** Marshal.FreeHGlobal() to accomplish the same task.
**
** In either case, normally the lock count on the native side must equal zero before memory can be freed.
*/
void FreeRecordPtr()
{
if(NULL != hglobalRecord)
{
bool bUnlock = GlobalUnlock(hglobalRecord);
hglobalRecord = ::GlobalFree(hglobalRecord);
assert(NULL == hglobalRecord);
}
}
//eof
rogupta
In short, what I gather from you is that the following managed code below will return an HGLOBAL that is actually a PVOID. This is ironic given that the IntPtr class has a method called IntPtr.ToPointer(). I also understand that Marshal.AllocHGlobal(int size) calls to ::LocalAlloc(LPTR, int size) or ::GlobalAlloc(GPTR, int size). I noted from Microsoft documentation that LPTR is (LMEM_FIXED | LMEM_ZEROINIT) and GPTR is (GMEM_FIXED | GMEM_ZEROINIT).
[Managed Code]
class MyClass
{
readonly MYREC _record; // = new MYREC();
public IntPtr _ppMyRecord = IntPtr.Zero; // hglobalMyRecord = IntPtr.Zero;
MyClass()
{
// Populate _record structure here...
_ppMyRecord = Marshal.AllocHGlobal(Marshal.SizeOf(_record));
// Copy the struct to unmanaged memory
Marshal.StructureToPtr(_record, _ppMyRecord, false); }
~MyClass()
{
// Free the unmanaged memory.
// Marshal.FreeHGlobal(_ppMyRecord ); // Free on the native side...
}
public IntPtr GetRecordPtr()
{
return _ppMyRecord;
}
}
[/Managed Code]
I accept your answer as correct; however, I have the following comments:
MSDN Library - October 2001 documentation for Marshal.AllocHGLOBAL states that GlobalAlloc() is called.
MSDN Library - Visual Studio 2005 documentation for Marshal.AllocHGLOBAL(Int32) states that unmanaged memory is allocated using GlobalAlloc(). In the Remarks, on the other hand, it states, "This method exposes the LocalAlloc Win32 API from Kernel32.dll." -- This is poor documentation if you ask me. But, given ::LocalAlloc() and ::GlobalAlloc() are the same under the 32-bit flat memory model, it really does not matter. More importantly though, nothing in the documentation indicates what flags are being used at the Platform SDK level for ::LocalAlloc() or GlobalAlloc(), whichever the case may be. Interestingly enough, treating the IntPtr return value of Marshal.AllocHGLOBAL(Int32) as an HGLOBAL or PVOID works either way. -- I suppose it should with the naming conventions used here.
Windows Architecture
I recall when Windows pre-emptive multitasking came into play where control of the Windows heap management shifted to the OS from the developer. A HANDLE was redefined from a pointer-to-an-object to a pointer-to-a-pointer-to-an-object. This allowed the Windows heap to pre-empt the user by moving memory around even though the user had made a call to ::LocalLock() or ::GlobalLock(); the only exception to the OS moving memory around when it wants to was when the flags LMEM_FIXED or GMEM_FIXED were used. With LMEM_FIXED or GMEM_FIXED, my guess is that windows must assign the same address value to the HANDLE and the pointer-to-the-object in the heap's memory management table. Otherwise, how can both ways work
Bottom Line
I want to pass a structure to native code. After receiving the structure in native code, I want to immediately release the interface while the structure remains in native memory and then use delete to delete the pointer to the structure or use ::GlobalFree(HANDLE) (or ::LocalFree(HANDLE)) to destroy the structure and free the handle on shutdown of the native application. My only caution is that without a lock count maintained by allocating memory with the use of the flags LMEM_FIXED or GMEM_FIXED, anyone with access to the pointer to the structure could decide to delete the pointer to the memory associated with the structure and produce a memory leak or instability. I am going to try assigning the pointer to a const pointer in a native-side class; I am trying to remember if a const-pointer can be deleted or not in Visual C++. I would like to control this aspect or behavior if I can.
James
Dallas, Texas
jmsigler2@hotmail.com
JV Chevy
AllocHGlobal wraps LocalAlloc(LPTR) so what you get back is indeed a pointer, not a handle.