Yeah, I figured you had a fairly good reason to build your own, but I had to ask.
My personal opinion is that projects like this ought be written in managed C++ (it's much easier to play with pointers in C++) and use the MC++ library as a gateway between the managed and unmanaged worlds. That being said, I've included some C# sample code that wraps a call to ldap_modify. When allocating unmanaged memory, the use of finally blocks becomes essential to avoid memory leaks. Since an exception could be thrown at any point in your code, check to see if memory was allocated (non-null arrays, non-IntPtr.Zero IntPtrs) first, then de-allocate the memory. You should be able to take this structure: typedef struct ldapmodW { ULONG mod_op; PWCHAR mod_type; union { PWCHAR *modv_strvals; struct berval **modv_bvals; } mod_vals; } LDAPModW, *PLDAPModW; and convert it to: enum LDAPModificationType { Add = 0, Delete = 1, Replace = 2 // We won't allow the user to specify string/binary - this should be // detected automatically // Binary = 0x80 } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] struct LDAPMod { // Note the use of a enum so we can use that nifty IntelliSense! public LDAPModificationType mod_op; public String mod_type; public IntPtr ppValues; // A pointer to a block of unmanaged memory containing an array of pointers } // Wrapper function // in this case, we'll restrict it to strings, but you could easily change data to Object[] and // automatically detect the object type... void ModifySomething(LDAPModificationType modificationType, String attributeName, String[] data) { LDAPMod ldapmod; IntPtr[] pMods = null; IntPtr[] strings = null; int sizeOfIntPtr; int counter; uint result = 0; // must initialize ppValues before the try block to make the compiler happy ldapmod.ppValues = IntPtr.Zero; try { // Set up the LDAPMod struct ldapmod.mod_op = modificationType; ldapmod.mod_type = attributeName; // In this case, don't use the binary flag - we'll supply strings // We have n strings, and we need a null terminator, so allocate n + 1 slots // You could instead work directly against the ppValue member // variable, but I find it easier to deallocate memory when I cache // the array of pointers in a local variable. strings = new IntPtr[data.Length + 1]; // Convert the strings into unmanaged pointers for(counter = 0; counter < data.Length; counter++) { strings[counter] = Marshal.StringToCoTaskMemUni(data[counter]); } strings[counter + 1] = IntPtr.Zero; // Null terminator // One *could* assume that the length of an IntPtr is 4... sizeOfIntPtr = Marshal.SizeOf(typeof(IntPtr)); // Allocate memory to store the array of pointers ldapmod.ppValues = Marshal.AllocCoTaskMem(sizeOfIntPtr * strings.Length); // Stuff the pointers into the array for(counter = 0; counter < strings.Length; counter++) Marshal.WriteIntPtr(ldapmod.ppValues, counter * sizeOfIntPtr, strings[counter]); // LDAPMod struct is now initialized // Allocate the array of LDAPMods - one plus a null terminator pMods = new IntPtr[2]; // Allocate unmanaged memory to hold the LDAPMod struct pMods[0] = Marshal.AllocCoTaskMem(Marshal.SizeOf(ldapmod)); // Stuff the struct into our array of LDAPMods Marshal.StructureToPtr(ldapmod, pMods[0], false); // Null terminator pMods[1] = IntPtr.Zero; // Finally! We get to call our function. // pLDAP is a member variable obtained when ldap_init is called // (or passed in a constructor, etc.) result = ldap_modify(this.pLDAP, "<dn goes here>", pMods); // Do something with the result } finally { // Time to free up the unmanaged memory! // Deallocate memory if the pointer is non-null. // I find it easier to read my code when I free memory in // the reverse of the allocation order. if(ldapmod.ppValues != IntPtr.Zero) Marshal.FreeCoTaskMem(ldapmod.ppValues); if(strings != null) { for(counter = 0; counter < strings.Length; counter++) { if(strings[counter] != IntPtr.Zero) Marshal.FreeCoTaskMem(strings[counter]); } } if(pMods != null && pMods[0] != IntPtr.Zero) Marshal.FreeCoTaskMem(pMods[0]); } } > Hi Andy > > System.DirectoryServices is just a wrapper around ADSI AFAIK. I already > have a VB6 library with classes that wrap around ADSI and while that is > well optimized, my LDAP library, so far, can read up to 7 times faster. > > Also there is much more scope for other things such as multi-threading (my > library can run the same search on multiple roots at the same time) and use > of Server Controls (I have managed to get a Persistent Search working) etc. > > > > I think I know what I want to do, but I find it difficult to work out > exactly how to do it. > > My plan now is:- > > Write an LDAP modification class that accepts a string[] of the new values, > uses Marshal.StringToHGlobalAuto to make a copy of them (no need to worry > about GC moving things then) and store the IntPtrs in an array (also in > HGlobal alloc'ed memory), that way I just have an IntPtr to the start of > this array to store in the structure which, I believe (hope), will be > easily marshalled and passed to the LDAP API. If I then add IDispose > functionality the class will be self-contained and relatively efficient > memory wise. > > Can anyone see any problems with the above approach? > > Cheers > Simon > > PS It would be nice to be able to just get an IntPtr directly to the > string, which I believe is an optimization used by the Marshaler since it > is immutable, to save having to copy the string. Is this possible and what > could I do about ensuring it is pinned for the duration? Does anyone have > any insight on this? You can read messages from the Advanced DOTNET archive, unsubscribe from Advanced DOTNET, or subscribe to other DevelopMentor lists at http://discuss.develop.com.